仓库源文站点原文


title: ginWeb框架学习 description: ginWeb框架基础学习(VScode+golang+ubuntu22.04.3LTS) slug: ginWebStudy date: 2023-09-11 13:40:00+0800 image: gin.png categories:

- techStudy

tags:

- gin
- go    

weight: 1 # You can add weight to some posts to override the default sorting (date descending)

comments: true

license: flase

math: true

toc: true

style:

keywords:

readingTime:


ginWeb框架学习(ubuntu22.04.3)

gin

gin安装与环境配置

  1. 安装gin
# 切换国内代理
GOPROXY=https://goproxy.cn,direct
# 安装gin
go get -u github.com/gin-gonic/gin
  1. 安装postman

postman官网 用于调试post请求接口用

  1. 测试gin是否安装成功
package main

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

func main() {
    router := gin.Default()
    router.GET("/index", func(context *gin.Context) {
        context.String(200, "Hello lihan!")
    })
    router.Run(":8080")
}
# 运行
go run main.go
# 访问[主机地址]:8080/index
# 例如 127.0.0.1:8080/index
# 显示 Hello World! 则安装成功

gin教程

gin启动方式

package main

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

func Index2(context *gin.Context) {
    context.String(200, "Hello lihan2!")
}

func main() {

    //创建一个默认路由引擎
    router := gin.Default()

    //注册一个路由和处理函数,访问/index的路由时,会执行后面的匿名函数
    router.GET("/index", func(context *gin.Context) {
        context.String(200, "Hello lihan!")
    })

    //另一种方法,可以直接使用已经声明的函数
    router.GET("/index2", Index2)

    //启动HTTP服务,gin会默认把web服务器运行在本机的0.0.0.0:8080端口上(即所有网卡IP的8080端口)
    router.Run(":8080")
    //第二种启动方式,用原生http服务的方式启动,这种方式可以实现更多的自定义配置
    //http.ListenAndServe(":8080", router)
}

gin视图 view

响应 response
  1. 状态码
  1. 返回字符串
router.GET("/", func(c *gin.Context) {
        c.String(200, "你好啊!")
    })
  1. 返回json 重点
//main.go
router.GET("/json", ginJson)
//ginJson函数
func ginJson(c *gin.Context) {
    //json响应结构体
    type UserInfo struct {
        UserName string `json:"username-json"` //返回给前端的字段名
        Age      int    `json:"age_json"`
        PassWord string `json:"-"` //"-"不会返回给前端
    }
    //user := UserInfo{"lihan", 32, "123456"}
    //c.JSON(200, user)

    //json响应map
    //userMap := map[string]string{
    //  "user_name": "lihan",
    //  "age":       "32",
    //}
    //c.JSON(200, userMap)

    //直接响应json
    c.JSON(200, gin.H{"user_name": "lihan", "age": 32})
}
  1. 返回xml和yaml
//main.go
router.GET("/xml", ginXml)
router.GET("/yaml", ginYaml)

//ginXml、ginYaml函数
func ginXml(c *gin.Context) {
    c.XML(200, gin.H{"user_name": "lihan", "age": 32, "status": http.StatusOK, "data": gin.H{"id": 1, "name": "lihan"}})
}

func ginYaml(c *gin.Context) {
    c.YAML(200, gin.H{"user_name": "lihan", "age": 32, "status": http.StatusOK, "data": gin.H{"id": 1, "name": "lihan"}})
}
  1. 返回html
//main.go
router.LoadHTMLGlob("templates/*")
router.GET("/html", ginHtml)

//ginHtml函数
func ginHtml(c *gin.Context) {
    type UserInfo struct {
        UserName string
        Age      int
        PassWord string
    }
    user := UserInfo{"lihan", 32, "123456"}
    c.HTML(200, "index.html", user)

    //c.HTML(200, "index.html", gin.H{"user_name": "lihan", "age": 32, "status": http.StatusOK, "data": gin.H{"id": 1, "name": "lihan"}})
} //gin.H()可以向html传参
{{/*  templates/index.html  */}}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>

<body>
    <h1>响应html {{ .UserName}}</h1>
    {{/* <h1>响应html {{ .data.name}}</h1> */}}
</body>

</html>
  1. 响应文件
//main.go
    //golang中,没有相对文件的路径,只有相对项目的路径

    //配置单个文件,网页请求的路由,文件路径
    router.StaticFile("/downloads/lihan.png", "static/lihan.png")
    //配置文件夹,网页请求的路由,文件夹路径
    router.StaticFS("/downloads/files", http.Dir("static/texts"))
    //配置js、css、图片等资源
    router.Static("/static", "static")
    //html中引用资源
        <img src="/static/resource/baidu.png" alt="baidu">
        <link rel="stylesheet" href="/static/css/style.css">
