Gin-Gonic

使用Goroutine

当在中间件或 handler 中启动新的 Goroutine 时,不能使用原始的上下文,必须使用只读副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
r := gin.Default()

r.GET("/long_async", func(c *gin.Context) {
// 创建在 goroutine 中使用的副本
cCp := c.Copy()
go func() {
// 用 time.Sleep() 模拟一个长任务。
time.Sleep(5 * time.Second)

// 请注意您使用的是复制的上下文 "cCp",这一点很重要
log.Println("Done! in path " + cCp.Request.URL.Path)
}()
})

绑定Uri

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
route := gin.Default()
route.GET("/:name/:id", func(c *gin.Context) {
var person Person
if err := c.ShouldBindUri(&person); err != nil {
c.JSON(400, gin.H{"msg": err.Error()})
return
}
c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
})
route.Run(":8088")
}

绑定数据到结构体

非嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Person struct {
Name string `form:"name"`
Address string `form:"address"`
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}

func startPage(c *gin.Context) {
var person Person
// 如果是 `GET` 请求,只使用 `Form` 绑定引擎(`query`)。
// 如果是 `POST` 请求,首先检查 `content-type` 是否为 `JSON` 或 `XML`,然后再使用 `Form`(`form-data`)。
// 查看更多:https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88
if c.ShouldBind(&person) == nil {
log.Println(person.Name)
log.Println(person.Address)
log.Println(person.Birthday)
}

c.String(200, "Success")
}

嵌套

仅支持没有 form 的嵌套结构体。

支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type StructA struct {
FieldA string `form:"field_a"`
}

type StructB struct {
NestedStruct StructA
FieldB string `form:"field_b"`
}

type StructC struct {
NestedStructPointer *StructA
FieldC string `form:"field_c"`
}

type StructD struct {
NestedAnonyStruct struct {
FieldX string `form:"field_x"`
}
FieldD string `form:"field_d"`
}

不支持

1
2
3
4
5
6
7
8
9
10
11
type StructX struct {
X struct {} `form:"name_x"` // 有 form
}

type StructY struct {
Y StructX `form:"name_y"` // 有 form
}

type StructZ struct {
Z *StructZ `form:"name_z"` // 有 form
}

自定义中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()

// 设置 example 变量
c.Set("example", "12345")

// 请求前

c.Next()

// 请求后
latency := time.Since(t)
log.Print(latency)

// 获取发送的 status
status := c.Writer.Status()
log.Println(status)
}
}

-------------

r.Use(Logger())

常见中间件

jwt

https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

Base64URL算法将jwt的header、payload加密header里面的加密算法(默认HS256):

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

得到签名,防止篡改数据

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

比较大的缺点:
JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

Gin当中:
当 用户登录 时,根据 用户信息 生成 token码,并将 token码 传递给 前端。
当用户再次发送请求时,请求连接中会包含用户对应的 token码,JWT中间件 会在接收到请求之后自动从 token码 中解析出用户信息,并放入请求的上下文 c 中。

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

var secretKey = []byte("stumap")

type AuthClaims struct {
UserId uint64 `json:"userId"`
jwt.StandardClaims
}

// ParseToken 解析请求头中的 token string,转换成被解析后的 jwt.Token
func ParseToken(tokenStr string) (*jwt.Token, error) {
// 解析 token string 拿到 token jwt.Token
return jwt.ParseWithClaims(tokenStr, &AuthClaims{}, func(tk *jwt.Token) (interface{}, error) {
return secretKey, nil
})
}

func JWTAuth() gin.HandlerFunc {
return func(ctx *gin.Context) {
tokenStr := ctx.GetHeader("Authorization")
if tokenStr == "" {
res := response.BaseResponse{Result: "Permission denied", ResultCode: 0}
ctx.JSON(http.StatusOK, res)
ctx.Abort()
return
}
log.Println("token: ", tokenStr)

token, err := ParseToken(tokenStr)
if err != nil {
res := response.BaseResponse{Result: "Invalid Token.", ResultCode: 0}
ctx.JSON(http.StatusForbidden, res)
ctx.Abort()
return
}

claims, ok := token.Claims.(*AuthClaims)
if !ok {
res := response.BaseResponse{Result: "Invalid Token.", ResultCode: 0}
ctx.JSON(http.StatusForbidden, res)
ctx.Abort()
return
}

ctx.Set("userId", claims.UserId)

ctx.Next()
}
}

Get the Token(When Log in successfully):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/////////TEST GET TOKEN

var expireTime = time.Now().Add(60 * 24 * time.Hour)
claim := AuthClaims{
UserId: 1,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime.Unix(),
IssuedAt: time.Now().Unix(),
Issuer: "stumap",
Subject: "gindemo",
},
}
testNosignedToken := jwt.NewWithClaims(jwt.SigningMethodES256, claim)
testOKToken, err := testNosignedToken.SignedString(secretKey)
if err != nil {
log.Fatal("generate token err: ", err)
}
fmt.Println(testOKToken)