Gin笔记

一、环境搭建:

Gin是一个很受欢迎的Go web框架

下载前在Goland最好配置一下代理

GOPROXY=https://goproxy.cn,direct

在Goland中创建Go项目之后,下载Gin的依赖

go get -u github.com/gin-gonic/gin

安装postman

修改ip为内网ip

router.Run("0.0.0.0:8080")

二、响应

响应json:

状态码200等价于http.StatusOk

package main

import "github.com/gin-gonic/gin"

func _string(c *gin.Context) {
	c.String(200, "你好呀")
}

func _json(c *gin.Context) {
	// json响应结构体
	//type Userinfo struct {
	//	UserName string `json:"user_name"`
	//	Age      int    `json:"age"`
	//	Password string `json:"-"` // 忽略会转换成json
	//}
	//user := Userinfo{
	//	UserName: "百香果",
	//	Age:      23,
	//}

	// json响应map
	//userMap := map[string]string{
	//	"username": "草莓",
	//	"age":      "25",
	//}

	// 直接响应json
	c.JSON(200, gin.H{"userName": "芒果", "age": "26"})

	//c.JSON(200, user)
	//c.JSON(200, userMap)
}

func main() {
	router := gin.Default()
	router.GET("/", _string)
	router.GET("/json", _json)
	router.Run(":80")
}
响应xml和yaml:
// 响应xml
func _xml(c *gin.Context) {
	c.XML(200, gin.H{"userName": "芒果", "age": "26", "status": http.StatusOK, "data": gin.H{"user": "菠萝"}})
}

// 响应yaml
func _yaml(c *gin.Context) {
	c.YAML(200, gin.H{"userName": "芒果", "age": "26", "status": http.StatusOK, "data": gin.H{"user": "菠萝"}})
}

func main() {
	router := gin.Default()
	router.GET("/xml", _xml)
	router.GET("/yaml", _yaml)
	router.Run(":80")
}
响应html:
// 响应html
func _html(c *gin.Context) {
	c.HTML(200, "index.html", gin.H{"username": "铃兰"})
}
func main() {
	router := gin.Default()
	router.LoadHTMLGlob("templates/*") // 要把html文件加载进来
	router.GET("/html", _html)
	router.Run(":80")
}
文件响应:
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func _string(c *gin.Context) {
	c.String(200, "你好呀")
}

func main() {
	router := gin.Default()
	// 加载模板目录下的所有模板文件
	router.LoadHTMLGlob("templates/*") // 要把html文件加载进来
	// 在go中,没有相对文件的路径,只有相对项目的路径
	// 这样可以做到只拿到static/static下面的文件,static下面的文件不指定无法获取到
	// 网页请求静态目录的前缀,后面是http.Dir方法,是一个目录,注意前缀不要重复
	router.StaticFS("/static", http.Dir("static/static"))
	// 配置单个文件,网页请求的路由,文件的路径
	router.StaticFile("/cat.png", "static/cat.png")
	router.Run(":80")
}

重定向:
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// 重定向
func _redirect(c *gin.Context) {
	// 第一个是重定向的状态码
	//c.Redirect(301, "https://www.baidu.com")
	//c.Redirect(302, "https://www.cnblogs.com/oaoa/")
	//也可以直接写路径
	c.Redirect(302, "/html")
}

func main() {
	router := gin.Default()
	// 加载模板目录下的所有模板文件
	router.LoadHTMLGlob("templates/*") // 要把html文件加载进来
	router.GET("/", _string)
	router.GET("/baidu", _redirect)

	router.Run(":80")
}

三、请求

查询参数Query:
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

func _query(c *gin.Context) {
	fmt.Println(c.Query("user"))
	fmt.Println(c.GetQuery("user"))
	fmt.Println(c.QueryArray("user")) // 如果传了多个user,拿到相同的查询参数
    fmt.Println(c.DefaultQuery("addr", "北京")) // 用户没传就使用默认值
}

func main() {
	router := gin.Default()
	router.GET("/query", _query)
	router.Run(":80")
}
动态参数Param:
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

func _query(c *gin.Context) {
	fmt.Println(c.Query("user"))
	fmt.Println(c.GetQuery("user"))
	fmt.Println(c.QueryArray("user")) // 如果传了多个user,拿到相同的查询参数
}

func _param(c *gin.Context) {
	fmt.Println(c.Param("user_id")) // 除了路径之外的信息
	fmt.Println(c.Param("book_id"))
	//http://127.0.0.1:80/param/xxxhhh/bookid13
}

func main() {
	router := gin.Default()
	router.GET("/query", _query)
	router.GET("/param/:user_id", _param)
	router.GET("/param/:user_id/:book_id", _param)
	router.Run(":80")
}
表单参数PostForm:

可以接收mutipart/form-dataapplication/x-www-form-urlencoded