<!-- 项目目录结构 --> - 项目文件夹[gowebstudy] - static - texts - test.txt - lihan.png - pwd.txt - 2.gin_view视图 - 2.1response响应.go 7. redirect 重定向 - HTTP 301 Moved Permanently (永久重定向): 当服务器返回HTTP状态码301时,它告诉客户端请求的资源已永久移动到一个新的URL。 浏览器(或其他HTTP客户端)会记住这个永久重定向,以后的请求都会直接转向新的URL,而不再请求原始URL,可缓存。 - HTTP 302 Found (临时重定向): 当服务器返回HTTP状态码302时,它告诉客户端请求的资源已临时移动到一个新的URL。 浏览器会将这个重定向视为临时性质,因此不会记住新的URL,每次都需要重新请求原始URL,不可缓存 ```go //main.go router.GET("/lihan", ginRedirect) //ginRedirect函数 func ginRedirect(c *gin.Context) { c.Redirect(301, "https://lihan3238.github.io/") } ``` ##### 请求 request 1. Query 查询参数 ```go //main.go func _query(c *gin.Context) { fmt.Println(c.Query("username")) //c.GetQuery仅判断是否存在,不判断是否为空 fmt.Println(c.GetQuery("username")) //c.QueryArray获取全部username的值,返回一个切片 fmt.Println(c.QueryArray("username")) //c.DefaultQuery获取username的值,如果为空则返回默认值 fmt.Println(c.DefaultQuery("id","default_id")) } //?id=2&username=lihan&username=op func main() { router := gin.Default() router.GET("/query", _query) router.Run(":8080") } //访问:http://192.168.56.105:8080/query?id=2&username=lihan&username=op //返回: lihan lihan true [lihan op] ``` 2. Param 动态参数 ```go //main.go router.GET("/param/:user_id", _param) router.GET("/param/:user_id/:book_id", _param) //_param函数 func _param(c *gin.Context) { fmt.Println(c.Param("user_id")) fmt.Println(c.Param("book_id")) } //http://192.168.56.105:8080/param/user1/book2 ``` 3. PostForm 表单参数 - 要使用Post而非Get请求 - 可以接收 multipart/form-data 和 application/x-www-form-urlencoded 类型的数据 ```go //main.go router.POST("/form", _form) //_form函数 func _form(c *gin.Context) { fmt.Println(c.PostForm("username")) fmt.Println(c.PostFormArray("id")) fmt.Println(c.DefaultPostForm("addr", "default_addr")) } ``` ![postman](postman1.png) ```shell # 返回 lihan [123456 88888] default_addr ``` 4. GetRawData 获取原始参数 ```go //main.go router.POST("/rawdata", _rawData) //_rawData函数 func _rawData(c *gin.Context) { //fmt.Println(c.GetRawData()) body, _ := c.GetRawData() fmt.Println(string(body)) } ``` <!-- x-www-form-urlencoded --> ![postman](postman2.png) ```shell # 返回 name=abcde ``` <!-- form-data --> ![postman](postman3.png) ```shell Content-Disposition: form-data; name="name" abcd ----------------------------609676969091043609505229-- ``` 5. GetRawData 序列化Json与类型绑定 ```go //main.go router.POST("/rawdata2", _rawData2) //_rawData2函数 func _rawData2(c *gin.Context) { body, _ := c.GetRawData() contentType := c.GetHeader("Content-Type") switch contentType { case "application/json": type User struct { Username string `json:"name"` Password int `json:"pwd"` } var user User err := json.Unmarshal(body, &user) if err != nil { fmt.Println(err.Error()) } fmt.Println(user) } } ``` ![postman](postman4.png) ```shell {lihan 123456} ``` <!-- 封装绑定json到任意类型(结构体为例) -->
func bandJson(c *gin.Context, obj any) (err error) {
    body, _ := c.GetRawData()
    contentType := c.GetHeader("Content-Type")
    switch contentType {
    case "application/json":
        err := json.Unmarshal(body, obj)
        if err != nil {
            fmt.Println(err.Error())
            return err
        }
    }
    return nil
}

func _rawData2(c *gin.Context) {
    type User struct {
        Username string `json:"name"`
        Password int    `json:"pwd"`
    }
    var user User
    err := bandJson(c, &user)
    if err != nil {
        fmt.Println(err.Error())
    }
    fmt.Println(user)
}
  1. 四大请求方式

Restful风格指的是网络应用中资源定位和资源操作的风格。 不是标准,只是一种风格。

//以文字资源为例

