gin入门

Gin入门笔记

1. 初始gin

1.1. 依赖安装

go get github.com/gin-gonic/gin

写gin程序都有一套固定的格式

  1. 初始化
  2. 写路由
  3. 监听运行

1.2. hello world

package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {router := gin.Default()router.GET("/", func(c *gin.Context) {c.String(http.StatusOK, "Hello World")})router.Run(":8000") 
}
  1. router:=gin.Default():这是默认的服务器。使用gin的Default方法创建一个路由Handler
  2. 然后通过Http方法绑定路由规则和路由函数。不同于net/http库的路由函数,gin进行了封装,把requestresponse都封装到了gin.Context的上下文环境中。
  3. 最后启动路由的Run方法监听端口。还可以用http.ListenAndServe(":8080", router),或者自定义Http服务器配置。

1.3. 两种启动方式

// 启动方式一
router.Run(":8000")
// 启动方式二
http.ListenAndServe(":8000", router)

1.4. 修改ip为内网

r.Run(":8080") // 等价于 r.Run("0.0.0.0:8080")

1.5. 关闭debug输出

不想看到gin默认的那些debug输出,怎么办呢?

设置运行模式即可,默认是debug

gin.SetMode("release")

案例:

package mainimport ("github.com/gin-gonic/gin"
)func main() {//使用gin的Default方法创建一个路由Handlerrouter := gin.Default()//通过Http方法绑定路由规则和路由函数,访问/index的路由,将由对应的函数去处理router.GET("/index", Index)//启动路由的Run方法监听端口router.Run("0.0.0.0:8080")//用原生http的方式,router.Run本质就是ListenAndServe的进一步封装//http.ListenAndServe(":8080",router)
}
func Index(c *gin.Context) {//状态码 200 表示正常响应 http.StatusOKc.String(200, "Hello world!")
}

2. 响应

2.1. 响应数据

package mainimport ("github.com/gin-gonic/gin""net/http"
)// 返回字符串
func _string(c *gin.Context) {c.String(http.StatusOK, "hello")
}// 返回json
func _json(c *gin.Context) {type UserInfo struct {UserName string `json:"user_name"`Age      int    `json:"age"`Password string `json:"-"`}user := UserInfo{UserName: "小木", Age: 20, Password: "123456"}c.JSON(200, user)c.JSON(200, gin.H{"user_name": "小木", "age": "20"})
}// 返回map
func _map(c *gin.Context) {userMap := map[string]string{"user_name": "小木","age":       "20",}c.JSON(200, userMap)
}// 返回xml
func _xml(c *gin.Context) {c.XML(http.StatusOK, gin.H{"user": "hanru", "message": "hey", "status": http.StatusOK})
}// 返回yaml
func _yaml(c *gin.Context) {c.YAML(http.StatusOK, gin.H{"user": "hanru", "message": "hey", "status": http.StatusOK})
}// 返回HTML
func _html(c *gin.Context) {// HTML的第三个参数是可以向HTML中传递数据// 但是现在都是前后端分离的时代了,也很少使用后端返回模板了,知道怎么用就好// html文件中使用 <title>{{.title}}</title>c.HTML(200, "index.html", gin.H{"username": "dysaniaer"})
}// 重定向
func _redirect(c *gin.Context) {c.Redirect(302, "http://www.baidu.com")//c.Redirect(302, "/html")
}func main() {router := gin.Default()// 在golang总,没有相对文件的路径,它只有相对项目的路径// 网页请求这个静态目录的前缀, 第二个参数是一个目录,注意,前缀不要重复router.StaticFS("/static", http.Dir("static/static"))// 配置单个文件, 网页请求的路由,文件的路径router.StaticFile("/titian.png", "static/titian.png")// 使用LoadHTMLGlob加载一个目录下的所有html文件// 也可以使用LoadHTMLFiles加载单个html文件// load之后,下面才能用这个文件名// 加载模板 只有这里加载了模板,下面才能用router.LoadHTMLGlob("templates/*")// r.LoadHTMLFiles("templates/index.html")router.GET("/string", _string)router.GET("/json", _json)router.GET("/map", _map)router.GET("/xml", _xml)router.GET("/yaml", _yaml)router.GET("/html", _html)router.GET("/baidu", _redirect)router.Run(":80")
}

2.2. 重定向

router.GET("/redirect", func(c *gin.Context) {//支持内部和外部的重定向c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")//c.Redirect(302, "/html")
})