func _form(c *gin.Context) {
	fmt.Println(c.PostForm("name"))
	fmt.Println(c.PostFormArray("name"))
	fmt.Println(c.DefaultPostForm("addr", "北京")) // 如果用户没有传就使用默认值
	forms, err := c.MultipartForm()              // 接收所有的form参数,包括文件
	fmt.Println(forms, err)
}
func main() {
	router := gin.Default()
	router.POST("/form", _form)
	router.Run(":80")
}

补充:

form-data

就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来表名文件类型;content-disposition,用来说明字段的一些信息;

由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件。

x-www-form-urlencoded

就是application/x-www-from-urlencoded,会将表单内的数据转换为键值对,比如,name=java&age = 23

区别

multipart/form-data:既可以上传文件等二进制数据,也可以上传表单键值对,只是最后会转化为一条信息; x-www-form-urlencoded:只能上传键值对,并且键值对都是间隔分开的。

原始参数GetRawData:
func _bindJson(c *gin.Context, obj any) (err error) {
	body, _ := c.GetRawData()
	contentType := c.GetHeader("Content-Type")
	fmt.Println(contentType)
	switch contentType {
	case "application/json": // 解析json数据
		err := json.Unmarshal(body, &obj)
		if err != nil {
			fmt.Println(err.Error())
			return err
		}
	}
	return nil
}

func _raw(c *gin.Context) {
	type User struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}
	var user User
	err := _bindJson(c, &user)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(user)
}
func main() {
	router := gin.Default()
	router.POST("/raw", _raw)
	router.Run(":80")
}
四大请求方式:

Restful风格指的是网络应用中资源定位和资源操作的风格,不是标准也不是协议

GET:从服务器取出资源(一项或多项)

POST:在服务器新建一个资源

PUT:在服务器更新资源(客户端提供完整资源数据)

PATCH:在服务器更新资源(客户端提供需要修改的资源数据)

DELETE:从服务器删除资源

/* 
以文字资源为例
GET    /articles       文章列表
GET    /articles/:id   文章详情
POST   /articles       添加文章
PUT    /articles/:id   修改某一篇文章
DELETE /articles/:id   删除某一篇文章
*/
package main

import (
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
)

type ArticleModel struct {
	Title   string `json:"title"`
	Content string `json:"content"`
}

func _bindJson(c *gin.Context, obj any) (err error) {
	body, _ := c.GetRawData()
	contentType := c.GetHeader("Content-Type")
	//fmt.Println(contentType)
	switch contentType {
	case "application/json": // 解析json数据
		err = json.Unmarshal(body, &obj)
		if err != nil {
			fmt.Println(err.Error())
			return err
		}
	}
	return nil
}

// 文章列表页面
func _getList(c *gin.Context) {
	// 搜索和分页
	articleList := []ArticleModel{
		{"Go语言入门", "这篇文章是Go语言入门"},
		{"红宝书", "这篇文章是JS红宝书"},
	}
	c.JSON(200, articleList)
}

func _getDetail(c *gin.Context) {
	// 获取param中的id
	fmt.Println(c.Param("id"))
	article := ArticleModel{"红宝书", "这篇文章是JS红宝书"}
	c.JSON(200, article)
}

// 创建文章
func _create(c *gin.Context) {
	// 接收前端传来的JSON数据
	var article ArticleModel
	err := _bindJson(c, &article)
	if err != nil {
		fmt.Println(err)
		return
	}
	c.JSON(200, article)
}

func _update(c *gin.Context) {
	// 接收前端传来的JSON数据
	fmt.Println(c.Param("id"))
	var article ArticleModel
	err := _bindJson(c, &article)
	if err != nil {
		fmt.Println(err)
		return
	}
	c.JSON(200, article)
}

func _delete(c *gin.Context) {
	fmt.Println(c.Param("id"))
	c.JSON(200, gin.H{})
}

func main() {
	router := gin.Default()
	//gin.SetMode(gin.DebugMode)
	//router.GET("/articles", _getList)
	//router.GET("/articles", _getDetail)
	//router.POST("/articles", _create)
	//router.POST("/articles/:id", _update)
	router.POST("/articles/:id", _delete)
	router.Run(":80")
}
请求头相关:

请求头参数获取

GetHeader可以大小写部分,且返回切片中的第一个数据