//GET       /articles       列出所有文章
//GET       /articles/:id   获取文章详情
//POST      /articles       新建一篇文章
//PUT       /articles/:id   更新某篇文章的信息
//DELETE    /articles/:id   删除某篇文章

//main.go
    router.GET("/articles", _getList)       // 文章列表
    router.GET("/articles/:id", _getDetail) // 文章详情
    router.POST("/articles", _create)       // 新建文章
    router.PUT("/articles/:id", _update)    // 修改文章
    router.DELETE("/articles/:id", _delete) // 删除文章

    //函数
    type ArticleModel 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 _bandJson(c *gin.Context, obj any) (err error) {
    body, _ := c.GetRawData()
    contentType := c.GetHeader("Content-Type")
    switch contentType {
    case "application/json":
        err := json.Unmarshal(body, obj)
        if err != nil {
            fmt.Println(err.Error())
            return err
        }
    }
    return nil
}

func _getList(c *gin.Context) {
    //包含搜索、分页等功能
    articleList := []ArticleModel{
        {Title: "Go语言入门", Content: "本文是Go语言入门指南"},
        {Title: "Stellarise群星攻略", Content: "本文是Stellarise群星攻略"},
        {Title: "马克思主义学习指南", Content: "本文是马克思注意学习指南"},
        {Title: "李寒个人介绍", Content: "本文是李寒个人介绍"},
    }

    //c.JSON(200, articleList)
    //接口封装
    c.JSON(200, Response{0, articleList, "success"})
}
func _getDetail(c *gin.Context) {
    //获取params中的id
    fmt.Println(c.Param("id"))
    //省略查询数据库的过程
    article := ArticleModel{Title: "李寒个人介绍", Content: "本文是李寒个人介绍"}
    c.JSON(200, Response{0, article, "success"})
}

func _create(c *gin.Context) {
//接受前端传来的JSON数据
    var article ArticleModel

    err := _bandJson(c, &article)
    if err != nil {
        c.JSON(200, Response{1, nil, "参数错误"})
        return
    }
//省略插入数据库的过程

    c.JSON(200, Response{0, article, "success"})
}
func _update(c *gin.Context) {
    //获取params中的id
    fmt.Println(c.Param("id"))
    //省略查询数据库的过程
    //接受前端传来的JSON数据
    var article ArticleModel

    err := _bandJson(c, &article)
    if err != nil {
        c.JSON(200, Response{1, nil, "参数错误"})
        return
    }
    //省略插入数据库的过程
    c.JSON(200, Response{0, article, "success"})
}
func _delete(c *gin.Context) {
    //省略查询数据库的过程
    //获取params中的id
    fmt.Println(c.Param("id"))
    //省略删除数据库的过程
    c.JSON(200, Response{0, map[string]string{}, "success"})
}

请求头与响应头

  1. Request Header 请求头

请求头参数获取

//获取请求头
    router.GET("/", func(c *gin.Context) {
        //单词首字母大小写不区分,单词之间用"-"分割
        //用于获取一个请求头
        fmt.Println(c.GetHeader("User-Agent"))
        fmt.Println(c.Request.Header.Get("User-Agent"))
        //fmt.Println(c.GetHeader("user-agent"))
        //fmt.Println(c.GetHeader("user-Agent"))

        //Header是一个map[string][]string类型
        fmt.Println(c.Request.Header)
        //获取所有请求头,区分大小写
        fmt.Println(c.Request.Header["User-Agent"])

        c.JSON(200, gin.H{"msg": "ok"})
    })

利用请求头,将爬虫和用户区别对待

    //利用请求头,将爬虫和用户区别对待
    //
    router.GET("/index", func(c *gin.Context) {
        userAgent := c.GetHeader("User-Agent")
        //方法一 正则去匹配
        //字符串的包含匹配
        if strings.Contains(userAgent, "python") {
            //爬虫来了
            c.JSON(200, gin.H{"data": "这是一个爬虫"})
            return

        }
        c.JSON(200, gin.H{"data": "这是一个用户"})
    })
  1. Response Header 响应头
    //设置响应头
    router.GET("/res", func(c *gin.Context) {
        c.Header("Content-Type", "application/text; charset=utf-8")
        c.JSON(200, gin.H{"data": "看看响应头"})
    })

postman

bind绑定参数

gin中bind可以很方便地将前端传递来的数据与 结构体 进行 参数绑定 以及 参数校验。

参数绑定
//ShouldBindJSON
type UserInfo struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    Sex  string `json:"sex"`
}
router.POST("/", func(c *gin.Context) {
    var userInfo UserInfo
    err := c.ShouldBindJSON(&userInfo)
    if err != nil {
        c.JSON(200, gin.H{"msg": "你错"})
        return
    }
    c.JSON(200, userInfo)
})

