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())

本文作者:喵喵队立大功

本文链接:https://www.cnblogs.com/oaoa/p/17115273.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   喵喵队立大功  阅读(235)  评论(0编辑  收藏  举报
评论
收藏
关注
推荐
深色
回顶
收起
点击右上角即可分享
微信分享提示