301 Moved Permanently

被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。

302 Found

请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。

3. 请求

3.1. 查询参数Query

func _query(c *gin.Context) {fmt.Println(c.Query("name"))// 返回 name值和是否接收到fmt.Println(c.GetQuery("name"))// 拿到多个相同的查询参数fmt.Println(c.QueryArray("name"))// 如果没有接收到参数就用默认值fmt.Println(c.DefaultQuery("name", "Jim"))
}

3.2. 动态参数 Param

func _param(c *gin.Context) {fmt.Println(c.Param("user_id"))fmt.Println(c.Param("book_id"))
}//两个路径都会匹配
router.GET("/param/:user_id/", _param)
router.GET("/param/:user_id/:book_id", _param)// ?param/12
// ?param/12/123

3.3. 表单 PostForm

可以接收 multipart/form-data; application/x-www-form-urlencoded

form-data

----------------------------638149124879484626406689
Content-Disposition: form-data; name="name"枫枫
----------------------------638149124879484626406689
Content-Disposition: form-data; name="name"zhangsan
----------------------------638149124879484626406689
Content-Disposition: form-data; name="addr"长沙市
----------------------------638149124879484626406689--

x-www-form-urlencoded

name=abc&age=23

json

{"name": "枫枫","age": 21
}func _raw(c *gin.Context) {body, _ := c.GetRawData()contentType := c.GetHeader("Content-Type")switch contentType {case "application/json":// json解析到结构体type User struct {Name string `json:"name"`Age  int    `json:"age"`}var user Usererr := json.Unmarshal(body, &user)if err != nil {fmt.Println(err.Error())}fmt.Println(user)}
}

封装一个解析json到结构体上的函数

func bindJson(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
}

3.4. 四大请求方式

GET POST PUT DELETE

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

  • GET:从服务器取出资源(一项或多项)
  • POST:在服务器新建一个资源
  • PUT:在服务器更新资源(客户端提供完整资源数据)
  • PATCH:在服务器更新资源(客户端提供需要修改的资源数据)
  • DELETE:从服务器删除资源
// 以文字资源为例// GET    /articles          文章列表
// GET    /articles/:id      文章详情
// POST   /articles          添加文章
// PUT    /articles/:id      修改某一篇文章
// DELETE /articles/:id      删除某一篇文章package mainimport ("encoding/json""fmt""github.com/gin-gonic/gin"
)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 _bindJson(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
}// _getList 文章列表页面
func _getList(c *gin.Context) {// 包含搜索,分页articleList := []ArticleModel{{"Go语言入门", "这篇文章是《Go语言入门》"},{"python语言入门", "这篇文章是《python语言入门》"},{"JavaScript语言入门", "这篇文章是《JavaScript语言入门》"},}c.JSON(200, Response{0, articleList, "成功"})
}// _getDetail 文章详情
func _getDetail(c *gin.Context) {// 获取param中的idfmt.Println(c.Param("id"))article := ArticleModel{"Go语言入门", "这篇文章是《Go语言入门》",}c.JSON(200, Response{0, article, "成功"})
}// _create 创建文章
func _create(c *gin.Context) {// 接收前端传递来的json数据var article ArticleModelerr := _bindJson(c, &article)if err != nil {fmt.Println(err)return}c.JSON(200, Response{0, article, "添加成功"})
}// _update 编辑文章
func _update(c *gin.Context) {fmt.Println(c.Param("id"))var article ArticleModelerr := _bindJson(c, &article)if err != nil {fmt.Println(err)return}c.JSON(200, Response{0, article, "修改成功"})
}// _delete 删除文章
func _delete(c *gin.Context) {fmt.Println(c.Param("id"))c.JSON(200, Response{0, map[string]string{}, "删除成功"})
}func main() {router := gin.Default()router.GET("/articles", _getList)       // 文章列表router.GET("/articles/:id", _getDetail) // 文章详情router.POST("/articles", _create)       // 添加文章router.PUT("/articles/:id", _update)    // 编辑文章router.DELETE("/articles/:id", _delete) // 删除文章router.Run(":80")
}

3.5. 请求头相关

请求头参数获取

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