postman

//ShouldBindQuery
//tag对应为form
type UserInfo struct {
    Name string `json:"name" form:"name"`
    Age  int    `json:"age" form:"age"`
    Sex  string `json:"sex" form:"sex"`
}
router.POST("/query", func(c *ginContext) {
    var userInfo UserInfo
    err := c.ShouldBindQuery(&userInfo)
    if err != nil {
        c.JSON(200, gin.H{"msg": "你错"})
        return
    }
    c.JSON(200, userInfo)
})

postman

//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"`
}
router.POST("/uri/:name/:age/:sex", func(c *gin.Context) {
    var userInfo UserInfo
    err := c.ShouldBindUri(&userInfo)
    if err != nil {
        c.JSON(200, gin.H{"msg": "你错了"})
        return
    }
    c.JSON(200, userInfo)
})

postman

//ShouldBind
//会根据请求头中的content-type去自动绑定
//form-data的参数也用这个,tag用form
//默认的tag就是form,可以绑定json,query,param,yaml,xml

type UserInfo struct {
    Name string `form:"name"`
    Age  int    `form:"age"`
    Sex  string `form:"sex"`
}

    router.POST("/form", 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)
    })

postman

参数验证
  1. 常用验证器 ```go // 不能为空,并且不能没有这个字段 required: 必填字段,如: binding:"required" // 针对字符串的长度 min 最小长度,如: binding:"min=5" max 最大长度,如: binding:"max=18" len 长度,如: binding:"en=6" // 针对数字的大小 eg 等于,如: binding:"eg=3" ne 不等于,如: binding:"ne=12" gt 大于,如: binding:"gt=18" gte 大于等于,如: binding:"gte=18" lt 小于,如:binding:"lt=18" lte 小于等于,如: binding:"lte=18" // 针对同级字段的 eqfield 等于其他字段的值,如: PassWord string binding:"eqfield=Password" nefield 不等于其他字段的值 忽略字段,如: binding:"_"

type SignUserInfo struct { // binding:"required"不能为空或不传 Name string json:"name" binding:"required" //用户名 Mail string json:"mail" //邮箱 // binding:"min=6,max=12"最小长度6,最大长度12 Password string json:"password" binding:"min=6,max=12" //密码 RePassword string json:"re_password" binding:"eqfield=Password" //确认密码 }


2. gin内置验证器
```go
// 枚举 只能是red 或green
oneof=red green
// 字符串
contains=fengfeng // 包含fengfeng的字符串
excludes // 不包含
startswith // 字符串前缀
endswith // 字符串后缀
// 数组
dive // dive后面的验证就是针对数组中的每一个元素

// 网络验证
ip
ipv4
ipv6
uri
url
// uri 在于I(Identifier)是统一资源标示符,可以唯一标识一个资源
// url 在于Locater,是统一资源定位符,提供找到该资源的确切路径
// 日期验证 1月2号下午3点4分5秒在2006年
datetime=2006-1-2
type SignUserInfo struct {
    Sex        string   `json:"sex" binding:"oneof=男 女"`                             //性别
    HobbyList  []string `json:"hobby_list" binding:"required,dive,startswith=ilove"` //爱好
    IP         string   `json:"ip" binding:"ip"`                                     //ip地址
    //必须用datetime=2006-01-02 15:04:05这个时间,不能换成别的时间
    //1月2号下午3点4分5秒2006年
    Date string `json:"date" binding:"datetime=2006-01-02 15:04:05"` //日期
    }
  1. 自定义验证器错误信息
//main.go
router.POST("/", func(c *gin.Context) {
        type User struct {
            Name string `json:"name" binding:"required" msg:"用户名校验失败"`
            Age  int    `json:"age" binding:"required,gt=10" msg:"年龄校验失败"`
        }
        var user User
        err := c.ShouldBindJSON(&user)
        if err != nil {
            //c.JSON(200, gin.H{"msg": err.Error()})
            c.JSON(200, gin.H{"msg": GetValidMsg(err, &user)})
            return
        }
        c.JSON(200, gin.H{"data": user})
    })
//GetValidMsg函数
func GetValidMsg(err error, obj any) string {
    // 将err接口断言为具体类型
    //使用的时候传文件指针
    getObj := reflect.TypeOf(obj)
    if errs, ok := err.(validator.ValidationErrors); ok {
        //断言成功
        //循环每一个错误信息
        for _, e := range errs {
            if f, exist := getObj.Elem().FieldByName(e.Field()); exist {
                msg := f.Tag.Get("msg")
                return msg
            }
        }
    }

    return err.Error()
}
  1. 自定义验证器
