Gin学习笔记-A
fresh包可以实现预加载
预定义函数
预定义的全局函数,用在html文件中
and | 函数返回它的第一个empty参数或者最后一个参数就是说"and x y"等价于"if x then y else x":所有参数都会执行 |
---|---|
or | 返回第一个非empty参数或者最后一个参数亦"or x y"等价于"if x then x else y":所有参数都会执行 |
not | 返回它的单个参数的整数类型长度 |
len | 返回它的参数的整数类型长度 |
index | 执行结果为第一个参数以剩下的参数为索引/键指向的值 |
自定义模板函数
// 时间戳转换成日期
func UnixToTime(timestamp int) string {
t := time.Unix(int64(timestamp), 0)
return t.Format("2006-01-01 15:04:05")
}
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// 自定义模板函数,注意这个函数要放在加载模板前
r.SetFuncMap(template.FuncMap{
"UnixToTime": UnixToTime,
})
// 配置模板的文件,多层级的时候就要这样配置
r.LoadHTMLGlob("templates/**/*")
}
在对应的html文件中
<!-- 这样调用 -->
{{UnixToTime .date}}
模板嵌套
在一个html里面嵌套另外一个html文件
比如这个page_header:
{{define "public/page_header.html" }}
<h1>我是一个公共的标题</h1>
{{end}}
把它添加到另一个index.html里面
<!--相当于给模板定义一个名字define end 成对出现-->
{{define "test/index.html"}}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<!-- 放在这里-->
{{template "public/page_header.html" .}}
<header>
<!-- 定义变量-->
{{$t := .title}}
<h4>这里测试变量标题:{{$t}}</h4>
测试gin的html模板文件{{.title}}
新闻内容{{.news.Title}}
我是新的模板页面!!~~~
</header>
</body>
</html>
{{end}}
静态文件服务
// 创建一个默认的路由引擎
r := gin.Default()
// 自定义模板函数,注意这个函数要放在加载模板前
r.SetFuncMap(template.FuncMap{
"UnixToTime": UnixToTime,
})
// 配置模板的文件,多层级的时候就要这样配置
r.LoadHTMLGlob("templates/**/*")
// 配置静态服务web目录 第一个参数表示路由,第二个参数表示映射目录
r.Static("/static", "././static")
接口传值
GET post以及动态路由传值、Get Post数据解析到结构体、Post xml数据解析到结构体
GET传值
r.GET("/", func(c *gin.Context) {
username := c.Query("username")
age := c.Query("age")
page := c.DefaultQuery("page", "1") // 没传的话设置成默认值
c.JSON(http.StatusOK, gin.H{
"username": username,
"age": age,
"page": page,
})
})
POST传值
r.POST("/add", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
age := c.DefaultPostForm("age", "20")
c.JSON(http.StatusOK, gin.H{
"username": username,
"password": password,
"age": age,
})
})
获取GET、POST传递的数据绑定到结构体
要求传过来的值和结构体tag里面form对应的值对应上
/?username=zhangsan&password=123456
// 注意首字母大写
type Userinfo struct {
Username string `form:"username" json:"user"`
Password string `form:"password" json:"password"`
}
// 获取GET POST 传递的数据绑定到结构体
r.GET("/getUser", func(c *gin.Context) {
user := &Userinfo{}
if err := c.ShouldBind(&user); err == nil {
fmt.Printf("%#v", user)
c.JSON(http.StatusOK, user)
} else {
c.JSON(http.StatusOK, gin.H{
"err": err.Error(),
})
}
})
// POST和GET用法相同
XML绑定结构体
<?xml.version="1.@"encoding="UTF-8"?>
<article>
<content type="string">我是张=</content>
<title type="string">张=</title>
</article>
type Article struct {
Title string `form:"title" xml:"title"`
Content string `form:"content" xml:"content"`
}
func main() {
// 返回XML数据
r.POST("/xml", func(c *gin.Context) {
article := &Article{}
xmlSliceData, _ := c.GetRawData() // 获取 c.Request.Body 读取请求数据
if err := xml.Unmarshal(xmlSliceData, &article); err == nil {
c.JSON(http.StatusOK, article)
fmt.Printf("%#v", article)
} else {
c.JSON(http.StatusBadRequest, gin.H{
"err": err.Error(),
})
}
})
}
动态路由传值
// 动态路由传值
r.GET("/list/:cid", func(c *gin.Context) {
cid := c.Param("cid")
c.String(200, "%v", cid)
})
路由分组
apiRouters := r.Group("/api")
{
apiRouters.GET("/", func(c *gin.Context) {
c.String(200, "我是一个api接口")
})
apiRouters.GET("/userlist", func(c *gin.Context) {
c.String(200, "我是一个api接口-userlist")
})
r.GET("/plist", func(c *gin.Context) {
c.String(200, "我是一个api接口-plist")
})
}
自定义控制器
可以拆分成router路由文件和controller文件,一个处理路由,一个处理路由对应的响应,做到一个解耦
package api
import "github.com/gin-gonic/gin"
type UserController struct {
}
func (con UserController) List(c *gin.Context) {
c.String(200, "我是一个api接口-userlist,单独放在api文件夹下")
}
中间件
Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子 (Hook) 函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
通俗理解:就是匹配路由前和匹配路由完成后执行的一系列操作
路由中间件
func initMiddleware(c *gin.Context) {
fmt.Println("aaa")
}
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// 自定义模板函数,注意这个函数要放在加载模板前
r.SetFuncMap(template.FuncMap{
"UnixToTime": UnixToTime,
})
// 配置模板的文件,多层级的时候就要这样配置
r.LoadHTMLGlob("templates/**/*")
// 配置静态服务web目录 第一个参数表示路由,第二个参数表示映射目录
r.Static("/static", "././static")
//演示中间件
r.GET("/", initMiddleware, func(c *gin.Context) {
c.String(200, "gin首页")
})
}
func initMiddleware(c *gin.Context) {
start := time.Now().UnixNano()
fmt.Println("1我是中间件")
// 调用该请求的剩余处理程序
c.Next()
fmt.Println("2我是一个中间件")
end := time.Now().UnixNano()
fmt.Println(end - start) // 花费的时间
}
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// 自定义模板函数,注意这个函数要放在加载模板前
r.SetFuncMap(template.FuncMap{
"UnixToTime": UnixToTime,
})
// 配置模板的文件,多层级的时候就要这样配置
r.LoadHTMLGlob("templates/**/*")
// 配置静态服务web目录 第一个参数表示路由,第二个参数表示映射目录
r.Static("/static", "././static")
//演示中间件
r.GET("/", initMiddleware, func(c *gin.Context) {
fmt.Println("这是一个首页")
c.String(200, "gin首页")
})
}
// 执行结果
/*
1我是中间件
这是一个首页
2我是一个中间件
*/
c.Next()
会跳过这个中间件执行后面路由的处理程序,执行完之后再回到这个函数里面执行c.Next()后面的语句
c.Abort()
表示终止调用该请求的剩余处理程序
func initMiddleware(c *gin.Context) {
start := time.Now().UnixNano()
fmt.Println("1我是中间件")
c.Abort() // 会跳过后面路由的处理,不进到后面的方法中,而是直接往下进行
fmt.Println("2我是一个中间件")
end := time.Now().UnixNano()
fmt.Println(end - start) // 花费的时间
}
/*
最终结果:
1我是中间件
2我是一个中间件
*/
多个中间件
func initMiddlewareOne(c *gin.Context) {
fmt.Println("我是中间件One")
// 调用该请求的剩余处理程序
c.Next()
fmt.Println("我是一个中间件One")
}
func initMiddlewareTwo(c *gin.Context) {
fmt.Println("我是中间件Two")
// 调用该请求的剩余处理程序
c.Next()
fmt.Println("我是一个中间件Two")
}
func main() {
//...
//演示中间件
r.GET("/", initMiddlewareOne, initMiddlewareTwo, func(c *gin.Context) {
fmt.Println("这是一个首页")
c.String(200, "gin首页")
})
}
/*
执行结果:感觉有点像递归...
我是中间件One
我是中间件Two
这是一个首页
我是一个中间件Two
我是一个中间件One
*/
全局中间件
// 全局中间件
r.Use(initMiddlewareOne, initMiddlewareTwo)
在路由分组中配置中间件
为路由组注册中间件有两种写法:
写法1:
apiRouters := r.Group("/api",xxxx) // 直接在Group后面的参数xxxx的位置写中间件
写法2:
apiRouters := r.Group("/api")
apiRouters.Use(xxxx) // 使用Use写中间件
可以把中间件单独写在一个文件中,再别的地方引入,如写法1和2中所示
package middlewares
import (
"fmt"
"github.com/gin-gonic/gin"
"time"
)
func InitMiddleware(c *gin.Context) {
// 判断用户是否登录
fmt.Println(time.Now())
fmt.Println(c.Request.URL)
}
中间件和对应控制器之间共享数据
设置值
ctx.Set("username","张三")
获取值
username, _ := ctx.Get("username")
gin默认中间件
gin.Default默认使用了Logger和Recovery中间件,其中:
- Logger中间件将日志写入
gin.DefaultWriter
,即使配置了GIN_MODE=release
- Recovery中间件会recover任何panic,如果有panic的话,会写入500响应码
如果不想使用上面两个默认的中间件,可以使用gin.New()
新建一个没有任何默认中间件的路由。
但是推荐default
gin中间件中使用goroutine
当在中间件或 handler 中启动新的 goroutine 时,不能使用原始的上下文 c *gin.Context
,必须使用其只读副本c.Copy()
Gin中自定义Model
我们的应用非常简单的话,我们可以在Controller中处理常见的业务逻辑,但是如果我们有一个功能想在多个控制器、或者多个模板里面复用的话,那么我们就可以把公共的功能单独抽取出来作为一个模块(Model)。Model是逐步抽象的过程,一般我们会在Model里面封装一些公共的方法让不同的Controller使用,也可以在Model中实现和数据库打交道
新建一个models文件夹,tools.go
package models
import (
"fmt"
"time"
)
// 时间戳转换成日期
func UnixToTime(timestamp int) string {
fmt.Println(timestamp)
t := time.Unix(int64(timestamp), 0)
return t.Format("2006-01-02 15:04:05")
}
在mian.go里面注册
// 创建一个默认的路由引擎
r := gin.Default()
// 自定义模板函数,注意这个函数要放在加载模板前
r.SetFuncMap(template.FuncMap{
"UnixToTime": models.UnixToTime,
})
在对应的index.html
文件中可以调用这个方法
<body>
<!--> .t是在定义模板文件时传过来的变量 </-->
{{UnixToTime .t}}
</body>
如果某个功能既在模板中使用又在控制器中使用,可以写在定义的Model里面
方便多人分组开发
单文件和多文件上传
<form action="/admin/user/doUpload" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username" placeholder="用户名"><br/>
头像:<input type="file" name="face"><br/>
<input type="submit" value="提交">
</form>
在controller里面配置静态文件,访问这个某个路由的时候,加载包含上面代码的HTML文件
enctype="multipart/form-data"
必须加上
单文件上传
func (con UserController) DoUpload(c *gin.Context) {
username := c.PostForm("username")
file, err := c.FormFile("face")
// file.Filename 获取文件名称 aaa.png ./static/upload/aaa.jpg 保存路径
dst := path.Join("/static/upload", file.Filename)
if err == nil {
c.SaveUploadedFile(file, dst)
}
//c.String(200, "执行上传")
c.JSON(http.StatusOK, gin.H{
"success": true,
"username": username,
"dst": dst,
})
}
多文件上传
<form action="/admin/user/doEdit" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username" placeholder="用户名"><br/>
头像1:<input type="file" name="face1"><br/>
头像2:<input type="file" name="face2"><br/>
<input type="submit" value="提交">
</form>
func (con UserController) DoEdit(c *gin.Context) {
username := c.PostForm("username")
file1, err1 := c.FormFile("face1")
// file.Filename 获取文件名称 aaa.png ./static/upload/aaa.jpg 保存路径
if err1 == nil {
dst := path.Join("/static/upload", file1.Filename)
c.SaveUploadedFile(file1, dst)
}
file2, err2 := c.FormFile("face2")
// file.Filename 获取文件名称 aaa.png ./static/upload/aaa.jpg 保存路径
if err2 == nil {
dst := path.Join("/static/upload", file2.Filename)
c.SaveUploadedFile(file2, dst)
}
//c.String(200, "执行上传")
c.JSON(http.StatusOK, gin.H{
"success": true,
"username": username,
})
}
相同名字的多个文件上传
<form action="/admin/user/doUpload" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username" placeholder="用户名"><br/>
头像1:<input type="file" name="face[]"><br/>
头像2:<input type="file" name="face[]"><br/>
头像3:<input type="file" name="face[]"><br/>
<input type="submit" value="提交">
</form>
func (con UserController) DoUpload(c *gin.Context) {
username := c.PostForm("username")
form,_ := c.MultipartForm()
files := form.File["face[]"]
for _, file := range files{
dst := path.Join("/static/upload", file.Filename)
// 上传文件至指定目录
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"username": username,
})
}
Gin按照日期存储图片
// 获取年月日
func GetDay() string {
template := "20060102"
return time.Now().Format(template)
}
// 获取时间戳
func GetUnix() int64 {
return time.Now().Unix()
}
/*
1.获取上传的文件
2.获取后缀名 判断类型是否正确 .jpg .png .gif .jpeg
3.创建图片保存目录 static/upload/20230623
4.生成文件名称和文件保存的目录
5.执行上传
*/
func (con UserController) DoUpload(c *gin.Context) {
username := c.PostForm("username")
// 1.获取上传的文件
file, err := c.FormFile("face")
if err == nil {
// 2.获取后缀名 判断类型是否正确 .jpg .png .gif .jpeg
extName := path.Ext(file.Filename)
allowExtMap := map[string]bool{
".jpg": true,
".png": true,
".gif": true,
".jpeg": true,
}
if _, ok := allowExtMap[extName]; !ok {
c.String(200, "上传的文件类型不合法")
return
}
// 3.创建图片保存目录 static/upload/20230419
day := models.GetDay()
dir := "./static/upload/" + day
os.MkdirAll(dir, 0666) // 创建文件
if err != nil {
fmt.Println(err)
c.String(200, "MkdirAll失败")
return
}
// 4.生成文件名称和文件保存目录
fileName := strconv.FormatInt(models.GetUnix(), 10) + extName
// 5.执行上传
dst := path.Join(dir, fileName)
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"username": username,
})
}
Gin中的Cookie以及多个域名共享Cookie
cookie保存在客户端浏览器中
可以实现的功能:
- 保持用户登录状态
- 保存用户浏览的历史记录
- 猜你喜欢,智能推荐
- 电商网站的加入购物车
在访问一个页面的时候,可以用c.Set
和c.Get
来交换数据,但如果是不同的页面,则需要cookie
设置Cookie
c.SetCookie(name, value string, maxAge int,path,domain string, secure, httpOnly bool)
- 第一个参数 key
- 第二个参数 value
- 第三个参数 过期时间,如果只想设置Cookie的保存路径而不想设置存活时间,可以在第三个参数中传递nil
- 第四个参数 cookie的路径
- 第五个参数 cookie的路径Domain作用域,本地调式配置成localhost,正式上线配置成域名
- 第六个参数 secure,当secure值为true时,cookie在HTTP中是无效的,在HTTPS中才有效
- 第七个参数 httpOnly,是微软对Cookie做的扩展,如果在Cookie中设置了"httpOnly"属性,则通过程序(JS脚本、applet等)将无法读取到Cookie的信息,防止XSS攻击产生
// 设置cookie
func (con DefaultController) Index(c *gin.Context) {
c.SetCookie("username", "张三", 3600, "/", "localhost", false, false)
// 设置较短的过期时间
c.SetCookie("hobby", "吃饭", 5, "/", "localhost", false, false)
}
// 获取cookie
func (con DefaultController) News(c *gin.Context) {
username, _ := c.Cookie("username")
c.String(200, "cookie"+username)
}
// 删除cookie
func (con DefaultController) DeleteCookie(c *gin.Context) {
// 删除cookie
c.SetCookie("username", "张三", -1, "/", "localhost", false, true)
c.String(200, "删除成功")
}
多个二级域名共享cookie
a.test.com
和b.test.com
func (con DefaultController) Index(c *gin.Context) {
c.SetCookie("username", "张三", 3600, "/", ".test.com", false, false)
}
Gin中Session设置获取以及分布式Session
session保存在服务器上
sessionId会放在cookie里面保存在客户端
Gin官方没有提供Session相关的文档,我们可以使用第三方的Session中间件来实现
https://github.com/gin-contrib/sessions
git-contrib/sessions
中间件支持的存储引擎
- cookie
- memstore
- redis
- memcached
- mongodb
引入插件之后在mian.go里面配置session
// 配置session中间件
// 创建基于cookie的存储引擎,secret11111 参数是用于加密的秘钥
store := cookie.NewStore([]byte("secret111"))
//配置session中间件 store 是前面创建的存储引擎,我们可以替换成其他存储引擎
r.Use(sessions.Sessions("mysession", store))
在controller里面使用的时候
func (con UserController) Index(c *gin.Context) {
// 设置sessions
session := sessions.Default(c)
session.Set("username", "张三111")
session.Save() // 设置session的时候必须调用
}
func (con UserController) GetSession(c *gin.Context) {
// 获取sessions
session := sessions.Default(c)
username := session.Get("username")
c.String(200, "username=%v", username)
}
如果负载均衡,那么session可以存放在redis
想要存储在redis,需要下启动redis,然后重新配置存储引擎
store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret11111"))
r.Use(sessions.Sessions("mysession", store))
如果想要设置session过期时间
session := sessions.Default(c)
session.Options(sessions.Options{
MaxAge: 3600 * 6, // 6 hrs MaxAge 单位是秒
})