func main() {

	router := gin.Default()
	// 请求头的各种获取方式
	router.GET("/", func(c *gin.Context) {
		//首字母大小写不区分,单词与单词之间用-连接
		// 用于获取一个请求头
		fmt.Println(c.GetHeader("User-Agent"))
		fmt.Println(c.GetHeader("User-agent"))
		fmt.Println(c.GetHeader("user-Agent"))

		//Header 是一个普通的map[string] []string
		fmt.Println(c.Request.Header)
		// 如果是使用Get方法或者 .GetHeader, 哪么可以不用区分大小写,并且返回第一个value
		fmt.Println(c.Request.Header.Get("User-Agent"))
		fmt.Println(c.Request.Header["User-Agent"])
		// 如果是map的取值方式,请注意大小写问题
		fmt.Println(c.Request.Header["user-agent"])

		// 自定义的请求头,用Get方法也是免大小写
		fmt.Println(c.Request.Header.Get("Token"))
		fmt.Println(c.Request.Header.Get("token"))
		c.JSON(200, gin.H{"msg": "成功"})
	})

	// 爬虫和用户区别对待
	router.GET("/index", func(c *gin.Context) {
		userAgent := c.GetHeader("User-Agent")
		// 用正则去匹配
		// 字符串的包含匹配
		if strings.Contains(userAgent, "python") {
			// 爬虫来了
			c.JSON(0, gin.H{"data": "这是响应给爬虫的数据"})
			return
		}
	})

	router.Run(":80")
}
响应头相关:
// 设置响应头
router.GET("/res", func(c *gin.Context) {
    c.Header("Token", "123asdasda45646asd")
    c.Header("Content-Type", "application/text; charset=utf-8") // 浏览器会当成文本直接下载
    c.JSON(0, gin.H{"data": "看看响应头"})
})
参数绑定:

gin中的bind可以很方便的进行参数绑定及参数校验

使用这个功能的时候,需要给结构体加上Tag:json 、form、url、xml、yaml

  • Must Bind

    不用,校验失败会改状态码

  • Should Bind

    可以绑定json、query、param、yaml、xml

    如果校验不通过会返回错误

    package main
    
    import "github.com/gin-gonic/gin"
    
    type UserInfo struct {
    	Name string `json:"name"`
    	Age  int    `json:"age"`
    	Sex  string `json:"sex"`
    }
    
    func main() {
    	router := gin.Default()
    	router.POST("/", func(c *gin.Context) {
    
    		var userInfo UserInfo
    		err := c.ShouldBind(&userInfo)
    		if err != nil {
    			c.JSON(200, gin.H{"msg": "你错了"})
    			return
    		}
    		c.JSON(200, userInfo)
    	})
    	router.Run(":80")
    }
    
  • ShouldBindQuery

    绑定查询参数:

    tag对应为form

    type UserInfo struct {
    	Name string `json:"name" form:"name"`
    	Age  int    `json:"age" form:"age"`
    	Sex  string `json:"sex" form:"sex"`
    }
    
    func main() {
    	router := gin.Default()
    	router.POST("/query", func(c *gin.Context) {
    		var userInfo UserInfo
    		err := c.ShouldBindQuery(&userInfo)
    		if err != nil {
    			fmt.Println(err)
    			c.JSON(200, gin.H{"msg": "你错了"})
    			return
    		}
    		c.JSON(200, userInfo)
    	})
    
    	router.Run(":80")
    }
    
  • ShouldBindUri

    tag对应uri

    type UserInfo struct {
    	Name string `json:"name" form:"name" uri:"name"`
    	Age  int    `json:"age" form:"age" uri:"age"`
    	Sex  string `json:"sex" form:"sex" uri:"sex"`
    }
    
    func main() {
    	router := gin.Default()
    	router.POST("/uri/:name/:age/:sex", func(c *gin.Context) {
    		var userInfo UserInfo
    		err := c.ShouldBindUri(&userInfo)
    		if err != nil {
    			fmt.Println(err)
    			c.JSON(200, gin.H{"msg": "你错了"})
    			return
    		}
    		c.JSON(200, userInfo)
    	})
    
    	router.Run(":80")
    }
    
  • 绑定formData

    会根据请求头中的content-type去自动绑定

    form-data的参数也用这个,tag用form

    默认的tag就是form

    绑定form-data、x-www-form-urlencode

    type UserInfo struct {
    	Name string `form:"name"`
    	Age  int    `form:"age"`
    	Sex  string `form:"sex"`
    }
    
    func main() {
    	router := gin.Default()
    	router.POST("/form", func(c *gin.Context) {
    		var userInfo UserInfo
    		err := c.ShouldBind(&userInfo)
    		if err != nil {
    			fmt.Println(err)
    			c.JSON(200, gin.H{"msg": "你错了"})
    			return
    		}
    		c.JSON(200, userInfo)
    	})
    
    	router.Run(":80")
    }
    

四、验证器

需要使用参数验证功能,需要加binding tag

常用验证器:
配置 作用 示例
required 必填字段 binding:"min=5"
min 最小长度 binding:"max=10"
max 最大长度 binding:"eq=3"
len 长度 binding:"len=6"
eq 等于 binding:"eq=3"
ne 不等于 binding:"ne=12"
gt 大于 binding:"gt=10"
gte 大于等于 binding:"gte=10"
lt 小于 binding:"lt=10"
lte 小于等于 binding:"lte=10"
eqfield 等于其他字段的值 PassWord string binding:"eqfield=ConfirmPassword"
nefield 不等于其他字段的值
- 忽略字段 binding:"-"
package main