//main
    //自定义验证器 sign
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        v.RegisterValidation("sign", signValid)
    }
    router.POST("/", func(c *gin.Context) {
        var user User
        err := c.ShouldBind(&user)
        if err != nil {
            c.JSON(200, err.Error())
            return
        }
        c.JSON(200, gin.H{"data": user})
        return
    })
//signValid函数和user结构体

type User struct {
    //自定义验证器 sign
    Name string `form:"name" binding:"required,sign" msg:"请输入名字"`
    Age  int    `form:"age" binding:"required,gt=10" msg:"请输入年龄"`
}

func signValid(fl validator.FieldLevel) bool {
    //不允许name为nameList中的值
    var nameList []string = []string{"lihan", "lihan3238", "Lihan"}
    for _, nameStr := range nameList {
        name := fl.Field().Interface().(string)
        if name == nameStr {
            return false
        }
    }
    return true

}

上传下载文件

上传文件
  1. 单文件

     // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
     // 单位是字节,<< 是左移预算符号,等价于 8 * 2^20
     // gin对文件上传大小的默认值是32MB
     router.MaxMultipartMemory = 8 << 20 // 8 MiB
     router.POST("/upload", func(c *gin.Context) {
         file, _ := c.FormFile("file")
         fmt.Println(file.Filename)
         fmt.Println(file.Size / 1024) //单位为KB
         //保存文件到本地
         c.SaveUploadedFile(file, "uploads/"+file.Filename)
         c.JSON(200, gin.H{"msg": "上传成功"})
     })
    
  2. 服务器保存文件的几种方式

下载文件
router.GET("/download", func(c *gin.Context) {

    //c.File("uploads/logo.png")
    //有些响应,比如图片,浏览器就会显示这图片,而不是下载,所以我们需要使浏览器起下载行为
    c.Header("Content-Type", "applicationoctet-stream")
    //一定要指定下载文件名(可以与源文件名同),不然默认download无后缀名
    c.Header("Content-Disposition","attachment; filename=1.png")
    //设置文件传输方式为二进制(乱码问题关)
    c.Header("Content-Transfer-Encoding","binary")
    //指定源文件
    c.File("uploads/logo.png")
}

如果是前后端模式下,后端就只需要响应一个文件数据 文件名和其他信息就写在请求头中

c.Header("fileName","xxx .png")
c.Header("msg","文件下载成功")
c.File("uploads/12 .png")

js

中间件和路由

中间件
  1. 单个路由的中间件
//main.go
    router.GET("/", m1, func(c *gin.Context) {
        fmt.Println("index...")
        c.JSON(200, gin.H{"msg": "index"})
    })//执行顺序:m1->index

    //m1中间件
    func m1(c *gin.Context) {
    fmt.Println("m1...")
    c.JSON(200, gin.H{"msg": "m1"})
    c.Abort() //阻止后续的处理函数执行
}
  1. //main.go
     router.GET("/", m1, func(c *gin.Context) {
    
         c.JSON(200, gin.H{"msg": "index"})
         fmt.Println("index...in")
         //next前是请求
         //Abort后不会执行之后的请求和响应,但是会执行已执行过请求的响应
         c.Next()
         //next后是响应
         fmt.Println("index...out")
     })
    
     //m1
     func m1(c *gin.Context) {
    
     c.JSON(200, gin.H{"msg": "m1"})
     fmt.Println("m1...in")
     c.Next()
     fmt.Println("m1...out")
    }
    
    #返回
    m1...in
    index...in
    index...out
    m1...out
    
  1. 全局注册中间件
//main.go
    router.Use(m10, m11) // 全局注册中间件

    router.GET("/1", func(c *gin.Context) {
        fmt.Println("index1......in")
        c.JSON(200, gin.H{"msg": "index1"})
        c.Next()
        fmt.Println("index1......out")
    })
    router.GET("/2", func(c *gin.Context) {
        fmt.Println("index2......in")
        c.JSON(200, gin.H{"msg": "index2"})
        c.Next()
        fmt.Println("index2......out")
    })
//中间件
func m10(c *gin.Context) {
    fmt.Println("m10......in")
    c.Next()
    fmt.Println("m10......out")
}
func m11(c *gin.Context) {
    fmt.Println("m11......in")
    c.Next()
    fmt.Println("m11......out")
}
m10......in
m11......in
index2......in
index2......out
m11......out
m10......out
  1. 中间件传递数据
//main.go
    router.GET("/", func(c *gin.Context) {
        name, _ := c.Get("name")
        fmt.Println(name)

        _user, _ := c.Get("user")
        user, ok := _user.(Person) // 使用类型断言
        if !ok {
            fmt.Println("类型断言失败")
        } else {
            fmt.Println(user.Name, user.Age)
        }

        c.JSON(200, gin.H{"msg": "index1"})

    })
    //中间件
    func m10(c *gin.Context) {
    fmt.Println("m10......in")
    c.Set("name", "lihan")
    c.Set("user", Person{
        Name: "lihan",
        Age:  18,
    })
}
路由分组
  1. 路由分组
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"`
    Id    int    `json:"id"`
}
type Response struct {
    Code int    `json:"code"`
    Data any    `json:"data"`
    Msg  string `json:"msg"`
}

