Gin复习

周末想仔细了解一下http传参,包括GET方法的传参、POST方法的传参,索性把GIN也捡起来了一点 ## 基础 **1.Gin.Use和Gin.Default的区别** ``` //Default加了两个中间件 Gin.Use(Logger(),Recovery()) ``` 有打印日志和从Panic中恢复的能力 Logger中间件会将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release Recovery中间件会recover任何panic,如果有panic的话,会写入500响应码 **2.Gin怎么分组** gin.Group对路由进行分组 ## 参数处理 Gin怎么拿到URL中的参数 用ID举例子 **Param** ``` C.Param("id") // 这个时候路由要这么写 id就成为了不定参数 GET("/:id") //这时候url中的请求格式就应该如下 http://localhost:8080/id=xx //如果有多的参数则应该是 http://localhost:8080/id=xx&name=xx GET("/:id/:name") //当我们想处理URL中所有的参数的时候 //比如说有name id type等等 //这个时候路由就该这么写 c.Param("all") GET("/*all") ``` **在URL中传参时必须传某个param该怎么做?** ``` type Produdct struct { ID int `uri:"id" binding:"required" Name string `uri:"name" binding:"required" } // uri也是tag支持的一个键值对之一 var p Product err := c.ShouldBindUri(&p) // 如果这里err有问题了,那么很显然就是某个param没有传 ``` ## struct tag 在很多项目代码里面,很容易看到有一些结构体的定义是类似下面这个结构体的 ``` type Location struct { Longitude float32 `json:"lon,omitempty"` Latitude float32 `json:"lat,omitempty"` } ``` 字段后面会有一个标签,这个标签通常是由反引号给括起来的 Go提供了可通过发射发现的结构体标签,这些在标签库json/xml中得到了广泛的使用,orm框架也支持了结构体标签,上面的这个例子就是因为encoding/json支持json结构体标签,每种标签都有自己的特殊规则 不过所有标签都遵循一个总体规则,这个规则是不能更改的,具体格式如下 ``key1:"value1" key2:"value2" key3:"value3"...`` 结构体标签可以有多个键值对,键与值要用冒号分割,值要使用双引号括起来,多个键值对之间使用一个**空格**进行分割 而一个值中要传递多个信息,不同库的实现是不一样的,在encoding/json中,多值使用逗号进行分割 例如下面的例子 ``json:"lon,omitempty"`` 在gorm中,多值使用分号进行分隔 ``gorm:"column:id;primaryKey"` 结构体标签的具体作用时机如下 **在编译阶段和成员进行关联,以字符串的形式进行关联,在运行阶段可以通过反射读取出来** 在Go项目的编译阶段是不会对struct tag做合法键值对的检查的,如果我们不小心写错了,就会很难被发现,这个时候我们就可以使用`go vet`工具,帮助我们做CI检查 下面是Go支持的struct tag类型 ![image.png](https://upload-images.jianshu.io/upload_images/28955861-4ba1e0a460e4d9ad.png) ![image.png](https://upload-images.jianshu.io/upload_images/28955861-ef95165bde7e94b3.png)![image.png](https://upload-images.jianshu.io/upload_images/28955861-236f55f06842fe9a.png) ## 自定义结构体标签 结构体标签是可以随意写的,只要符合语法规则。但是一些库没有支持该标签的情况下,随意写的标签是没有任何意义的,如果想要我们的标签变得有意义,就需要我们提供解析方法。可以通过反射的方法获取标签,下面是一个例子 ``` type test struct { Name string `cheng:"name"` ID int `cheng:"id"` Type int `cheng:"type"` } func getStructTag(obj interface{}) { t := reflect.TypeOf(obj) for i := 0; i < t.NumField(); i++ { value := t.Field(i) tag := value.Tag.Get("cheng") fmt.Println("get tag is", tag) } } func main() { t := test{ Name: "yuyating", ID: 2021212345, Type: 1, } getStructTag(t) } 输出结果 get tag is name get tag is id get tag is type ``` ## Post-body参数的类型 在POST请求中,常见的body数据类型包括 1. application/x-www-form-urlencoded: 这是最常见的POST请求数据格式,适用于简单的表单数据。在这种格式下,数据以键值对的形式出现,每个键值对之间使用&符号分割,例如:key1=value1&key2=value2 2. multipart/form-data:适用于上传文件或者二进制数据,通常用于文件上传功能。在这种格式下,数据被分割成多个部分,每个部分有自己的Content-Type,例如Content-Type:image/jpeg。这种格式下,数据以一定的边界符分割,每个部分之间以该边界符分割 3. application/json:适用于发送JSON格式的数据,在这种格式下,数据以JSON格式组织,例如{"key1":"value1","key2":"value2"} 4. text/xml: 适用于发送XML格式的数据,在这种格式下,数据以XML格式组织,例如value1 一般情况下,POST请求的body数据类型是需要根据API设计要求而选择的。如果混用不同类型的数据格式,服务器端可能无法正确解析请求的数据,导致请求失败。 ## ShouldBind方法 ShouldBind用于绑定请求中的参数,将其转换成Go结构体或者map类型,该方法的参数类型绑定顺序为 1. 如果是query string,则按照form表单的格式进行解析 2. 如果是post表单数据,则按照form表单的进行解析 3. 如果是json格式,按照json格式解析 4. 如果是xml格式,按照XML格式解析 5. 如果是protobuf格式,按照protobuf格式解析 在绑定参数的时候,Gin会根据请求的Content-Type自动选择核实的参数绑定方式,可以通过ShouldBind方法来完成自动绑定。例如,如果请求的Content-Type为application/json,则Gin会自动将请求体中的JSON数据解析为Go结构体 **为什么query string会按照form表单进行解析呢?form表单不是放在body里的吗?** 虽然form表单数据通常被放在POST请求中的body里面,但是在HTTP请求中,form表单数据也可以以query string的形式出现在url中。在这种情况下,query string中的键值对与form表单中的键值对是相同的,都是由键和值组成的键值对,通过&符号进行分割 因此,Gin在解析query string时会按照form表单的格式进行解析,即将query string中的键值对解析为Go结构体或者Map类型。这样就能够通过Gin的ShouldBind方法统一处理query string和form表单数据,提高了代码复用性和可读性 需要注意的是,在将query string解析为Go结构体或者map类型的时候,需要将URL编码转义的字符进行解码。例如将%20转换为空格。Gin会自动进行这一步操作,不需要手动进行解码。 ## Gin中间件 Gin允许开发者在处理请求的过程中加上自己的钩子函数,这个钩子函数就被称为中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等等。 在JAVA等面向对象编程语言中,面向切面编程(AOP)的思想和中间件是类似的 而拦截器(interceptor)的思想和中间件也是类似的 AOP和MiddleWare、Interceptor都是用于改善软件系统架构的技术,但它们的实现和目标有所不同 相同点 - 都是通过将特定功能从主要业务逻辑中分离出来,以改善系统的可维护性和可扩展性 - 都是在系统中插入特定代码来实现所需功能的(hook) - 都可以提高代码的复用性,减少重复代码的编写 不同点 - AOP关注的是切面,即与业务逻辑无关的横切关注点,如安全性、日志记录、性能检测等等,它们被成为切面,AOP使用依赖注入和动态代理等特定的技术,实现这些切面 - 中间件关注的是不同系统组件之间的通信和交互,是一种软件层,为应用程序提供基础服务,如消息传递、数据传输和远程调用等等 AOP更关注于解决代码层面的问题,中间件则更关注于解决系统层面的问题 拦截器通常只在特定的代码路径或者逻辑流中执行,例如在特定的web请求或者调用特定的方法的时候,通常由程序本身实现,通过代码中的特定注解或配置来声明和使用,旨在通过拦截请求和响应来处理和修改它们,以实现特定的功能,如安全性、性能检测和日志记录等等 使用中间件 ``` //注册全局中间件 c.Use(middleware()) //注册某一条路由的中间件 r.GET("/xxx",MiddleWare(),handler) ``` **注意:在中间件或者handler中启用新的goroutine的时候,不能使用原始的上下文c *gin.Context,必须使用其只读副本c.Copy()** c.Next和c.Abort ``` func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) if finalSize >= int(abortIndex) { // 这里有一个最大限制 panic("too many handlers") } mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers } 一个路由的中间件函数和处理函数结合到一起成为一条处理链条 本质上就是一个由HandlerFunc组成的切片 Next: func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ } } 通过索引遍历HandlersChain链条,从而实现依次调用该路由的每一个函数 Abort: func (c *Context) Abort() { c.index = abortIndex // 直接将索引置为最大限制值,从而退出循环 } ``` Next: ![image.png](https://upload-images.jianshu.io/upload_images/28955861-7be595ed8cdea449.png) Abort: 中断整个调用链条,从当前函数返回 我们的handlers也是HandleFunc类型,所以如果一条路由的专用middleware,调用了c.Next,其实就是直接跳到了handlers中去执行 ## 优雅关机 优雅关机的使用场景,我们不能让一个项目随意的退出,因为这个时候可能还有请求没有处理完,如果你一个信号、或者一个stop按键能够直接让程序停止,那么显然这个项目是不合格的。正确做法是应该处理完所有请求、释放对应资源之后,再停止程序 下面是一个例子 ``` // 把 run 放在子协程中执行 go func() { r.Run() }() // 一个信号通道 exit:=make(chan os.Signal) // 监听通道中有没有这两种信号 signal.Notify(exit,syscall.SIGINT,syscall.SIGTERM) <-exit log.Println("process exit") ``` 本文由[mdnice](https://mdnice.com/?platform=6)多平台发布
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容