router.GET("/", func(c *gin.Context) {// 首字母大小写不区分  单词与单词之间用 - 连接// 用于获取一个请求头fmt.Println(c.GetHeader("User-Agent"))//fmt.Println(c.GetHeader("user-agent"))//fmt.Println(c.GetHeader("user-Agent"))//fmt.Println(c.GetHeader("user-AGent"))// Header 是一个普通的 map[string][]stringfmt.Println(c.Request.Header)// 如果是使用 Get方法或者是 .GetHeader,那么可以不用区分大小写,并且返回第一个valuefmt.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": "成功"})
})

3.6. 响应头相关

设置响应头

// 设置响应头
router.GET("/res", func(c *gin.Context) {c.Header("Token", "jhgeu%hsg845jUIF83jh")c.Header("Content-Type", "application/text; charset=utf-8")c.JSON(0, gin.H{"data": "看看响应头"})
})

4. Bind绑定器

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

4.1. 参数绑定

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

Must Bind

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

Should Bind

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

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

ShouldBindJSON

绑定json数据

package mainimport "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 UserInfoerr := c.ShouldBindJSON(&userInfo)if err != nil {c.JSON(200, gin.H{"msg": "你错了"})return}c.JSON(200, userInfo)})router.Run(":80")
}
ShouldBindQuery

绑定查询参数

tag对应为form

// ?name=枫枫&age=21&sex=男
package mainimport ("fmt""github.com/gin-gonic/gin"
)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 UserInfoerr := 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

// /uri/fengfeng/21/男package mainimport ("fmt""github.com/gin-gonic/gin"
)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 UserInfoerr := c.ShouldBindUri(&userInfo)if err != nil {fmt.Println(err)c.JSON(200, gin.H{"msg": "你错了"})return}c.JSON(200, userInfo)})router.Run(":80")
}
ShouldBind

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

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

默认的tag就是form

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

package mainimport ("fmt""github.com/gin-gonic/gin"
)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 UserInfoerr := c.ShouldBind(&userInfo)if err != nil {fmt.Println(err)c.JSON(200, gin.H{"msg": "你错了"})return}c.JSON(200, userInfo)})router.Run(":80")
}

4.2. 验证器

4.2.1. 常用验证器

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

// 不能为空,并且不能没有这个字段
required: 必填字段,如:binding:"required"  // 针对字符串的长度
min 最小长度,如:binding:"min=5"
max 最大长度,如:binding:"max=10"
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=Password"`
nefield 不等于其他字段的值- 忽略字段,如:binding:"-"
4.2.2. gin内置验证器
// 枚举  只能是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-01-02
4.2.3. 自定义验证的错误信息

当验证不通过时,会给出错误的信息,但是原始的错误信息不太友好,不利于用户查看

只需要给结构体加一个msg 的tag

type UserInfo struct {Username string `json:"username" binding:"required" msg:"用户名不能为空"`Password string `json:"password" binding:"min=3,max=6" msg:"密码长度不能小于3大于6"`Email    string `json:"email" binding:"email" msg:"邮箱地址格式不正确"`
}

当出现错误时,就可以来获取出错字段上的msg

  • err:这个参数为ShouldBindJSON返回的错误信息
  • obj:这个参数为绑定的结构体
  • 还有一点要注意的是,validator这个包要引用v10这个版本的,否则会出错
// GetValidMsg 返回结构体中的msg参数
func GetValidMsg(err error, obj any) string {// 使用的时候,需要传obj的指针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()
}
4.2.4. 自定义验证器
  1. 注册验证器函数
// github.com/go-playground/validator/v10
// 注意这个版本得是v10的if v, ok := binding.Validator.Engine().(*validator.Validate); ok {v.RegisterValidation("sign", signValid)
}
  1. 编写函数
// 如果用户名不等于fengfeng就校验失败
func signValid(fl validator.FieldLevel) bool {name := fl.Field().Interface().(string)if name != "fengfeng" {return false}return true
}
  1. 使用