func UserListView(c *gin.Context) {
    var userList []UserInfo = []UserInfo{
        {"lihan", 18},
        {"李寒", 22},
        {"op", 28},
    }
    c.JSON(200, Response{200, userList, "success"})
}

func ArticleListView(c *gin.Context) {
    var articleList []ArticleInfo = []ArticleInfo{
        {"gin", 1},
        {"go", 2},
        {"vue", 3},
    }
    c.JSON(200, Response{200, articleList, "success"})
}

func UserRouterInit(api *gin.RouterGroup) {
    api1 := api.Group("api_1")
    {
        api1.GET("/users1", UserListView)
        api1.POST("/users2", UserListView)
    }
}

func ArticleRouterInit(api *gin.RouterGroup) {
    api2 := api.Group("api_2")
    {
        api2.GET("/users3", ArticleListView)
        api2.POST("/users4", ArticleListView)
    }
}

func main() {
    router := gin.Default()
    api := router.Group("api")

    UserRouterInit(api)
    ArticleRouterInit(api)
    router.GET("/users", UserListView)
    router.Run(":8080")

}
  1. 路由分组中间件
package main

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

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

func _UserListView(c *gin.Context) {
    type UserInfo struct {
        Name string `json:"name"`
        Age  int    `json:"age"`
    }
    var userList []UserInfo = []UserInfo{
        {Name: "张三", Age: 18},
        {Name: "李四", Age: 19},
        {Name: "王五", Age: 20},
    }
    c.JSON(200, Res{200, userList, "success"})
}

func Middleware(c *gin.Context) {
    token := c.GetHeader("token")
    if token == "3238" {
        c.Next()
        return
    }
    c.JSON(200, Res{401, nil, "token error"})
    c.Abort()
} //中间件

func _UserRouterInit(router *gin.RouterGroup) {
    userManager := router.Group("user_manager")
    userManager.Use(Middleware)
    {
        userManager.GET("/users", _UserListView) // api/user_manager/users
    }
}

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

    api := router.Group("api")

    api.GET("/login", func(c *gin.Context) {
        c.JSON(200, Res{200, nil, "login success"})
    }) //不在中间件中的路由,不需要验证

    _UserRouterInit(api)

    router.Run(":8080")

}

postman

闭包:

func Middleware(c *gin.Context) {
    token := c.GetHeader("token")
    if token == "3238" {
        c.Next()
        return
    }
    c.JSON(200, Res{401, nil, "token error"})
    c.Abort()
} //中间件
//_UserRouterInit
userManager.Use(Middleware)

可写为:
func Middleware(msg string) gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("token")
        if token == "3238" {
            c.Next()
            return
        }
        c.JSON(200, Res{401, nil, msg})
        c.Abort()
    }
} //中间件
//_UserRouterInit
userManager.Use(Middleware("token error"))

gin.Default()中间件 等于gin.New()加上Logger(), Recovery()若干中间件

gin log日志

