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-data
和application/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内置日志组件:
为什么要使用日志
- 记录用户操作,猜测用户行为
- 记录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单独放一个文件中,然后引入进来
-
初始化日志器和日志实例
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") }
-
在项目中使用定义的日志实例记录日志
// user列表 func ListUsers(ctx *gin.Context){ util.LogEntry.Error("用户列表") ctx.JSON(200, gin.H{ "message": "hello go", }) }
-
定义日志中间件,记录常见异常的日志信息
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() } } }
-
gin集成自定义的中间件
router := gin.New() // 注册自定义的日志器 router.Use(middleware.LoggerMiddleware())