
【Go学习】04-2-Gin框架-模板渲染会话中间件
【Go学习】04-2-Gin框架-模板渲染会话中间件
【Go学习】04-2-Gin框架-模板渲染会话中间件
模板渲染
模板是golang语言的一个标准库,使用场景很多,gin框架同样支持模板
基本使用
定义一个存放模板文件的templates
文件夹
创建helloworld/templates/index.tmpl
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>gin_templates</title>
</head>
<body>
{{.title}}
</body>
</html>
后端代码:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// 模板解析
r.LoadHTMLFiles("templates/index.tmpl")
r.GET("/index", func(c *gin.Context) {
// HTML请求
// 模板的渲染
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "hello 模板",
})
})
r.Run(":9090") // 启动server
}
多模板渲染
如果有多个模板,可以统一进行渲染
// 模板解析,解析templates目录下的所有模板文件
r.LoadHTMLGlob("templates/**")
如果目录为templates/post/index.tmpl
和templates/user/index.tmpl
这种,可以
// **/* 代表所有子目录下的所有文件
router.LoadHTMLGlob("templates/**/*")
自定义模板函数
修改前端
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>gin_templates</title>
</head>
<body>
{{.title | safe }}
</body>
</html>
safe相当于给title又加了个函数
// gin框架给模板添加自定义函数
r.SetFuncMap(template.FuncMap{
"safe": func(str string) template.HTML {
return template.HTML(str)
},
})
// 模板解析,解析templates目录下的所有模板文件
r.LoadHTMLGlob("templates/**")
r.GET("/index", func(c *gin.Context) {
// HTML请求
// 模板的渲染
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "<a href='http://baidu.com'>跳转到其他地方</a>",
})
})
静态文件处理
创建helloworld/static/css/index.css
body{
background-color: aqua;
}
修改helloworld/templates/index/index.tmpl引入css
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>gin_templates</title>
<link rel="stylesheet" href="/css/index.css">
</head>
<body>
{{.title | safe }}
</body>
</html>
静态文件映射
// 加载静态文件
r.Static("/css", "./static/css")
前端要/css,那么我们映射到./static/css下
func main() {
r := gin.Default()
// gin框架给模板添加自定义函数
r.SetFuncMap(template.FuncMap{
"safe": func(str string) template.HTML {
return template.HTML(str)
},
})
// 模板解析,解析templates目录下的所有模板文件
r.LoadHTMLGlob("templates/**/*")
r.GET("/index", func(c *gin.Context) {
// HTML请求
// 模板的渲染
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "<a href='http://baidu.com'>跳转到其他地方</a>",
})
})
r.Static("/css", "./static/css") // 静态文件处理
r.Run(":9090") // 启动server
}
这样我们就成功引入静态文件了
会话
会话控制涉及到cookie和session的使用
cookie
- HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出
- Cookie就是解决HTTP协议无状态的方案之一
- Cookie实际上就是服务器保存在浏览器上的一段信息。浏览器有了Cookie之后,每次向服务器发送请求时都会同时将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求
- Cookie由服务器创建,并发送给浏览器,最终由浏览器保存
设置cookie
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
参数说明:
参数名 | 类型 | 说明 |
---|---|---|
name | string | cookie名字 |
value | string | cookie值 |
maxAge | int | 有效时间,单位是秒,MaxAge=0 忽略MaxAge属性,MaxAge<0 相当于删除cookie, 通常可以设置-1代表删除,MaxAge>0 多少秒后cookie失效 |
path | string | cookie路径 |
domain | string | cookie作用域 |
secure | bool | Secure=true,那么这个cookie只能用https协议发送给服务器 |
httpOnly | bool | 设置HttpOnly=true的cookie不能被js获取到 |
r.GET("/cookie", func(c *gin.Context) {
// 设置cookie
c.SetCookie("site_cookie", "cookievalue", 3600, "/", "localhost", false, true)
})
在前端也能在cookie找到我们设置的cookie
读取cookie
因为我们刚刚设置了个key叫site_cookie,value叫cookievalue的cookie,时间使6分钟,我们可以读取出来
r.GET("/read", func(c *gin.Context) {
// 根据cookie名字读取cookie值
data, err := c.Cookie("site_cookie")
if err != nil {
c.JSON(200, err.Error())
return
}
c.JSON(200, data)
})
删除cookie
通过将cookie的MaxAge设置为-1, 达到删除cookie的目的。
r.GET("/del", func(c *gin.Context) {
// 设置cookie MaxAge设置为-1,表示删除cookie
c.SetCookie("site_cookie", "cookievalue", -1, "/", "localhost", false, true)
c.String(200,"删除cookie")
})
Session
在Gin框架中,我们可以依赖gin-contrib/sessions中间件处理session。
支持cookie、redis、mongodb等等
安装session包
go get github.com/gin-contrib/sessions
但是这个包依赖个github.com/chenzhuoyu/iasm
,这个已经不更新了,我们替换成github.com/cloudwego/iasm v0.2.0
,所以在go.mod中添加
replace github.com/chenzhuoyu/iasm => github.com/cloudwego/iasm v0.2.0
使用demo
func main() {
r := gin.Default()
// 创建基于cookie的存储引擎,secret 参数是用于加密的密钥
store := cookie.NewStore([]byte("secret"))
// 设置session中间件,参数mysession,指的是session的名字,也是cookie的名字
// store是前面创建的存储引擎,我们可以替换成其他存储引擎
r.Use(sessions.Sessions("mysession", store))
r.GET("/hello", func(c *gin.Context) {
// 初始化session对象
session := sessions.Default(c)
// 通过session.Get读取session值
// session是键值对格式数据,因此需要通过key查询数据
if session.Get("hello") != "world" {
fmt.Println("没读到")
// 设置session数据
session.Set("hello", "world")
session.Save()
}
c.JSON(200, gin.H{"hello": session.Get("hello")})
})
r.Run(":8080")
}
-
cookie.NewStore([]byte("secret"))
创建了一个基于 Cookie 的Session
存储引擎,"secret"
是加密密钥。 -
sessions.Sessions("mysession", store)
注册Session
中间件:mysession
是 SessionID 在 Cookie 里的 key。store
指定存储引擎(这里是 Cookie)。
这样就相当于session存储在cookie里,cookie作为存储引擎
我们访问http://localhost:8000/hello这样就能在cookie里找到保存着我们session的cookie了,不过我们一般选择redis作为我们的存储引擎,因为cookie一是不安全,二是可以被手动删除
多Session
func main() {
r := gin.Default()
store := cookie.NewStore([]byte("secret"))
sessionNames := []string{"a", "b"}
r.Use(sessions.SessionsMany(sessionNames, store))
r.GET("/hello", func(c *gin.Context) {
sessionA := sessions.DefaultMany(c, "a")
sessionB := sessions.DefaultMany(c, "b")
if sessionA.Get("hello") != "world!" {
sessionA.Set("hello", "world!")
sessionA.Save()
}
if sessionB.Get("hello") != "world?" {
sessionB.Set("hello", "world?")
sessionB.Save()
}
c.JSON(200, gin.H{
"a": sessionA.Get("hello"),
"b": sessionB.Get("hello"),
})
})
r.Run(":8080")
}
基于redis存储引擎的session
如果我们想将session数据保存到redis中,只要将session的存储引擎改成redis即可。
使用redis作为存储引擎的例子:
首先安装redis存储引擎的包
PS F:\Code\Golang\TuLing\workPath\ginlearn\helloworld> go get github.com/gin-contrib/sessions/redis
编写程序
func main() {
r := gin.Default()
// 初始化基于redis的存储引擎
// 参数说明:
// 第1个参数 - redis最大的空闲连接数
// 第2个参数 - 数通信协议tcp或者udp
// 第3个参数 - redis地址, 格式,host:port
// 第4个参数 - redis密码
// 第5个参数 - session加密密钥
store, _ := redis.NewStore(10, "tcp", "192.168.101.68:6379", "redis", []byte("secret"))
r.Use(sessions.Sessions("mysession", store))
r.GET("/incr", func(c *gin.Context) {
session := sessions.Default(c)
var count int
v := session.Get("count")
if v == nil {
count = 0
} else {
count = v.(int)
count++
}
session.Set("count", count)
session.Save()
c.JSON(200, gin.H{"count": count})
})
r.Run(":8080")
}
运行查看redis
这要这个session存在,那么刷新页面这个count就++
中间件
在Gin框架中,中间件(Middleware)指的是可以拦截http请求-响应生命周期的特殊函数,在请求-响应生命周期中可以注册多个中间件,每个中间件执行不同的功能,一个中间执行完再轮到下一个中间件执行。
中间件的常见应用场景如下:
- 请求限速
- api接口签名处理
- 权限校验
- 统一错误处理
Gin支持设置全局中间件和针对路由分组设置中间件,设置全局中间件意思就是会拦截所有请求,针对分组路由设置中间件,意思就是仅对这个分组下的路由起作用。
中间件使用
func main() {
r := gin.New()
// 通过use设置全局中间件
// 设置日志中间件,主要用于打印请求日志
r.Use(gin.Logger())
// 设置Recovery中间件,主要用于拦截panic错误,不至于导致进程崩掉
r.Use(gin.Recovery())
r.GET("/test", func(ctx *gin.Context) {
panic(errors.New("test error"))
})
r.Run(":8080")
}
-
r.Use(gin.Logger())
r.Use(gin.Logger())
这个中间件用于记录请求日志。每次请求到来时,它会打印出请求的相关信息,比如请求方法、路径、状态码、响应时间等。它会帮助你快速追踪 API 的调用情况。
-
gin.Recovery()
r.Use(gin.Recovery())
这个中间件的作用是拦截
panic
错误,确保程序不会因为未处理的异常而崩溃。当程序发生panic
时,它会捕获异常并恢复程序,返回 500 状态码的错误响应。这样可以避免服务器崩溃,并且确保返回一个友好的错误页面。
自定义中间件
使用Use可以使用gin自带的中间件或者其他第三方中间件,也可以自己开发中间件
自己实现中间件自然要返回gin.HandlerFunc
,这是个接口
type HandlerFunc func(*Context)
所以返回这个即可
package main
// 导入gin包
import (
"github.com/gin-gonic/gin"
"log"
"time"
)
// 自定义个日志中间件
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
// 可以通过上下文对象,设置一些依附在上下文对象里面的键/值数据
c.Set("example", "12345")
log.Println("before action")
// 在这里处理请求到达控制器函数之前的逻辑
// 调用下一个中间件,或者控制器处理函数,具体得看注册了多少个中间件。
c.Next()
log.Println("after action")
// 在这里可以处理请求返回给用户之前的逻辑
latency := time.Since(t)
log.Print(latency)
// 例如,查询请求状态吗
status := c.Writer.Status()
log.Println(status)
}
}
func main() {
r := gin.New()
// 注册上面自定义的日志中间件
r.Use(Logger())
r.GET("/test", func(c *gin.Context) {
// 查询我们之前在日志中间件,注入的键值数据
example := c.MustGet("example").(string)
// it would print: "12345"
log.Println(example)
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
响应如下:
[GIN-debug] Listening and serving HTTP on :8080
2025/03/11 20:13:24 before action
2025/03/11 20:13:24 12345
2025/03/11 20:13:24 after action
2025/03/11 20:13:24 27.1478ms
2025/03/11 20:13:24 200
/test被拦截先执行logger然后再c.Next()时执行/test方法,然后进行logger的后续方法。
如果有多个中间件(比如 Logger1
、Logger2
和 Logger3
)注册在 Gin 路由上,它们的执行顺序依然是由 r.Use()
注册的顺序决定的。即使是多个日志中间件,它们会按照你注册的顺序依次执行。
更多推荐
所有评论(0)