gin内置日志
func main() {
    file, _ := os.Create("gin.log")
    gin.DefaultWriter = io.MultiWriter(file, os.Stdout) //同时写入文件和控制台
    router := gin.Default()

    router.GET("/index", func(c *gin.Context) {
        c.String(200, "hello world")
    })

    router.Run(":8080")
}
//在项目目录下生成gin.log文件
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /index                    --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080
[GIN] 2023/09/22 - 13:33:20 | 200 |      16.375µs |    192.168.56.1 | GET      "/index"
[GIN] 2023/09/22 - 13:33:20 | 404 |         506ns |    192.168.56.1 | GET      "/favicon.ico"
定义格式
  1. 定义路由格式

     gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, numHandlers int) {
         log.Printf(
             "[lihan] %s %s %s %d\n",
             httpMethod,
             absolutePath,
             handlerName,
             numHandlers,
         )
     }
     file, _ := os.Create("gin.log")
     gin.DefaultWriter = io.MultiWriter(file, os.Stdout) //同时写入文件和控制台
     router := gin.Default()
    
     router.GET("/index", func(c *gin.Context) {
         c.String(200, "hello world")
     })
    
     router.Run(":8080")
    

  1. 定义日志格式 ```go package main

import ( "fmt" "io" "log" "os"

"github.com/gin-gonic/gin"

)

func myLogFormat(param gin.LogFormatterParams) string {

// 你的自定义格式
return fmt.Sprintf(
    "[lihan]    %s  |%d|    %s%s%s  %s\n",
    param.TimeStamp.Format("2006-01-02 15:04:05"),
    param.StatusCode,
    //param.Method,
    param.MethodColor(), param.Method, param.ResetColor(), //根据不同的请求类型输出不同颜色
    param.Path,
)

}

func main() { gin.SetMode(gin.ReleaseMode) gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, numHandlers int) { log.Printf( "[lihan] %s %s %s %d\n", httpMethod, absolutePath, handlerName, numHandlers, ) } file, _ := os.Create("gin.log") gin.DefaultWriter = io.MultiWriter(file, os.Stdout) //同时写入文件和控制台 router := gin.New() //router.Use(gin.LoggerWithFormatter(myLogFormat)) router.Use(gin.LoggerWithConfig(gin.LoggerConfig{Formatter: myLogFormat}))

router.GET("/index", func(c *gin.Context) {
    c.String(200, "hello world")
})

router.Run(":8080")

}


修改前:
![](log_2.png)
修改后:
![](log_3.png)

##### 第三方日志 logrus

1. 下载
```bush
go get github.com/sirupsen/logrus
  1. 日志等级
    logrus.SetLevel(logrus.DebugLevel)

    logrus.Error("出错了")
    logrus.Warnln("警告")
    logrus.Infof("信息")
    logrus.Debugf("debug")
    logrus.Println("打印")
    fmt.Println(logrus.GetLevel())

更改日志级别

日志等级

PanicLevel// 会抛一个异常
FatalLevel// 打印日志之后就会退出
ErrorLevel
WarnLevel
InfoLevel
DebugLevel
TraceLevel// 低级别
  1. 设置特定字段 ```go package main

import "github.com/sirupsen/logrus"

func main() { log_1 := logrus.WithField("app", "4.3logrus设置特定字段").WithField("service", "logrus") log_2 := logrus.WithFields(logrus.Fields{ "user_id": "22", "ip": "192.168.56.105", }) log_3 := log_1.WithFields(logrus.Fields{ "user_id": "22", "ip": "192.168.56.105", })

log_1.Errorf("出错了")
log_2.Errorf("出错了")
log_3.Errorf("出错了")

}


设置输出格式
- ForceColors: 是否强制使用额色输出。
- DisableColors: 是否禁用额色输出。
- ForceQuote: 是否强制引用所有值。
- DisableQuote: 是否禁用引用所有值。
- DisableTimestamp: 是否禁用时间戳记录
- FullTimestamp:是否在连接到 TTY 时输出完整的时间戳
- TimestampFormat: 用于输出完整时间戳的时间戳格式。
```go
    //输出行号


    //设置输出格式为json格式
    logrus.SetFormatter(&logrus.JSONFormatter{})

    //设置输出时间戳
    logrus.SetFormatter(&logrus.TextFormatter{
        ForceColors:     true,
        TimestampFormat: "2006-01-02 15:04:05",
        FullTimestamp:   true,
    })
  1. 控制台颜色 ```go package main

import "fmt"

func main() { // 前景色 fmt.Println("\033[30m 黑色 \033[0m") fmt.Println("\033[31m 红色 \033[0m") fmt.Println("\033[32m 绿色 \033[0m") fmt.Println("\033[33m 黄色 \033[0m") fmt.Println("\033[34m 蓝色 \033[0m") fmt.Println("\033[35m 紫色 \033[0m") fmt.Println("\033[36m 青色 \033[0m") fmt.Println("\033[37m 灰色 \033[0m") // 背景色 fmt.Println("\033[40m 黑色 \033[0m") fmt.Println("\033[41m 红色 \033[0m") fmt.Println("\033[42m 绿色 \033[0m") fmt.Println("\033[43m 黄色 \033[0m") fmt.Println("\033[44m 蓝色 \033[0m") fmt.Println("\033[45m 紫色 \033[0m") fmt.Println("\033[46m 青色 \033[0m") fmt.Println("\033[47m 灰色 \033[0m") }

![](logrus_2.png)

5. Hook


例如实现一个名称写入日志都加一个 field
我们需要实现两个方法以实现 Hook 接口
```go
type MyHook struct {
}

func (hook MyHook) Levels() []logrus.Level {
    return logrus.AllLevels
}