import "github.com/gin-gonic/gin"

type SignUserInfo struct {
	//Name       string `json:"name" binding:"required"` // 用户名
	Name     string `json:"name" binding:"min=4,max=6"` // 用户名
	Age      int    `json:"age"`                        // 年龄
	Password string `json:"password"`                   // 密码
	//RePassword string `json:"re_password"`                // 确认密码
	RePassword string `json:"re_password" binding:"eqfield=Password"` // 确认密码
}

func main() {
	router := gin.Default()
	router.POST("/", func(c *gin.Context) {
		var user SignUserInfo
		err := c.ShouldBindJSON(&user)
		if err != nil {
			c.JSON(200, gin.H{"msg": err.Error()})
		}
		c.JSON(200, gin.H{"msg": user})
	})
	router.Run(":80")
}
gin内置验证器:

url是uri的子集

/*
枚举  只能是red或green
oneof=red green

字符串
contains=hello   包含hello的字符串
excludes  不包含
startswith 字符串前缀
endswith  字符串后缀

数组
dive  dive后面的验证就是针对数组中的每一个元素

网络验证
ip
ipv4
ipv6
uri     在于I(Identifier)是统一资源标示符,可以唯一标识一个资源
url     在于Locater,是统一资源定位符,提供找到该资源的确切路径

日期验证
datetime=2006-01-02 15:04:05

*/
package main

import "github.com/gin-gonic/gin"

type SignUserInfo struct {
	//Name       string `json:"name" binding:"required"` // 用户名
	//Name     string `json:"name" binding:"min=4,max=6"` // 用户名
	//Name string `json:"name" binding:"excludes=f"` // 用户名   不包含f
	//Name string `json:"name" binding:"startswith=f"` // 用户名   前缀为f
	Name     string   `json:"name" binding:"endswith=f"` // 用户名   后缀为f
	LikeList []string `json:"like_list" binding:"required,dive,startswith=like"`
	IP       string   `json:"ip" binding:"ip"`                             // 需要满足ip要求
	Url      string   `json:"url" binding:"url"`                           // 满足url要求
	Uri      string   `json:"uri" binding:"uri"`                           // 满足url要求
	Age      int      `json:"age"`                                         // 年龄
	Password string   `json:"password"`                                    // 密码
	Date     string   `json:"date" binding:"datetime=2006-01-02 15:04:05"` // 1月2日下午3点4分5秒在2006年
	//RePassword string `json:"re_password"`                // 确认密码
	RePassword string `json:"re_password" binding:"eqfield=Password"` // 确认密码
	Sex        string `json:"sex" binding:"oneof=man woman"`          //枚举
}

func main() {
	router := gin.Default()
	router.POST("/", func(c *gin.Context) {
		var user SignUserInfo
		err := c.ShouldBindJSON(&user)
		if err != nil {
			c.JSON(200, gin.H{"msg": err.Error()})
		}
		c.JSON(200, gin.H{"msg": user})
	})
	router.Run(":80")
}
自定义验证的错误信息:
package main

import (
	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"reflect"
)

// 获取结构体中的msg参数
func GetValidMsg(err error, obj any) string {
	getObj := reflect.TypeOf(obj) // 拿到值
	// 将err接口断言为具体类型
	if errs, ok := err.(validator.ValidationErrors); ok {
		// 断言成功
		for _, e := range errs {
			// 循环每一个错误信息
			// 根据报错字段获取结构体具体字段
			if f, exits := getObj.Elem().FieldByName(e.Field()); exits {
				msg := f.Tag.Get("msg")
				return msg
			}
		}
	}
	return ""
}

func main() {
	router := gin.Default()
	router.POST("/", func(c *gin.Context) {
		type User struct {
			Name string `json:"name" binding:"required" msg:"用户名校验失败"`
			Age  int    `json:"age" binding:"required" msg:"请输入年龄"`
		}
		var user User
		err := c.ShouldBindJSON(&user)
		if err != nil {
			c.JSON(200, gin.H{"msg": GetValidMsg(err, &user)})
			return
		}
		c.JSON(200, gin.H{"data": user})
		return
	})
	router.Run(":80")
}
自定义绑定器:
package main

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
	"reflect"
)

// 获取结构体中的msg参数
func _GetValidMsg(err error, obj any) string {
	getObj := reflect.TypeOf(obj) // 拿到值
	// 将err接口断言为具体类型
	if errs, ok := err.(validator.ValidationErrors); ok {
		// 断言成功
		for _, e := range errs {
			// 循环每一个错误信息
			// 根据报错字段获取结构体具体字段
			if f, exits := getObj.Elem().FieldByName(e.Field()); exits {
				msg := f.Tag.Get("msg")
				return msg
			}
		}
	}
	return err.Error()
}

type User struct {
	Name string `json:"name" binding:"required,sign" msg:"用户名校验失败"`
	Age  int    `json:"age" binding:"required" msg:"请输入年龄"`
}

