关于后端:Gin复习

7次阅读

共计 4939 个字符,预计需要花费 13 分钟才能阅读完成。

周末想认真理解一下 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 类型

自定义构造体标签

构造体标签是能够随便写的,只有合乎语法规定。然而一些库没有反对该标签的状况下,随便写的标签是没有任何意义的,如果想要咱们的标签变得有意义,就须要咱们提供解析办法。能够通过反射的办法获取标签,上面是一个例子

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 格局组织,例如 <?xml version =”1.0″ encoding=”UTF-8″?><root><key1>value1</key1><key2>

个别状况下,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:

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 多平台公布

正文完
 0