Gin学习笔记-A

发布时间 2023-04-19 23:32:58作者: 喵喵队立大功

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.Setc.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.comb.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 单位是秒
})