func signValid(fl validator.FieldLevel) bool {
	var nameList []string = []string{"辉哥", "龙哥", "灿哥"}
	for _, nameStr := range nameList {
		name := fl.Field().Interface().(string)
		if name == nameStr {
			return false
		}
	}
	return true
}

func main() {
	router := gin.Default()
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		v.RegisterValidation("sign", signValid)
	}

	router.POST("/", func(c *gin.Context) {

		var user User
		err := c.ShouldBindJSON(&user)
		if err != nil {
			c.JSON(200, gin.H{"msg": _GetValidMsg(err, &user)})
			return
		}
		c.JSON(200, gin.H{"data": user})
		return
	})
	router.Run(":80")
}

五、文件

文件上传:
单文件:

SaveUploadFile

c.SaveUploadFile(file,dst)   // 文件对象 文件路径,注意要从新项目根路径开始写
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"os"
)

func main() {
	router := gin.Default()
	router.POST("/upload", func(c *gin.Context) {
		file, _ := c.FormFile("file")
		readerFile, _ := file.Open()
		// 也可以使用os.Create
		writeFile, _ := os.Create("./upload/13.png")
		defer writeFile.Close()
		n, _ := io.Copy(writeFile, readerFile) // 拷贝
		fmt.Println("n=", n)

		//fmt.Println(string(data))
		//fmt.Println(file.Filename)
		fmt.Println(file.Size / 1024) // 单位是字节
		c.SaveUploadedFile(file, "./upload/12.png")
		c.JSON(200, gin.H{"msg": "上传成功"})
	})
	router.Run(":80")
}
多个文件:
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"os"
)

func main() {
	router := gin.Default()
	router.POST("/upload", func(c *gin.Context) {
		file, _ := c.FormFile("file")
		readerFile, _ := file.Open()
		// 也可以使用os.Create
		writeFile, _ := os.Create("./upload/13.png")
		defer writeFile.Close()
		n, _ := io.Copy(writeFile, readerFile) // 拷贝
		fmt.Println("n=", n)

		//fmt.Println(string(data))
		//fmt.Println(file.Filename)
		fmt.Println(file.Size / 1024) // 单位是字节
		c.SaveUploadedFile(file, "./upload/12.png")
		c.JSON(200, gin.H{"msg": "上传成功"})
	})

	router.POST("/uploads", func(c *gin.Context) {
		form, _ := c.MultipartForm()
		files, _ := form.File["upload[]"]
		for _, file := range files {
			c.SaveUploadedFile(file, "./uploads/"+file.Filename)
		}
		c.JSON(200, gin.H{"msg": fmt.Sprintf("成功上传%d个文件", len(files))})
	})

	router.Run(":80")
}
文件下载:

直接响应一个路径下的文件

c.File("uploads/12.png")

有些响应,比如图片,浏览器就会显示这个图片,而不是下载,所以我们需要使浏览器唤起下载行为

c.Header("Content-Type","application/octet-stream")  // 表示是文件流,唤起浏览器下载,一般设置了这个就需要设置文件名
c.Header("Content-Dispositon","attachment;filename="+"test.png")  // 用来指定下载下来的文件名
c.Header("Content-Transfer-Encoding","binary")  // 表示传输过程中的编码形式,乱码问题可能就是因为它
c.File("uploads/test.png")

注意:文件下载浏览器可能会有缓存,这个需要注意一下,解决办法就是加查询参数

前端在写的时候要

this.$http({
    method:"post",
    utl:"file/upload",
    data:postData,
    responseType:"blob"   // 前端要加这个
}).then(res=>{})
package main

import "github.com/gin-gonic/gin"

func main() {
	router := gin.Default()
	router.GET("/download", func(c *gin.Context) {
		c.Header("Content-Type", "application/octet-stream")            // 表示是文件流,唤起浏览器下载,一般设置了这个就需要设置文件名
		c.Header("Content-Dispositon", "attachment;filename="+"12.png") // 用来指定下载下来的文件名
		c.File("upload/12.png")                                         // 这样会直接在网页预览
	})
	router.Run(":8080")
}

六、gin中间件和路由

Gin框架允许开发者在处理请求中,加入用户自己的钩子(Hook)函数,这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限教研、数据分页、记录日志、耗时统计等。比如,如果访问一个网页的话,不管访问什么路径都需要进行登录,此时就需要为所有路径处理函数进行统一一个中间件

Gin的中间件必须是一个gin.HandlerFunc类型

单独注册中间件:

c.Next()之前的就是请求中间件,之后的就是响应中间件

如果其中一个中间件响应了c.Abort(),后续中间件将不再执行,直接按照顺序走完所有的响应中间件

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

func m1(c *gin.Context) {
	fmt.Println("m1 in ...")
	//c.Abort() // 使用这个方法之后,就不会响应后面的了
	c.Next()
	fmt.Println("m1 out ...")
	//c.JSON(200, gin.H{"msg": "index"})

}