type UserInfo struct {Name string `json:"name" binding:"sign" msg:"用户名错误"`Age  int    `json:"age" binding:""`
}
package mainimport ("github.com/gin-gonic/gin""github.com/gin-gonic/gin/binding""github.com/go-playground/validator/v10""reflect"
)func GetValidMsg(err error, obj interface{}) string {// obj为结构体指针getObj := reflect.TypeOf(obj)// 断言为具体的类型,err是一个接口if errs, ok := err.(validator.ValidationErrors); ok {for _, e := range errs {if f, exist := getObj.Elem().FieldByName(e.Field()); exist {return f.Tag.Get("msg") //错误信息不需要全部返回,当找到第一个错误的信息时,就可以结束}}}return err.Error()
}
// 如果用户名不等于fengfeng就校验失败
func signValid(fl validator.FieldLevel) bool {name := fl.Field().Interface().(string)if name != "fengfeng" {return false}return true
}func main() {router := gin.Default()router.POST("/", func(c *gin.Context) {type UserInfo struct {Name string `json:"name" binding:"sign" msg:"用户名错误"`Age  int    `json:"age" binding:""`}var user UserInfoerr := c.ShouldBindJSON(&user)if err != nil {// 显示自定义的错误信息msg := GetValidMsg(err, &user)c.JSON(200, gin.H{"msg": msg})return}c.JSON(200, user)})// 注册if v, ok := binding.Validator.Engine().(*validator.Validate); ok {v.RegisterValidation("sign", signValid)}router.Run(":80")
}

5. 文件上传和下载

5.1. 文件上传

5.1.1. 单文件上传
func main() {router := gin.Default()// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)// 单位是字节, << 是左移预算符号,等价于 8 * 2^20// gin对文件上传大小的默认值是32MBrouter.MaxMultipartMemory = 8 << 20  // 8 MiBrouter.POST("/upload", func(c *gin.Context) {// 单文件file, _ := c.FormFile("file")log.Println(file.Filename)dst := "./" + file.Filename// 上传文件至指定的完整文件路径c.SaveUploadedFile(file, dst)c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))})router.Run(":8080")
}
5.1.2. 服务端保存文件的几种方式
SaveUploadedFile
c.SaveUploadedFile(file, dst)  // 文件对象  文件路径,注意要从项目根路径开始写
Create+Copy

file.Open的第一个返回值就是我们讲文件对象中的那个文件(只读的),我们可以使用这个去直接读取文件内容

file, _ := c.FormFile("file")
log.Println(file.Filename)
// 读取文件中的数据,返回文件对象
fileRead, _ := file.Open()
dst := "./" + file.Filename
// 创建一个文件
out, err := os.Create(dst)
if err != nil {fmt.Println(err)
}
defer out.Close()
// 拷贝文件对象到out中
io.Copy(out, fileRead)
5.1.3. 读取上传的文件
file, _ := c.FormFile("file")
// 读取文件中的数据,返回文件对象
fileRead, _ := file.Open()
data, _ := io.ReadAll(fileRead)
fmt.Println(string(data))

这里的玩法就很多了

例如我们可以基于文件中的内容,判断是否需要保存到服务器中

5.1.4. 多文件上传
func main() {router := gin.Default()// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)router.MaxMultipartMemory = 8 << 20 // 8 MiBrouter.POST("/upload", func(c *gin.Context) {// Multipart formform, _ := c.MultipartForm()files := form.File["upload[]"]  // 注意这里名字不要对不上了for _, file := range files {log.Println(file.Filename)// 上传文件至指定目录c.SaveUploadedFile(file, "./"+file.Filename)}c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))})router.Run(":8080")
}

5.2. 文件下载

5.2.1. 直接响应一个路径下的文件
c.File("uploads/12.png")

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

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

注意,文件下载浏览器可能会有缓存,这个要注意一下

解决办法就是加查询参数

5.2.2. 前后端模式下的文件下载

如果是前后端模式下,后端就只需要响应一个文件数据

文件名和其他信息就写在请求头中

c.Header("fileName", "xxx.png")
c.Header("msg", "文件下载成功")
c.File("uploads/12.png")
5.2.3. 前端写法
async downloadFile(row) {this.$http({method: 'post',url: 'file/upload',data:postData,responseType: "blob"}).then(res => {const _res = res.datalet blob = new Blob([_res], {type: 'application/png'});let downloadElement = document.createElement("a");let href = window.URL.createObjectURL(blob); //创建下载的链接downloadElement.href = href;downloadElement.download = res.headers["fileName"]; //下载后文件名document.body.appendChild(downloadElement);downloadElement.click(); //点击下载document.body.removeChild(downloadElement); //下载完成移除元素window.URL.revokeObjectURL(href); //释放掉blob对象})}

6. 中间件和路由

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

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

6.1. 单独注册中间件

import ("fmt""github.com/gin-gonic/gin""net/http"
)
func indexHandler(c *gin.Context) {fmt.Println("index.....")c.JSON(http.StatusOK, gin.H{"msg": "index",})
}//定义一个中间件
func m1(c *gin.Context) {fmt.Println("m1 in.........")
}
func main() {r := gin.Default()//m1处于indexHandler函数的前面,请求来之后,先走m1,再走indexr.GET("/index", m1, indexHandler)_ = r.Run()
}

6.2. 多个中间件

router.GET,后面可以跟很多HandlerFunc方法,这些方法其实都可以叫中间件

package mainimport ("fmt""github.com/gin-gonic/gin"
)func m1(c *gin.Context) {fmt.Println("m1 ...in")
}
func m2(c *gin.Context) {fmt.Println("m2 ...in")
}func main() {router := gin.Default()router.GET("/", m1, func(c *gin.Context) {fmt.Println("index ...")c.JSON(200, gin.H{"msg": "响应数据"})}, m2)router.Run(":8080")
}/*
m1  ...in
index ...
m2  ...in
*/

6.3. 中间件拦截响应

c.Abort()拦截,后续的HandlerFunc就不会执行了

package mainimport ("fmt""github.com/gin-gonic/gin"
)func m1(c *gin.Context) {fmt.Println("m1 ...in")c.JSON(200, gin.H{"msg": "第一个中间件拦截了"})c.Abort()
}
func m2(c *gin.Context) {fmt.Println("m2 ...in")
}func main() {router := gin.Default()router.GET("/", m1, func(c *gin.Context) {fmt.Println("index ...")c.JSON(200, gin.H{"msg": "响应数据"})}, m2)router.Run(":8080")
}

6.4. 中间件放行

c.Next(),Next前后形成了其他语言中的请求中间件和响应中间件

package mainimport ("fmt""github.com/gin-gonic/gin"
)func m1(c *gin.Context) {fmt.Println("m1 ...in")c.Next()fmt.Println("m1 ...out")
}
func m2(c *gin.Context) {fmt.Println("m2 ...in")c.Next()fmt.Println("m2 ...out")
}func main() {router := gin.Default()router.GET("/", m1, func(c *gin.Context) {fmt.Println("index ...in")c.JSON(200, gin.H{"msg": "响应数据"})c.Next()fmt.Println("index ...out")}, m2)router.Run(":8080")
}/*
m1 ...in
index ...in
m2 ...in   
m2 ...out  
index ...out
m1 ...out
*/

在这里插入图片描述

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

6.5. 全局注册中间件

package mainimport ("fmt""github.com/gin-gonic/gin"
)func m10(c *gin.Context) {fmt.Println("m1 ...in")c.Next()fmt.Println("m1 ...out")
}func main() {router := gin.Default()router.Use(m10)router.GET("/", func(c *gin.Context) {fmt.Println("index ...in")c.JSON(200, gin.H{"msg": "index"})c.Next()fmt.Println("index ...out")})router.Run(":8080")}

使用Use去注册全局中间件,Use接收的参数也是多个HandlerFunc

6.6. 中间件传递数据

使用Set设置一个key-value,

在后续中间件中使用Get接收数据

package mainimport ("fmt""github.com/gin-gonic/gin"
)func m10(c *gin.Context) {fmt.Println("m1 ...in")c.Set("name", "fengfeng")
}func main() {router := gin.Default()router.Use(m10)router.GET("/", func(c *gin.Context) {fmt.Println("index ...in")name, _ := c.Get("name")fmt.Println(name)c.JSON(200, gin.H{"msg": "index"})})router.Run(":8080")}

value的类型是any类型,所有我们可以用它传任意类型,在接收的时候做好断言即可

package mainimport ("fmt""github.com/gin-gonic/gin"
)type User struct {Name stringAge  int
}func m10(c *gin.Context) {fmt.Println("m1 ...in")c.Set("name", User{"枫枫", 21})c.Next()fmt.Println("m1 ...out")
}func main() {router := gin.Default()router.Use(m10)router.GET("/", func(c *gin.Context) {fmt.Println("index ...in")name, _ := c.Get("name")user := name.(User)fmt.Println(user.Name, user.Age)c.JSON(200, gin.H{"msg": "index"})})router.Run(":8080")}

6.7. 路由分组

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

例如,以下的路由前面统一加上api的前缀

package mainimport "github.com/gin-gonic/gin"func main() {router := gin.Default()r := router.Group("/api")r.GET("/index", func(c *gin.Context) {c.String(200, "index")})r.GET("/home", func(c *gin.Context) {c.String(200, "home")})router.Run(":8080")
}

6.8. 路由分组注册中间件

package mainimport ("fmt""github.com/gin-gonic/gin"
)func middle(c *gin.Context) {fmt.Println("middle ...in")
}func main() {router := gin.Default()r := router.Group("/api").Use(middle)  // 可以链式,也可以直接r.Use(middle)r.GET("/index", func(c *gin.Context) {c.String(200, "index")})r.GET("/home", func(c *gin.Context) {c.String(200, "home")})router.Run(":8080")
}

这样写我们就可以指定哪一些分组下可以使用中间件了

当然,中间件还有一种写法,就是使用函数加括号的形式

package mainimport ("fmt""github.com/gin-gonic/gin"
)func middle(c *gin.Context) {fmt.Println("middle ...in")
}
func middle1() gin.HandlerFunc {// 这里的代码是程序一开始就会执行return func(c *gin.Context) {// 这里是请求来了才会执行fmt.Println("middle1 ...inin")}
}func main() {router := gin.Default()r := router.Group("/api").Use(middle, middle1())r.GET("/index", func(c *gin.Context) {c.String(200, "index")})r.GET("/home", func(c *gin.Context) {c.String(200, "home")})router.Run(":8080")
}

6.9. gin.Default

func Default() *Engine {debugPrintWARNINGDefault()engine := New()engine.Use(Logger(), Recovery())return engine
}

gin.Default()默认使用了Logger和Recovery中间件,其中:

Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。 Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。 如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

使用gin.New,如果不指定日志,那么在控制台中就不会有日志显示

7.gin内置日志

为什么要使用日志

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

7.1. 输出日志到log文件

package mainimport ("github.com/gin-gonic/gin""io""os"
)func main() {// 输出到文件f, _ := os.Create("gin.log")//gin.DefaultWriter = io.MultiWriter(f)// 如果需要同时将日志写入文件和控制台,请使用以下代码。gin.DefaultWriter = io.MultiWriter(f, os.Stdout)router := gin.Default()router.GET("/", func(c *gin.Context) {c.JSON(200, gin.H{"msg": "/"})})router.Run()
}

7.2. 定义路由格式

启动gin,它会显示所有的路由,默认格式如下

[GIN-debug] POST   /foo    --> main.main.func1 (3 handlers)
[GIN-debug] GET    /bar    --> main.main.func2 (3 handlers)
[GIN-debug] GET    /status --> main.main.func3 (3 handlers)
gin.DebugPrintRouteFunc = func(httpMethod,absolutePath,handlerName string,nuHandlers int) {log.Printf("[ feng ] %v %v %v %v\n",httpMethod,absolutePath,handlerName,nuHandlers,)
}
/*  输出如下
2022/12/11 14:10:28 [ feng ] GET / main.main.func3 3
2022/12/11 14:10:28 [ feng ] POST /index main.main.func4 3
2022/12/11 14:10:28 [ feng ] PUT /haha main.main.func5 3
2022/12/11 14:10:28 [ feng ] DELETE /home main.main.func6 3
*/

7.3. 查看路由

router.Routes()  // 它会返回已注册的路由列表

7.4. 环境切换

在这里插入图片描述

如果不想看到这些debug日志,那么我们可以改为release模式

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

7.5. 修改log的显示

默认的是这样的

[GIN] 2022/12/11 - 14:22:00 | 200 |  0s |  127.0.0.1 | GET  "/"

如果觉得不好看,我们可以自定义

package mainimport ("fmt""github.com/gin-gonic/gin"
)func LoggerWithFormatter(params gin.LogFormatterParams) string {return fmt.Sprintf("[ feng ] %s  | %d | \t %s | %s | %s \t  %s\n",params.TimeStamp.Format("2006/01/02 - 15:04:05"),params.StatusCode,  // 状态码params.ClientIP,  // 客户端ipparams.Latency,  // 请求耗时params.Method,  // 请求方法params.Path,  // 路径)
}
func main() {router := gin.New()router.Use(gin.LoggerWithFormatter(LoggerWithFormatter))router.Run()}

也可以这样

func LoggerWithFormatter(params gin.LogFormatterParams) string {return fmt.Sprintf("[ feng ] %s  | %d | \t %s | %s | %s \t  %s\n",params.TimeStamp.Format("2006/01/02 - 15:04:05"),params.StatusCode,params.ClientIP,params.Latency,params.Method,params.Path,)
}
func main() {router := gin.New()router.Use(gin.LoggerWithConfig(gin.LoggerConfig{Formatter: LoggerWithFormatter},),)router.Run()}

但是你会发现自己这样输出之后,没有颜色了,不太好看,我们可以输出有颜色的log

func LoggerWithFormatter(params gin.LogFormatterParams) string {var statusColor, methodColor, resetColor stringstatusColor = params.StatusCodeColor()methodColor = params.MethodColor()resetColor = params.ResetColor()return fmt.Sprintf("[ feng ] %s  | %s %d  %s | \t %s | %s | %s %-7s %s \t  %s\n",params.TimeStamp.Format("2006/01/02 - 15:04:05"),statusColor, params.StatusCode, resetColor,params.ClientIP,params.Latency,methodColor, params.Method, resetColor,params.Path,)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/58670.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CentOS 7 安装 ntp,自动校准系统时间

1、安装 ntp yum install ntp 安装好后&#xff0c;ntp 会自动注册成为服务&#xff0c;服务名称为 ntpd 2、查看当前 ntpd 服务的状态 systemctl status ntpd 3、启动 ntpd 服务、查看 ntpd 服务的状态 systemctl start ntpdsystemctl status ntpd 4、设置 ntpd 服务开机启…

转发forward与重定redirect

转发与重定向在网络通信和Web开发中扮演着不同的角色&#xff0c;它们之间的主要区别体现在以下几个方面&#xff1a; 一、定义与实现方式 转发&#xff1a; 在Web开发中&#xff0c;转发通常是由request请求发起的&#xff0c;是服务器内部的一种行为。转发时&#xff0c;服务…

使用 `Celery` 配合 `RabbitMQ` 作为消息代理,实现异步任务的调度、重试、定时任务以及错误监控等功能

python基础代码、优化、扩展和监控的完整示例。此示例使用 Celery 配合 RabbitMQ 作为消息代理&#xff0c;实现异步任务的调度、重试、定时任务以及错误监控等功能。 项目结构 我们将项目结构组织如下&#xff0c;以便代码逻辑清晰且易于扩展&#xff1a; project/ │ ├──…

Unity3D UI 拖拽

Unity3D 实现 UI 元素拖拽功能。 UI 拖拽 通常画布上的 UI 元素都是固定位置的&#xff0c;我们可以通过实现拖拽接口&#xff0c;让 UI 元素可以被拖拽到其他位置。 拖拽接口 创建一个脚本 UIDrag.cs&#xff0c;在默认继承的 MonoBehaviour 后面&#xff0c;再继承三个接…

【LeetCode】【算法】394. 字符串解码

LeetCode 394. 字符串解码 题目描述 给定一个经过编码的字符串&#xff0c;返回它解码后的字符串。 编码规则为: k[encoded_string]&#xff0c;表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 你可以认为输入字符串总是有效的&#xff1b;输入字…

深度学习:预训练(Pre-training详解

预训练&#xff08;Pre-training详解 预训练&#xff08;Pre-training&#xff09;是深度学习和自然语言处理领域中一个核心概念&#xff0c;特别是在面对需要大规模参数模型的应用场景下。预训练涉及在通常是大规模且多样化的数据集上训练模型&#xff0c;目的是捕获广泛且通…

【新手入门软件测试--该如何分辨前后端问题及如何定位日志--前后端问题分辨与日志定位查询问题】

前后端问题分辨与日志定位查询 一、前端问题1. 页面无法加载2. 样式错乱3. API请求失败4. 数据格式错误5. 跨域请求问题 二、后端问题6. 表单验证失败7. 数据库连接失败8. 请求超时9. 权限问题10. JavaScript运行错误 三、日志查询的方法1. 查看日志文件2. 过滤关键字3. 实时查…

基于 SSM(Spring + Spring MVC + MyBatis)框架构建电器网上订购系统

基于 SSM&#xff08;Spring Spring MVC MyBatis&#xff09;框架构建电器网上订购系统可以为用户提供一个方便快捷的购物平台。以下将详细介绍该系统的开发流程&#xff0c;包括需求分析、技术选型、数据库设计、项目结构搭建、主要功能实现以及前端页面设计。 需求分析 …

esp32学习:利用虫洞ESP32开发板,快速实现无线图传

我们的虫洞开发板&#xff0c;能够完美运行esp who AI代码&#xff0c;所以实现无线图传那是非常容易的&#xff0c;我们先看看examples目录&#xff1a; 里面有比较多的web例程&#xff0c;在这些例程下&#xff0c;稍作修改&#xff0c;就可以快速实现我的图传无线功能&#…

mac m1 docker本地部署canal 监听mysql的binglog日志

mac m1 docker本地部署canal监听mysql的binglog日志(虚拟机同理) 根据黑马视频部署 1.docker 部署mysql 1.docker拉取mysql 镜像 因为m1是arm架构.需要多加一条信息 正常拉取 docker pull mysql:tagm1拉取 5.7的版本. tag需要自己指定版本 docker pull --platform linux/x…

还在为慢速数据传输苦恼?Linux 零拷贝技术来帮你!

前言 程序员的终极追求是什么&#xff1f;当系统流量大增&#xff0c;用户体验却丝滑依旧&#xff1f;没错&#xff01;然而&#xff0c;在大量文件传输、数据传递的场景中&#xff0c;传统的“数据搬运”却拖慢了性能。为了解决这一痛点&#xff0c;Linux 推出了 零拷贝 技术&…

基于java+SpringBoot+Vue的微服务在线教育系统设计与实现

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis Maven mysql5.7或8.0等等组成&#x…

【无标题】西安交通大学提出少锚点的端到端车道线检测算法Polar R-CNN

Abstract 车道线检测在自动驾驶中是一个关键且充满挑战的任务&#xff0c;特别是在实际场景中&#xff0c;由于车道线可能因其他车辆而被遮挡、形状纤细且长度较长&#xff0c;检测难度增大。现有基于锚点的检测方法通常依赖于预设的锚点来提取特征&#xff0c;并随后对车道线…

【手撕排序3】归并排序

&#x1f343; 本系列包括常见的各种排序算法&#xff0c;如果感兴趣&#xff0c;欢迎订阅&#x1f6a9; &#x1f38a;个人主页:小编的个人主页 &#x1f380; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 ✌️ &#x1f91e; &#x1f91f; &#x1f918; &#x1f91…

Vue中使用Antd中a-table实现表格数据列合并展示

原数据 根据需求实现当前两列数据中有相同数据时,合并列单元格 实现 源码 数据 const dataSource = ref([{id: 1,pl: "冰箱",zznd: "P1",sm: "说明说明说明1",dw: "台",gs: "1",dj: "100"},{id: 1,pl: "冰…

点击评论详情,跳到评论页面,携带对象参数写法:

// 跳转到回复页面去goReply() {第1种方法&#xff0c;跳转并携带参数写法如下&#xff1a;uni.navigateTo({//先把要传过去的item变成字符串形式。但是要注意的是&#xff0c;这样携带字符串形式&#xff0c;是有长度限制的。所以内容多了后&#xff0c;要注意这种方式遗漏内容…

简单的签到程序 python笔记

简单的人脸识别签到程序 在看完以下代码后&#xff0c;略微修改一番&#xff0c;你就能够组装出自己的“简单的人脸识别签到程序”了。 请注意库的安装&#xff0c;否则会不可用。 你可以通过在cmd中使用&#xff1a;pip install来安装。 以下代码运行python 3.8 UI界面 使…

如何利用指纹浏览器爬虫绕过Cloudflare的防护?

网络爬虫能够系统地浏览网页并提取所需的数据&#xff0c;通常被用于市场研究、数据分析或者竞争情报。然而&#xff0c;一些反爬虫机制给网络爬虫的工作带来了不少挑战和风险。 其中&#xff0c;Cloudflare提供了多层次的防护机制&#xff0c;包括IP封锁、速率限制、CAPTCHA验…

数据结构-数组(稀疏矩阵转置)和广义表

目录 1、数组定义 1&#xff09;数组存储地址计算示例①行优先②列优先 2&#xff09;稀疏矩阵的转置三元组顺序表结构定义 ①普通矩阵转置②三元组顺序表转置稀疏矩阵③稀疏矩阵的快速转置 3&#xff09;十字链表结构定义 2、广义表定义 1&#xff09;基本操作①GetHead②GetT…

mysql常见的一些配置项

MySQL 有许多配置选项&#xff0c;可以用来调整其行为以满足特定的需求。以下是一些常见的配置选项&#xff0c;除了大小写敏感之外&#xff0c;这些配置选项也经常被调整&#xff1a; 1. 字符集和排序规则 character_set_server: 设置服务器的默认字符集。collation_server:…