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 服务开机启…

Unity3D UI 拖拽

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

基于 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: "冰…

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

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

【Spring】Spring Web MVC基础入门~(含大量例子)

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;什么是Spring Web MVC 1&#xff1a;Servlet 2&#xff1a;总结 二&#xff1a;MVC …

有向图的完全可达性(有向图搜索全路径的问题) C#DFs

在考察输入输出方面我觉得是道难题了 第一次遇见邻接表的数据结构该怎么声明 卡码网105 在力扣没找见完全相同的题 感觉需要多练习多复习这种类型的题 105. 有向图的完全可达性 题目描述 给定一个有向图&#xff0c;包含 N 个节点&#xff0c;节点编号分别为 1&…

登陆页面渗透测试常见的20种思路与总结

【渗透测试】16个实用谷歌浏览器插件分享 飞雪网络安全人才培养计划&#xff0c;绝对零区&#xff0c;公益教学&#xff01; 思路总结 1、之前是否已经留过后门&#xff0c;是&#xff0c;直接getshell&#xff0c;否&#xff0c;进行测试 2、SQL注入&万能密码&#xf…

qt QWebSocketServer详解

1、概述 QWebSocketServer 是 Qt 框架中用于处理 WebSocket 服务器端的类。它允许开发者创建 WebSocket 服务器&#xff0c;接受客户端的连接&#xff0c;并与之进行双向通信。WebSocket 是一种在单个 TCP 连接上进行全双工通讯的协议&#xff0c;它使得客户端和服务器之间的数…

掌握分布式系统的38个核心概念

天天说分布式分布式&#xff0c;那么我们是否知道什么是分布式&#xff0c;分布式会遇到什么问题&#xff0c;有哪些理论支撑&#xff0c;有哪些经典的应对方案&#xff0c;业界是如何设计并保证分布式系统的高可用呢&#xff1f; 1. 架构设计 这一节将从一些经典的开源系统架…

中小跨境卖家如何选择物流?

跨境物流作为电商交易的核心环节&#xff0c;其复杂性和多变性对卖家来说不言而喻。本文将为您详细解析跨境物流的七大流程、常见物流测评以及推荐的工具&#xff0c;帮助您在激烈的市场竞争中把握物流优势&#xff0c;提升业务效率和客户满意度。 跨境物流七大流程 1. 启运国出…

6大国有银行软开的薪资待遇清单

牛客上刷到一条关于计算机专业值得去的银行软开清单,其中对 6 大国有银行软开的薪资待遇分析我觉得很有必要同步给大家看一看。 截图信息来自牛客的漫长白日梦 其中邮储软开是最值得推荐的(offer 投票没输过),二线城市转正后第一个完整年的收入在 30 万左右,一线城市更高…

我们来学mysql -- EXPLAIN之ID(原理篇)

EXPLAIN之ID 题记ID 题记 2024美国大选已定&#xff0c;川普剑登上铁王座&#xff0c;在此过程中出谋划策的幕僚很重要&#xff0c;是他们决定了最终的执行计划在《查询成本之索引选择》中提到&#xff0c;explain的输出&#xff0c;就是优化器&#xff08;幕僚&#xff09;选…

蓝桥杯-网络安全比赛题目-遗漏的压缩包

小蓝同学给你发来了他自己开发的网站链接&#xff0c; 他说他故意留下了一个压缩包文件&#xff0c;里面有网站的源代码&#xff0c; 他想考验一下你的网络安全技能。 &#xff08;点击“下发赛题”后&#xff0c;你将得到一个http链接。如果该链接自动跳转到https&#xff0c;…