func index(c *gin.Context) {
	fmt.Println("index ... in")
	c.JSON(200, gin.H{"msg": "index"})
	c.Next()
	fmt.Println("index ...out")
}

func m2(c *gin.Context) {
	fmt.Println("m2 in ...")
	c.Next()
	//c.JSON(200, gin.H{"msg": "index"})
	fmt.Println("m2 out ...")
}

func main() {
	router := gin.Default()
	router.GET("/", m1, index, m2)
	router.Run(":8080")
}
全局中间件和中间件传参:
全局注册中间件:
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

func m10(c *gin.Context) { // 全局中间件
	fmt.Println("m10...in")
	c.JSON(200, gin.H{"msg": "响应被我吃掉了"})
	c.Abort()
	c.Next()
	fmt.Println("m10...out")
}
func m11(c *gin.Context) { // 全局中间件
	fmt.Println("m11...in")
	c.Next()
	fmt.Println("m11...out")
}

func main() {
	router := gin.Default()
	router.Use(m10, m11) // 注册中间件
	router.GET("/m10", func(c *gin.Context) {
		fmt.Println("index...in")
		c.JSON(200, gin.H{"msg": "m10"})
		c.Next()
		fmt.Println("index...out")
	})
	router.GET("/m11", func(c *gin.Context) {
		c.JSON(200, gin.H{"msg": "m11"})
	})
	router.Run(":8080")
}
中间件传递数据:
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

type User struct {
	Name string
	Age  int
}

func m10(c *gin.Context) { // 全局中间件
	fmt.Println("m10...in")
	c.JSON(200, gin.H{"msg": "响应被我吃掉了"})
	c.Set("name", "zzz")
	c.Set("user", User{
		Name: "test",
		Age:  25,
	})
	//c.Abort()
	//c.Next()
	fmt.Println("m10...out")
}
func m11(c *gin.Context) { // 全局中间件
	fmt.Println("m11...in")
	c.Next()
	fmt.Println("m11...out")
}

func main() {
	router := gin.Default()
	router.Use(m10, m11) // 注册中间件
	router.GET("/m10", func(c *gin.Context) {
		fmt.Println("index...in")
		c.JSON(200, gin.H{"msg": "m10"})
		name, _ := c.Get("name")
		fmt.Println(name)
		_user, _ := c.Get("user")
		user := _user.(User) // 如果需要用到user中的某一个字段,需要断言
		fmt.Println(user.Name)
		c.Next()
		fmt.Println("index...out")
	})
	router.GET("/m11", func(c *gin.Context) {
		c.JSON(200, gin.H{"msg": "m11"})
	})
	router.Run(":8080")
}
路由分组:

将一系列路由放到一个组下,统一管理

package main

import "github.com/gin-gonic/gin"

type UserInfo struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

type ArticleInfo struct {
	Title   string `json:"title"`
	Content string `json:"content"`
}

type Response struct {
	Code int    `json:"code"`
	Data any    `json:"data"`
	Msg  string `json:"msg"`
}

func UserList(c *gin.Context) {
	var userList []UserInfo = []UserInfo{
		{"book", 21},
		{"li", 22},
		{"wang", 23},
	}
	c.JSON(200, Response{
		Code: 0,
		Data: userList,
		Msg:  "请求成功",
	})
}
func articleList(c *gin.Context) {
	var userList = []ArticleInfo{
		{"go", "从0到1"},
		{"python", "从0到1"},
	}
	c.JSON(200, Response{
		Code: 0,
		Data: userList,
		Msg:  "请求成功",
	})
}

func main() {
	router := gin.Default()

	api := router.Group("api")
	userManger := api.Group("user_manager")
	{
		userManger.GET("/users", UserList) // 访问需要/api/user_manager/users
	}
	articleManager := api.Group("article_manager")
	{
		articleManager.GET("/articles", articleList)
	}
	//api.GET("/users", UserList)        // 访问需要/api/users
	//router.GET("/users", UserList)
	router.Run(":8080")
}
路由分组中间件:
package main

import "github.com/gin-gonic/gin"

type UserInfo struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

type ArticleInfo struct {
	Title   string `json:"title"`
	Content string `json:"content"`
}

type Response struct {
	Code int    `json:"code"`
	Data any    `json:"data"`
	Msg  string `json:"msg"`
}

func UserList(c *gin.Context) {
	var userList []UserInfo = []UserInfo{
		{"book", 21},
		{"li", 22},
		{"wang", 23},
	}
	c.JSON(200, Response{
		Code: 0,
		Data: userList,
		Msg:  "请求成功",
	})
}
func articleList(c *gin.Context) {
	var userList = []ArticleInfo{
		{"go", "从0到1"},
		{"python", "从0到1"},
	}
	c.JSON(200, Response{
		Code: 0,
		Data: userList,
		Msg:  "请求成功",
	})
}