func (hook MyHook) Fire(entry *logrus.Entry) error {
    entry.Data["app"] = "lihan"
    return nil
}

func main() {
    logrus.AddHook(&MyHook{})

    logrus.Warnln("warning")
    logrus.Error("error")
}

logrus hook 是一个值得深入学习的设计,你可以轻易适用 hook 来实现多文件写入。 比如,warn&error 级别的日志独立输出到 error_warn.log 文件里,其他都放在一起。

package main

import (
    "os"

    "github.com/sirupsen/logrus"
)

type MyHook struct {
}

func (hook MyHook) Levels() []logrus.Level {
    return []logrus.Level{logrus.WarnLevel, logrus.ErrorLevel}
}

func (hook MyHook) Fire(entry *logrus.Entry) error {
    //entry.Data["app"] = "lihan"
    //fmt.Println(entry.Level)

    file, _ := os.OpenFile("4.日志log/error_warn.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
    line, _ := entry.String()

    file.Write([]byte(line + "\n"))
    return nil
}

func main() {
    logrus.AddHook(&MyHook{})

    logrus.Warnln("warning")
    logrus.Error("error")
    logrus.Debug("debug")
    logrus.Infoln("info")
}
  1. 日志分割 按照时间分割 ```go package main

import ( "fmt" "os" "time"

"github.com/sirupsen/logrus"

)

type FileDateHook struct { file *os.File logPath string fileDate string //判断日期切换目录 appName string }

func (hook FileDateHook) Levels() []logrus.Level { return []logrus.Level{logrus.WarnLevel, logrus.ErrorLevel} }

func (hook FileDateHook) Fire(entry *logrus.Entry) error { timer := entry.Time.Format("2006-01-0215-04") line, := entry.String() if hook.fileDate == timer { hook.file.Write([]byte(line)) return nil } //有新时间 hook.file.Close() os.MkdirAll(fmt.Sprintf("%s/%s", hook.logPath, timer), os.ModePerm)

filename := fmt.Sprintf("%s/%s/%s.log", hook.logPath, timer, hook.appName)
hook.file, _ = os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
hook.fileDate = timer
hook.file.Write([]byte(line))
return nil

}

func InitFile(logPath, appName string) {

fileDate := time.Now().Format("2006-01-02_15-04")
//创建目录
err := os.MkdirAll(fmt.Sprintf("%s/%s", logPath, fileDate), os.ModePerm)
if err != nil {
    logrus.Error(err)
    return
}

filename := fmt.Sprintf("%s/%s/%s.log", logPath, fileDate, appName)
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
if err != nil {
    logrus.Error(err)
    return
}
filehook := FileDateHook{file, logPath, fileDate, appName}
logrus.AddHook(&filehook)

}

func main() { InitFile("4.日志log/logs_1", "lihan_log") for { time.Sleep(20 * time.Second) logrus.GetLevel() logrus.Warnln("warning")

    logrus.Error("error")
    logrus.Infoln("info")
}

}

按照等级分割
err,warn,info,all.log
```go
package main

import (
    "fmt"
    "os"

    "github.com/sirupsen/logrus"
)

const (
    alllog  = "all"
    errlog  = "err"
    warnlog = "warn"
    infolog = "info"
)

type FileLevelHook struct {
    file     *os.File
    errFile  *os.File
    warnFile *os.File
    infoFile *os.File
    logPath  string
}

func (hook FileLevelHook) Levels() []logrus.Level {
    return logrus.AllLevels
}

func (hook FileLevelHook) Fire(entry *logrus.Entry) error {
    line, _ := entry.String()
    switch entry.Level {
    case logrus.ErrorLevel:
        hook.errFile.Write([]byte(line))
    case logrus.WarnLevel:
        hook.warnFile.Write([]byte(line))
    case logrus.InfoLevel:
        hook.infoFile.Write([]byte(line))

    }

    hook.file.Write([]byte(line))
    return nil
}

func InitLevel(logPath string) {

    err := os.MkdirAll(fmt.Sprintf("%s", logPath), os.ModePerm)
    if err != nil {
        logrus.Error(err)
        return
    }

    allFile, _ := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, alllog), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
    errFile, _ := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, errlog), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
    warnFile, _ := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, warnlog), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
    infoFile, _ := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, infolog), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)

    filehook := FileLevelHook{allFile, errFile, warnFile, infoFile, logPath}
    logrus.AddHook(&filehook)

}

func main() {
    InitLevel("4.日志log/log_level")
    logrus.Error("error")
    logrus.Warnln("warning")
    logrus.Infoln("info")
    logrus.Println("print")
}
  1. gin集成logrus
    看不下去了呜哇哇哇!!!