func MiddleWare(c *gin.Context) {
	token := c.GetHeader("token")
	if token == "1234" {
		c.Next()
		return
	}
	c.JSON(200, Response{
		Code: 200,
		Data: nil,
		Msg:  "权限验证失败",
	})
}

func main() {
	router := gin.Default()
	//router := gin.New()  // 不含任何中间件

	api := router.Group("api")
	userManger := api.Group("user_manager").Use(MiddleWare)
	{
		userManger.GET("/users", UserList) // 访问需要/api/user_manager/users
	}
	articleManager := api.Group("article_manager")
	{
		articleManager.GET("/articles", articleList)
	}
	//api.GET("/users", UserList)        // 访问需要/api/users
	//router.GET("/users", UserList)
	router.Run(":8080")
}

七、日志

gin内置日志组件:

为什么要使用日志

  1. 记录用户操作,猜测用户行为
  2. 记录bug

环境切换不想看到debug日志可以改为release模式

gin.SetMode(gin.ReleaseMode)
router := gin.Default()

自定义输出格式

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
)

func main() {
	gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
		log.Printf(
			"%s %s %s %d \n",
			httpMethod,
			absolutePath,
			handlerName,
			nuHandlers,
		)
	}
	//router := gin.Default()
	router := gin.New()
	router.Use(gin.LoggerWithFormatter(func(params gin.LogFormatterParams) string {
		return fmt.Sprintf(
			"%s |%d| %s %s\n",
			params.TimeStamp.Format("2006-01-02 15:04:05"),
			params.StatusCode,
			params.Method,
			params.MethodColor(), // 可以修改颜色
			params.Path,
		)
	}))
	router.GET("/index", func(c *gin.Context) {})
	router.POST("/users", func(c *gin.Context) {})
	router.POST("/articles", func(c *gin.Context) {})
	router.DELETE("/articles/:id", func(c *gin.Context) {})
	fmt.Println(router.Routes()) // 会把所有路由打印下来
	router.Run(":8080")
}

输出带颜色

fmt.Printf("\033[97;41m红底白字\033[0m 正常颜色")
logrus:
下载:
go get github.com/sirupsen/logrus
日志等级:
PanicLevel  // 会抛一个异常
FatalLevel  // 打印日志之后就会退出
ErrorLevel
WarnLevel
InfoLevel
DebugLevel
TraceLevel  // 低级别
更改日志级别:
logrus.SetLevel(logrus.WarnLevel) // 设置日志等级
package main

import (
	"fmt"
	"github.com/sirupsen/logrus"
)

const (
	cBlack = 0
	cRed   = 1
	cGreen = 2
)

func PrintColor(colorCode int, text string) {
	fmt.Printf("\033[3%dm%s\033[0m", colorCode, text)
}

func main() {

	PrintColor(cRed, "红色")
	logrus.SetFormatter(&logrus.TextFormatter{
		ForceColors:     true,
		FullTimestamp:   true,
		TimestampFormat: "2006-01-02 15:04:05",
	})
	logrus.SetLevel(logrus.DebugLevel)
	logrus.SetLevel(logrus.WarnLevel) // 设置日志等级
	logrus.SetLevel(logrus.InfoLevel) // 设置日志等级
	logrus.Errorf("出错了")
	logrus.Warnln("警告")
	logrus.Infof("信息")
	logrus.Debugf("debug")
	logrus.Println("打印")

	fmt.Println(logrus.GetLevel())               // 显示等级
	logrus.SetFormatter(&logrus.JSONFormatter{}) // 用JSON显示
	logrus.SetFormatter(&logrus.TextFormatter{   // 设置颜色
		ForceColors: true,
	})

	log := logrus.WithField("app", "study").WithField("service", "logrus") // 可以链式调用
	log = logrus.WithFields(logrus.Fields{
		"user": "ww",
		"ip":   "192.168.200.254",
	})
	log.Errorf("你好") // 会统一加上上面的字段

	fmt.Println("\033[31m 红色 \033[0m") // 设置字体颜色
	fmt.Println("\033[41m 红色 \033[0m") // 设置背景颜色
}
自定义格式:

输出到日志文件

package main

import (
	"github.com/sirupsen/logrus"
	"io"
	"os"
)

func main() {
	file, _ := os.OpenFile("logrus_study/info.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)

	logrus.SetOutput(io.MultiWriter(file, os.Stdout)) // 同时输出屏幕和文件
	logrus.Infof("你好")
	logrus.Error("出错了")
	logrus.Errorf("出错了")
	logrus.Errorln("出错了")
}

自定义输出格式:

func main() {
    router := gin.New()
    // LoggerWithFormatter middleware will write the logs to gin.DefaultWrite
    // By default gin.DefaultWriter = os.Stdout
    router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
        // your custom format
        return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
            param.ClientIP,
            param.TimeStamp.Format(time.RFC1123),
            param.Method,
            param.Path,
            param.Request.Proto,
            param.StatusCode,
            param.Latency,
            param.Request.UserAgent(),
            param.ErrorMessage,
        )
    }))
    router.Use(gin.Recovery())
    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })
    router.Run(":8080")
}
显示行号:
logrus.SetReportCaller(true)
hook:

初始化时为logrus添加hook,logrus可以实现各种扩展功能

// logrus在记录Levels()返回的日志级别的消息时会触发HOOK
// 按照Fire方法定义的内容修改logrus.Entry
type Hook interface {
    Levels() []Level
    Fire(*Entry) error
}
gin集成logrus:

use使用中间件,把logrus单独放一个文件中,然后引入进来

  1. 初始化日志器和日志实例

    package util
    
    import (
        "fmt"
        rotatelogs "github.com/lestrrat-go/file-rotatelogs"
        "github.com/rifflock/lfshook"
        "github.com/sirupsen/logrus"
        "os"
        "time"
    )
    
    var (
    	Logger = logrus.New()  // 初始化日志对象
    	LogEntry *logrus.Entry
    )
    
    
    func init() {
        // 写入日志文件
        logPath := "logs/log"  // 日志存放路径
        linkName := "logs/latest.log"  // 最新日志的软连接路径
        src, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE, 0755)  // 初始化日志文件对象
        if err != nil {
            fmt.Println("err: ", err)
        }
        //log := logrus.New()  // 初始化日志对象
        Logger.Out = src  // 把产生的日志内容写进日志文件中
    
        // 日志分隔:1. 每天产生的日志写在不同的文件;2. 只保留一定时间的日志(例如:一星期)
        Logger.SetLevel(logrus.DebugLevel)  // 设置日志级别
        logWriter, _ := rotatelogs.New(
            logPath + "%Y%m%d.log",  // 日志文件名格式
            rotatelogs.WithMaxAge(7 * 24 * time.Hour),  // 最多保留7天之内的日志
            rotatelogs.WithRotationTime(24*time.Hour),  // 一天保存一个日志文件
            rotatelogs.WithLinkName(linkName),  // 为最新日志建立软连接
        )
        writeMap := lfshook.WriterMap{
            logrus.InfoLevel: logWriter,  // info级别使用logWriter写日志
            logrus.FatalLevel: logWriter,
            logrus.DebugLevel: logWriter,
            logrus.ErrorLevel: logWriter,
            logrus.PanicLevel: logWriter,
        }
        Hook := lfshook.NewHook(writeMap, &logrus.TextFormatter{
            TimestampFormat: "2006-01-02 15:04:05",  // 格式日志时间
        })
        Logger.AddHook(Hook)
        LogEntry = logrus.NewEntry(Logger).WithField("service", "yi-shou-backstage")
    }
    
  2. 在项目中使用定义的日志实例记录日志

    // user列表
    func ListUsers(ctx *gin.Context){
        util.LogEntry.Error("用户列表")
        ctx.JSON(200, gin.H{
            "message": "hello go",
        })
    }
    
  3. 定义日志中间件,记录常见异常的日志信息

    package middleware
    
    import (
        "fmt"
        "github.com/gin-gonic/gin"
        "github.com/sirupsen/logrus"
        "math"
        "time"
        "yi-shou-backstage/util"
    )
    
    func LoggerMiddleware() gin.HandlerFunc{
        return func(c *gin.Context) {
            startTime := time.Now()
            c.Next()  // 调用该请求的剩余处理程序
            stopTime := time.Since(startTime)
            spendTime := fmt.Sprintf("%d ms", int(math.Ceil(float64(stopTime.Nanoseconds() / 1000000))))
            //hostName, err := os.Hostname()
            //if err != nil {
            //    hostName = "Unknown"
            //}
            statusCode := c.Writer.Status()
            //clientIP := c.ClientIP()
            //userAgent := c.Request.UserAgent()
            dataSize := c.Writer.Size()
            if dataSize < 0 {
                dataSize = 0
            }
            method := c.Request.Method
            url := c.Request.RequestURI
            Log := util.Logger.WithFields(logrus.Fields{
                //"HostName": hostName,
                "SpendTime": spendTime,
                "path": url,
                "Method": method,
                "status": statusCode,
                //"Ip": clientIP,
                //"DataSize": dataSize,
                //"UserAgent": userAgent,
            })
            if len(c.Errors) > 0 {  // 矿建内部错误
                Log.Error(c.Errors.ByType(gin.ErrorTypePrivate))
            }
            if statusCode >= 500 {
                Log.Error()
            } else if statusCode >= 400 {
                Log.Warn()
            }else {
                Log.Info()
            }
        }
    }
    
  4. gin集成自定义的中间件

    router := gin.New()
    // 注册自定义的日志器
    router.Use(middleware.LoggerMiddleware())
    
posted @ 2023-02-13 09:19  喵喵队立大功  阅读(171)  评论(0编辑  收藏  举报