Gin

环境:https://goproxy.cn,driect

github.com/gin-gonic/gin

介绍

Gin 是一个用 Go (Golang) 编写的 Web 框架。 它具有类似 martini 的 API,性能要好得多,多亏了 httprouter,速度提高了 40 倍。 如果您需要性能和良好的生产力,您一定会喜欢 Gin。

在本节中,我们将介绍 Gin 是什么,它解决了哪些问题,以及它如何帮助你的项目。

或者, 如果你已经准备在项目中使用 Gin,请访问快速入门.

源码分析

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

实现

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   c := engine.pool.Get().(*Context)
   c.writermem.reset(w)
   c.Request = req
   c.reset()

   engine.handleHTTPRequest(c)

   engine.pool.Put(c)
}
  • 通过对象池来减少内存申请和GC回收的消耗
  • 取出来要用的时候再初始化

gin

特性

快速

基于 Radix 树的路由,小内存占用。没有反射。可预测的 API 性能。

支持中间件

传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如:Logger,Authorization,GZIP,最终操作 DB。

Crash 处理

Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。例如,你可以向 Sentry 报告这个 panic!

JSON 验证

Gin 可以解析并验证请求的 JSON,例如检查所需值的存在。

路由组

更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能。

错误管理

Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。

内置渲染

Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API。

可扩展性

新建一个中间件非常简单,去查看示例代码吧。

快速入门

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run(":8000")// 监听并在 0.0.0.0:8080 上启动服务
}
  • gin.Default()默认使用了LoggerRecover中间件
  • Logger是负责进行打印输出日志的中间件,方便开发者进行程序的调试
  • Recover如果程序执行过程中遇到了panic中断了服务,则Recover会恢复程序的运行并返回500的内部错误。

Engine

type Engine struct {
	RouterGroup
	RedirectTrailingSlash bool
	RedirectFixedPath bool
	HandleMethodNotAllowed bool
	ForwardedByClientIP bool
	AppEngine bool
	UseRawPath bool
	UnescapePathValues bool
	RemoveExtraSlash bool
	RemoteIPHeaders []string
	TrustedPlatform string
	MaxMultipartMemory int64
	delims           render.Delims
	secureJSONPrefix string
	HTMLRender       render.HTMLRender
	FuncMap          template.FuncMap
	allNoRoute       HandlersChain
	allNoMethod      HandlersChain
	noRoute          HandlersChain
	noMethod         HandlersChain
	pool             sync.Pool
	trees            methodTrees
	maxParams        uint16
	maxSections      uint16
	trustedProxies   []string
	trustedCIDRs     []*net.IPNet
}

请求处理

HTTP 协议的 8 种请求类型介绍

HTTP 协议中共定义了八种方法或者叫“动作”来表明对Request-URI指定的资源的不同操作方式,具体介绍如下:

  • OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送'*'的请求来测试服务器的功能性。
  • HEAD:向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。
  • GET:向特定的资源发出请求。
  • POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。
  • PUT:向指定资源位置上传其最新内容。
  • DELETE:请求服务器删除 Request-URI 所标识的资源。
  • TRACE:回显服务器收到的请求,主要用于测试或诊断。
  • CONNECT:HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。

通用处理

Handle方法

func (group *RouterGroup)Handle(httpMethod,relativePath string,handler...Handler)
  • httpMethod:表示要处理的HTTP请求类型,8种请求方式之一
  • relativePath:表示要解析的接口,由开发者定义
  • handlers:处理对应的请求的代码定义

Restful风格的API

  • gin支持Restful风格的API
  • 即Representational State Transfer的缩写。直接翻译的意思是”表现层状态转化”,是一种互联网应用程序的API设计理念:URL定位资源,用HTTP描述操作

1.获取文章 /blog/getXxx Get blog/Xxx

2.添加 /blog/addXxx POST blog/Xxx

3.修改 /blog/updateXxx PUT blog/Xxx

4.删除 /blog/delXxxx DELETE blog/Xxx

GET请求处理

路径参数- /:xx - Param("xx")

对于类似这样的请求:http://127.0.0.1:8080/index/12,那么如何获取最后路径中12的值呢?

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
)

func Index(ctx *gin.Context) {
    id := ctx.Param("id")
    fmt.Println(id)
    ctx.String(http.StatusOK, "success!")
}

func main() {
    router := gin.Default()
    
    // 路径参数获取,如:http://127.0.0.1:8080/index/12,获取12
    router.GET("/index/:id", Index)

    router.Run(":8080")
}

在挂载路由时需要通过":"来进行匹配,然后在视图函数中通过ctx.Param方法获取。

查询参数-/hello?xx=..? -Query()

对于类似这样的请求:http://127.0.0.1:8080/index1?id=12,那么如何获取最后路径中12的值呢?

1、ctx.Query

传参:http://127.0.0.1:8080/index1?id=12

路由:router.GET("/index1", Index1)

视图函数获取:ctx.Query("id")

2、ctx.DefaultQuery

传参:http://127.0.0.1:8080/index2

路由:router.GET("/index2", Index2)

视图函数获取:ctx.DefaultQuery("id", "0")

如果没有获取到id,就得到默认值0.

3、ctx.QueryArray

传参:http://127.0.0.1:8080/index3?id=1,2,3,4,5

路由:router.GET("/index3", Index3)

视图函数获取:ctx.QueryArray("id")

4、ctx.QueryMap

传参:http://127.0.0.1:8080/index4?user[name]="lily"&user[age]=15

路由:router.GET("/index4", Index4)

视图函数获取:ctx.QueryMap("user")

通用参数匹配 /user/:name/*action

当我们需要动态参数的路由时,如 /user/:id,通过调用不同的参数 :id 动态获取相应的用户信息。其中 /user/:id/*type*type 的参数为可选。

package main

import (
    "net/http"
    "strings"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.GET("/user/:name/*action", func(c *gin.Context) {
        name := c.Param("name")
        action := c.Param("action")
        //截取/
        action = strings.Trim(action, "/")
        c.String(http.StatusOK, name+" is "+action)
    })
    //默认为监听8080端口
    r.Run(":8000")
}

正常的结果:

http://localhost:8080/api/hjz/HJZ114152
hjz is HJZ114152

注释掉:Trim()后

http://localhost:8080/api/hjz/HJZ114152
hjz is /HJZ114152

POST请求处理

表单传输为post请求,http常见的传输格式为四种:

  • application/json
  • application/x-www-form-urlencoded
  • application/xml
  • multipart/form-data

表单参数可以通过PostForm()方法获取,该方法默认解析的是x-www-form-urlencoded或from-data格式的参数

普通方式提交表单-post -PostForm("xxx")

1、ctx.PostForm

  • 表单
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/post_index" method="post">
    <p>用户名:<input type="text" name="username"></p>
    <p>密 码:<input type="password" name="password"></p>
    <p><input type="submit"></p>
</form>
</body>
</html>
  • 后台处理
...
func PostIndex(ctx *gin.Context)  {
    username := ctx.PostForm("username")
    password := ctx.PostForm("password")
    fmt.Printf("用户名:%s, 密码:%s", username, password)
    ctx.String(http.StatusOK, "提交成功!")
}
...

2、ctx.PostFormMap

  • 表单
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/post_index1" method="post">
    <p>用户名:<input type="text" name="user[username]"></p>
    <p>密 码:<input type="password" name="user[password]"></p>
    <p><input type="submit"></p>
</form>
</body>
</html>
  • 后台处理
...
func PostIndex1(ctx *gin.Context)  {
    userMap := ctx.PostFormMap("user")
    fmt.Println(userMap)
    ctx.String(http.StatusOK, "提交成功!")
}
...

3、ctx.DefaultPostForm、ctx.PostFormArray

  • 表单
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/post_index2" method="post">
    <p>用户名:<input type="text" name="username"></p>
    <p>密 码:<input type="password" name="password"></p>
    <p>爱 好:
        读书<input type="checkbox" name="hobby" value="1">
        看电影<input type="checkbox" name="hobby" value="2">
        音乐<input type="checkbox" name="hobby" value="3">
    </p>
    <p><input type="submit"></p>
</form>
</body>
</html>
  • 后台处理
...
func PostIndex2(ctx *gin.Context)  {
    username := ctx.PostForm("username")
    password := ctx.PostForm("password")
    age := ctx.DefaultPostForm("age", "0")
    hobby := ctx.PostFormArray("hobby")
    fmt.Printf("用户名:%s, 密码:%s, 年龄:%s, 爱好:%s", username, password, age, hobby)
    ctx.String(http.StatusOK, "提交成功!")
}
...

例子

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
)

func GetIndex(ctx *gin.Context) {
    ctx.HTML(http.StatusOK, "index.html", nil)
}

func PostIndex(ctx *gin.Context) {
    username := ctx.PostForm("username")
    password := ctx.PostForm("password")
    fmt.Printf("用户名:%s, 密码:%s", username, password)
    ctx.String(http.StatusOK, "提交成功!")
}

func GetIndex1(ctx *gin.Context) {
    ctx.HTML(http.StatusOK, "index1.html", nil)
}

func PostIndex1(ctx *gin.Context) {
    userMap := ctx.PostFormMap("user")
    fmt.Println(userMap)
    ctx.String(http.StatusOK, "提交成功!")
}

func GetIndex2(ctx *gin.Context) {
    ctx.HTML(http.StatusOK, "index2.html", nil)
}

func PostIndex2(ctx *gin.Context) {
    username := ctx.PostForm("username")
    password := ctx.PostForm("password")
    age := ctx.DefaultPostForm("age", "0")
    hobby := ctx.PostFormArray("hobby")
    fmt.Printf("用户名:%s, 密码:%s, 年龄:%s, 爱好:%s", username, password, age, hobby)
    ctx.String(http.StatusOK, "提交成功!")
}

func main() {
    router := gin.Default()

    router.LoadHTMLGlob("template/*")

    // ctx.PostForm
    router.GET("/get_index", GetIndex)
    router.POST("/post_index", PostIndex)
    // ctx.PostFormMap
    router.GET("/get_index1", GetIndex1)
    router.POST("/post_index1", PostIndex1)
    // ctx.DefaultPostForm、ctx.PostFormArray
    router.GET("/get_index2", GetIndex2)
    router.POST("/post_index2", PostIndex2)

    router.Run(":8080")
}

Ajax方式提交表单

ajax的后台处理逻辑与普通的表单的提交的处理方式基本相同,只不过在返回的时候需要返回json数据,前台使用回调函数进行处理。

  • 前台
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/js/jquery-3.6.0.js"></script>
</head>
<body>

<form>
    <p>用户名:<input id="username" type="text"></p>
    <p>密码:<input id="password" type="password"></p>
    <p><input type="button" value="提交" id="btn_submit"></p>
</form>

<script>
    var btn = document.getElementById("btn_submit")
    btn.onclick = function (ev) {
        var username = document.getElementById("username").value
        var password = document.getElementById("password").value

        $.ajax({
            url: '/post_index',
            type: 'POST',
            data: {
                username: username,
                password: password
            },
            success: function (data) {
                alert(data) // 响应成功的回调函数
            },
            fail: function (data) {

            }
        })
    }


</script>
</body>
</html>
  • 后台
package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
)

func GetIndex(ctx *gin.Context) {
    ctx.HTML(http.StatusOK, "index.html", nil)
}

func PostIndex(ctx *gin.Context) {
    username := ctx.PostForm("username")
    password := ctx.PostForm("password")
    fmt.Println(username, password)
    data := map[string]interface{}{
        "code":    2000,
        "message": "成功",
    }
    ctx.JSON(http.StatusOK, data)
}

func main() {
    router := gin.Default()

    router.LoadHTMLGlob("template/*")
    router.Static("/static", "static")

    router.GET("/get_index", GetIndex)
    router.POST("/post_index", PostIndex)

    router.Run(":8080")
}

参数绑定

无论时get请求的参数还是post请求的请求体,在后台都需要通过对应的方法来获取对应参数的值,那么有没有一种方式能够让我们定义好请求数据的格式,然后自动进行获取,这里可以通过参数绑定的方式来进行处理。它能够基于请求自动提取JSON、form表单和QueryString类型的数据,并把值绑定到指定的结构体对象

这里以get请求的查询参数为例:

  • 请求格式
http://127.0.0.1:8080/index?username=%22llkk%22&password=%22123%22
  • 后台处理
package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
)

type User struct {
    Username string `form:"username" json:"username"`
    Password string `form:"password" json:"password"`
}

func Index(ctx *gin.Context) {
    var user User
    err := ctx.ShouldBind(&user)
    fmt.Println(err)
    fmt.Println(user.Username, user.Password) // "llkk" "123"

    ctx.String(http.StatusOK, "success")

}

func main() {
    router := gin.Default()

    router.GET("/index", Index)

    router.Run(":8080")
}

文件处理

上传单个文件

  • multipart/form-data格式用于文件上传
  • gin文件上传与原生的net/http方法类似,不同在于gin把原生的request封装到c.Request中
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
          上传文件:<input type="file" name="file" >
          <input type="submit" value="提交">
    </form>
</body>
</html>
package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    //限制上传最大尺寸
    r.MaxMultipartMemory = 8 << 20
    r.POST("/upload", func(c *gin.Context) {
        file, err := c.FormFile("file")
        if err != nil {
            c.String(500, "上传图片出错")
        }
        // c.JSON(200, gin.H{"message": file.Header.Context})
        c.SaveUploadedFile(file, file.Filename)
        c.String(http.StatusOK, file.Filename)
    })
    r.Run()
}

上传多个文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <form action="http://localhost:8000/upload" method="post" enctype="multipart/form-data">
          上传文件:<input type="file" name="files" multiple>
          <input type="submit" value="提交">
    </form>
</body>
</html>
package main

import (
   "github.com/gin-gonic/gin"
   "net/http"
   "fmt"
)

// gin的helloWorld

func main() {
   // 1.创建路由
   // 默认使用了2个中间件Logger(), Recovery()
   r := gin.Default()
   // 限制表单上传大小 8MB,默认为32MB
   r.MaxMultipartMemory = 8 << 20
   r.POST("/upload", func(c *gin.Context) {
      form, err := c.MultipartForm()
      if err != nil {
         c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
      }
      // 获取所有图片
      files := form.File["files"]
      // 遍历所有图片
      for _, file := range files {
         // 逐个存
         if err := c.SaveUploadedFile(file, file.Filename); err != nil {
            c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))
            return
         }
      }
      c.String(200, fmt.Sprintf("upload ok %d files", len(files)))
   })
   //默认端口号是8080
   r.Run(":8000")
}

快速分析

GET

get请求的参数会放在地址栏里,用明文的形式提交给后台 .

例如:

http://127.0.0.1:8081/path/123 
r.GET("/path/:id", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"success": true,
		})
	})

获得URL里面的参数

http://127.0.0.1:8081/path/123?user=abc&pwd=123456
r.GET("/path/:id", func(c *gin.Context) {
		id := c.Param("id") //获取占位表达式后面的参数
		//user和pwd放在地址栏后面,所以叫query传参
		user := c.Query("user") //
		pwd := c.Query("pwd")
		c.JSON(200, gin.H{
			"id":   id,
			"user": user,
			"pwd":  pwd,
		})
	})

设置一个参数的默认值

r.GET("/path/:id", func(c *gin.Context) {
		id := c.Param("id") //获取占位表达式后面的参数
		//user和pwd放在地址栏后面,所以叫query传参
		user := c.DefaultQuery("user", "kaka") //设置user的默认值为kaka
		pwd := c.Query("pwd")
		c.JSON(200, gin.H{
			"id":   id,
			"user": user,
			"pwd":  pwd,
		})
	})

POST

post的请求参数放在form或者body里,form即表单,body是以当前最流行的json格式进行交互。

r.POST("/path", func(c *gin.Context) {
		user := c.DefaultPostForm("user", "aaa")
		pwd := c.PostForm("pwd")
		c.JSON(200, gin.H{
			"user": user,
			"pwd":  pwd,
		})
	})

Delete

delete请求一般为uri,同样也可以用body

r.DELETE("/path/:id", func(c *gin.Context) {
		id := c.Param("id") //获取占位表达式后面的参数
		c.JSON(200, gin.H{
			"id": id,
		})
	})

delete请求实际工作中用的不多,传参和取参方法和get类似

PUT

参数在form、body或者uri里

r.PUT("/path", func(c *gin.Context) {
		user := c.DefaultPostForm("user", "aaa")
		pwd := c.PostForm("pwd")
		c.JSON(200, gin.H{
			"user": user,
			"pwd":  pwd,
		})
	})

put传参和获取参数的方法和post基本一样

返回数据

[]byte

...
engine := gin.Defaut()
engine.GET("/hello",func(context *gin.Context){
    fullPath := "请求路径: "+context.FullPath()
    fmt.Println(fullPath)
    context.Writer.Writer([]byte(fullPath))
})
engine.Run()
...

调用了http.ResponseWriter中包含的方法

string

...
engine := gin.Defaut()
engine.GET("/hello",func(context *gin.Context){
    fullPath := "请求路径: "+context.FullPath()
    fmt.Println(fullPath)
    context.Writer.WriterString([]byte(fullPath))
})
engine.Run()
...

JSON

map

...
engine := gin.Defaut()
engine.GET("/hello",func(context *gin.Context){
    fullPath := "请求路径: "+context.FullPath()
    fmt.Println(fullPath)
    context.JSON(200,map[string]interface{}{
        "code":1,
        "message":"OK",
        "data":FullPath
    })
})
engine.Run()
...

结构体

type Resopnse type{
    Code int		`json:"code"`
    Message string 	 `json:"message"`
    Data interface{} `json:"data"`
}
...
engine := gin.Defaut()
engine.GET("/hello",func(context *gin.Context){
    fullPath := "请求路径: "+context.FullPath()
    fmt.Println(fullPath)
    context.JSON(200,&Resopnse{
        Code:1,
        Message:"OK",
        Data:FullPath
    })
})
engine.Run()
...

数据解析

Json 数据解析和绑定

ShouldBindJSON("&xx")

  • 客户端传参,后端接收并解析到结构体
package main

import (
   "github.com/gin-gonic/gin"
   "net/http"
)

// 定义接收数据的结构体
type Login struct {
   // binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
   User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
   Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}

func main() {
   // 1.创建路由
   // 默认使用了2个中间件Logger(), Recovery()
   r := gin.Default()
   // JSON绑定
   r.POST("loginJSON", func(c *gin.Context) {
      // 声明接收的变量
      var json Login
      // 将request的body中的数据,自动按照json格式解析到结构体
      if err := c.ShouldBindJSON(&json); err != nil {
         // 返回错误信息
         // gin.H封装了生成json数据的工具
         c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
         return
      }
      // 判断用户名密码是否正确
      if json.User != "root" || json.Pssword != "admin" {
         c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
         return
      }
      c.JSON(http.StatusOK, gin.H{"status": "200"})
   })
   r.Run(":8000")
} 

表单数据解析和绑定

Bind("&xx")

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <form action="http://localhost:8000/loginForm" method="post" enctype="application/x-www-form-urlencoded">
        用户名<input type="text" name="username"><br>
        密码<input type="password" name="password">
        <input type="submit" value="提交">
    </form>
</body>
</html>
package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

// 定义接收数据的结构体
type Login struct {
    // binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
    User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
    Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}

func main() {
    // 1.创建路由
    // 默认使用了2个中间件Logger(), Recovery()
    r := gin.Default()
    // JSON绑定
    r.POST("/loginForm", func(c *gin.Context) {
        // 声明接收的变量
        var form Login
        // Bind()默认解析并绑定form格式
        // 根据请求头中content-type自动推断
        if err := c.Bind(&form); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        // 判断用户名密码是否正确
        if form.User != "root" || form.Pssword != "admin" {
            c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
            return
        }
        c.JSON(http.StatusOK, gin.H{"status": "200"})
    })
    r.Run(":8000")
}

URI数据解析和绑定

ShouldBindUri()

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

// 定义接收数据的结构体
type Login struct {
    // binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
    User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
    Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}

func main() {
    // 1.创建路由
    // 默认使用了2个中间件Logger(), Recovery()
    r := gin.Default()
    // JSON绑定
    r.GET("/:user/:password", func(c *gin.Context) {
        // 声明接收的变量
        var login Login
        // Bind()默认解析并绑定form格式
        // 根据请求头中content-type自动推断
        if err := c.ShouldBindUri(&login); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        // 判断用户名密码是否正确
        if login.User != "root" || login.Pssword != "admin" {
            c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
            return
        }
        c.JSON(http.StatusOK, gin.H{"status": "200"})
    })
    r.Run(":8000")
}

视图渲染

各种数据格式的响应

  • json、结构体、XML、YAML类似于java的properties、ProtoBuf

JSON

    // 1.json
    r.GET("/someJSON", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "someJSON", "status": 200})
    })

结构体

// 2. 结构体响应
    r.GET("/someStruct", func(c *gin.Context) {
        var msg struct {
            Name    string
            Message string
            Number  int
        }
        msg.Name = "root"
        msg.Message = "message"
        msg.Number = 123
        c.JSON(200, msg)
    })

XML

// 3.XML
    r.GET("/someXML", func(c *gin.Context) {
        c.XML(200, gin.H{"message": "abc"})
    })

YAML

    // 4.YAML响应
    r.GET("/someYAML", func(c *gin.Context) {
        c.YAML(200, gin.H{"name": "zhangsan"})
    })

protoduf

 // 5.protobuf格式,谷歌开发的高效存储读取的工具
    // 数组?切片?如果自己构建一个传输格式,应该是什么格式?
    r.GET("/someProtoBuf", func(c *gin.Context) {
        reps := []int64{int64(1), int64(2)}
        // 定义数据
        label := "label"
        // 传protobuf格式数据
        data := &protoexample.Test{
            Label: &label,
            Reps:  reps,
        }
        c.ProtoBuf(200, data)
    })

Gin框架使用HTML模板渲染

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.LoadHTMLGlob("./templates/*")
	r.GET("/demo", func(ctx *gin.Context) {
		ctx.HTML(http.StatusOK, "test.html", gin.H{
			"name": "admin",
			"pwd":  "123456",
		})
	})
	r.Run()
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    账号是:{{.name}}<br>
    密码是:{{.pwd}}
</body>
</html>

重定向

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.GET("/index", func(c *gin.Context) {
        c.Redirect(http.StatusMovedPermanently, "http://www.5lmh.com")
    })
    r.Run()
}

同步异步

  • goroutine机制可以方便地实现异步处理
  • 另外,在启动新的goroutine时,不应该使用原始上下文,必须使用它的只读副本
package main

import (
    "log"
    "time"

    "github.com/gin-gonic/gin"
)

func main() {
    // 1.创建路由
    // 默认使用了2个中间件Logger(), Recovery()
    r := gin.Default()
    // 1.异步
    r.GET("/long_async", func(c *gin.Context) {
        // 需要搞一个副本
        copyContext := c.Copy()
        // 异步处理
        go func() {
            time.Sleep(3 * time.Second)
            log.Println("异步执行:" + copyContext.Request.URL.Path)
        }()
    })
    // 2.同步
    r.GET("/long_sync", func(c *gin.Context) {
        time.Sleep(3 * time.Second)
        log.Println("同步执行:" + c.Request.URL.Path)
    })

    r.Run(":8000")
} 

日志的打印上:

[GIN] 2022/08/01 - 10:48:56 | 200 |            0s |             ::1 | GET      "/long_async"
2022/08/01 10:48:59 异步执行:/long_async
2022/08/01 10:49:17 同步执行:/long_sync
[GIN] 2022/08/01 - 10:49:17 | 200 |    3.0071153s |             ::1 | GET      "/long_sync"

中间件

全局中间件

  • 所有请求都经过此中间件
package main

import (
    "fmt"
    "time"

    "github.com/gin-gonic/gin"
)

// 定义中间
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("中间件开始执行了")
        // 设置变量到Context的key中,可以通过Get()取
        c.Set("request", "中间件")
        status := c.Writer.Status()
        fmt.Println("中间件执行完毕", status)
        t2 := time.Since(t)
        fmt.Println("time:", t2)
    }
}

func main() {
    // 1.创建路由
    // 默认使用了2个中间件Logger(), Recovery()
    r := gin.Default()
    // 注册中间件
    r.Use(MiddleWare())
    // {}为了代码规范
    {
        r.GET("/ce", func(c *gin.Context) {
            // 取值
            req, _ := c.Get("request")
            fmt.Println("request:", req)
            // 页面接收
            c.JSON(200, gin.H{"request": req})
        })

    }
    r.Run()
}
中间件开始执行了
中间件执行完毕 200
time: 516.1µs
request: 中间件
[GIN] 2022/10/02 - 14:56:20 | 200 |       516.1µs |             ::1 | GET      "/ce"

Next()方法

package main

import (
    "fmt"
    "time"

    "github.com/gin-gonic/gin"
)

// 定义中间
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("中间件开始执行了")
        // 设置变量到Context的key中,可以通过Get()取
        c.Set("request", "中间件")
        // 执行函数
        c.Next()
        // 中间件执行完后续的一些事情
        status := c.Writer.Status()
        fmt.Println("中间件执行完毕", status)
        t2 := time.Since(t)
        fmt.Println("time:", t2)
    }
}

func main() {
    // 1.创建路由
    // 默认使用了2个中间件Logger(), Recovery()
    r := gin.Default()
    // 注册中间件
    r.Use(MiddleWare())
    // {}为了代码规范
    {
        r.GET("/ce", func(c *gin.Context) {
            // 取值
            req, _ := c.Get("request")
            fmt.Println("request:", req)
            // 页面接收
            c.JSON(200, gin.H{"request": req})
        })

    }
    r.Run()
}
中间件开始执行了
request: 中间件   
中间件执行完毕 200
time: 235.9µs     
[GIN] 2022/10/02 - 14:57:46 | 200 |       754.8µs |             ::1 | GET      "/ce"

局部中间件

package main

import (
    "fmt"
    "time"

    "github.com/gin-gonic/gin"
)

// 定义中间
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("中间件开始执行了")
        // 设置变量到Context的key中,可以通过Get()取
        c.Set("request", "中间件")
        // 执行函数
        c.Next()
        // 中间件执行完后续的一些事情
        status := c.Writer.Status()
        fmt.Println("中间件执行完毕", status)
        t2 := time.Since(t)
        fmt.Println("time:", t2)
    }
}

func main() {
    // 1.创建路由
    // 默认使用了2个中间件Logger(), Recovery()
    r := gin.Default()
    //局部中间键使用
    r.GET("/ce", MiddleWare(), func(c *gin.Context) {
        // 取值
        req, _ := c.Get("request")
        fmt.Println("request:", req)
        // 页面接收
        c.JSON(200, gin.H{"request": req})
    })
    r.Run()
}

在中间件中使用Goroutine

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

package main

import (
	"github.com/gin-gonic/gin"
	"log"
	"time"
)

func main() {
	r := gin.Default()

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

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

	r.GET("/long_sync", func(c *gin.Context) {
		// 用 time.Sleep() 模拟一个长任务。
		time.Sleep(5 * time.Second)

		// 因为没有使用 goroutine,不需要拷贝上下文
		log.Println("Done! in path " + c.Request.URL.Path)
	})
	r.Run()
}

会话控制

Cookie介绍

  • HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出
  • Cookie就是解决HTTP协议无状态的方案之一,中文是小甜饼的意思
  • Cookie实际上就是服务器保存在浏览器上的一段信息。浏览器有了Cookie之后,每次向服务器发送请求时都会同时将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求
  • Cookie由服务器创建,并发送给浏览器,最终由浏览器保存

Cookie的用途

  • 测试服务端发送cookie给客户端,客户端请求时携带cookie

Cookie的使用

  • 测试服务端发送cookie给客户端,客户端请求时携带cookie
package main

import (
   "github.com/gin-gonic/gin"
   "fmt"
)

func main() {
   // 1.创建路由
   // 默认使用了2个中间件Logger(), Recovery()
   r := gin.Default()
   // 服务端要给客户端cookie
   r.GET("cookie", func(c *gin.Context) {
      // 获取客户端是否携带cookie
      cookie, err := c.Cookie("key_cookie")
      if err != nil {
         cookie = "NotSet"
         // 给客户端设置cookie
         //  maxAge int, 单位为秒
         // path,cookie所在目录
         // domain string,域名
         //   secure 是否智能通过https访问
         // httpOnly bool  是否允许别人通过js获取自己的cookie
         c.SetCookie("key_cookie", "value_cookie", 60, "/",
            "localhost", false, true)
      }
      fmt.Printf("cookie的值是: %s\n", cookie)
   })
   r.Run(":8000")
}

Gin设置日志

在Gin框架中记录日志方法如下

package main

import (
	"io"
	"os"
	"github.com/gin-gonic/gin"
)

func main() {
	// 禁用控制台颜色,将日志写入文件时不需要控制台颜色。
	gin.DisableConsoleColor()

	// 记录到文件。
	f, _ := os.Create("gin.log")
	gin.DefaultWriter = io.MultiWriter(f)

	// 如果需要同时将日志写入文件和控制台,请使用以下代码。
	// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})

	r.Run()
}

以上代码执行结果如下动画

Cookie的联系

  • 模拟实现权限验证中间件
    • 有2个路由,login和home
    • login用于设置cookie
    • home是访问查看信息的请求
    • 在请求home之前,先跑中间件代码,检验是否存在cookie
  • 访问home,会显示错误,因为权限校验未通过
package main

import (
   "github.com/gin-gonic/gin"
   "net/http"
)

func AuthMiddleWare() gin.HandlerFunc {
   return func(c *gin.Context) {
      // 获取客户端cookie并校验
      if cookie, err := c.Cookie("abc"); err == nil {
         if cookie == "123" {
            c.Next()
            return
         }
      }
      // 返回错误
      c.JSON(http.StatusUnauthorized, gin.H{"error": "err"})
      // 若验证不通过,不再调用后续的函数处理
      c.Abort()
      return
   }
}

func main() {
   // 1.创建路由
   r := gin.Default()
   r.GET("/login", func(c *gin.Context) {
      // 设置cookie
      c.SetCookie("abc", "123", 60, "/",
         "localhost", false, true)
      // 返回信息
      c.String(200, "Login success!")
   })
   r.GET("/home", AuthMiddleWare(), func(c *gin.Context) {
      c.JSON(200, gin.H{"data": "home"})
   })
   r.Run(":8000")
}

Cookie的缺点

  • 不安全,明文
  • 增加带宽消耗
  • 可以被禁用
  • cookie有上限

Sessions

gorilla/sessions为自定义session后端提供cookie和文件系统session以及基础结构。

主要功能是:

  • 简单的API:将其用作设置签名(以及可选的加密)cookie的简便方法。
  • 内置的后端可将session存储在cookie或文件系统中。
  • Flash消息:一直持续读取的session值。
  • 切换session持久性(又称“记住我”)和设置其他属性的便捷方法。
  • 旋转身份验证和加密密钥的机制。
  • 每个请求有多个session,即使使用不同的后端也是如此。
  • 自定义session后端的接口和基础结构:可以使用通用API检索并批量保存来自不同商店的session。

代码:

package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/sessions"
)

// 初始化一个cookie存储对象
// something-very-secret应该是一个你自己的密匙,只要不被别人知道就行
var store = sessions.NewCookieStore([]byte("something-very-secret"))

func main() {
    http.HandleFunc("/save", SaveSession)
    http.HandleFunc("/get", GetSession)
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        fmt.Println("HTTP server failed,err:", err)
        return
    }
}

func SaveSession(w http.ResponseWriter, r *http.Request) {
    // Get a session. We're ignoring the error resulted from decoding an
    // existing session: Get() always returns a session, even if empty.

    // 获取一个session对象,session-name是session的名字
    session, err := store.Get(r, "session-name")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // 在session中存储值
    session.Values["foo"] = "bar"
    session.Values[42] = 43
    // 保存更改
    session.Save(r, w)
}
func GetSession(w http.ResponseWriter, r *http.Request) {
    session, err := store.Get(r, "session-name")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    foo := session.Values["foo"]
    fmt.Println(foo)
}

删除session的值:

    // 删除
    // 将session的最大存储时间设置为小于零的数即为删除
    session.Options.MaxAge = -1
    session.Save(r, w)

参数验证

结构体验证

用gin框架的数据验证,可以不用解析数据,减少if else,会简洁许多。

package main

import (
    "fmt"
    "time"

    "github.com/gin-gonic/gin"
)

//Person ..
type Person struct {
    //不能为空并且大于10
    Age      int       `form:"age" binding:"required,gt=10"`
    Name     string    `form:"name" binding:"required"`
    Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}

func main() {
    r := gin.Default()
    r.GET("/5lmh", func(c *gin.Context) {
        var person Person
        if err := c.ShouldBind(&person); err != nil {
            c.String(500, fmt.Sprint(err))
            return
        }
        c.String(200, fmt.Sprintf("%#v", person))
    })
    r.Run()
}

自定义验证

都在代码里自己看吧

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
    "gopkg.in/go-playground/validator.v9"
)

/*
    对绑定解析到结构体上的参数,自定义验证功能
    比如我们需要对URL的接受参数进行判断,判断用户名是否为root如果是root通过否则返回false
*/
type Login struct {
    User    string `uri:"user" validate:"checkName"`
    Pssword string `uri:"password"`
}

// 自定义验证函数
func checkName(fl validator.FieldLevel) bool {
    if fl.Field().String() != "root" {
        return false
    }
    return true
}
func main() {
    r := gin.Default()
    validate := validator.New()
    r.GET("/:user/:password", func(c *gin.Context) {
        var login Login
         //注册自定义函数,与struct tag关联起来
        err := validate.RegisterValidation("checkName", checkName)
        if err := c.ShouldBindUri(&login); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        err = validate.Struct(login)
        if err != nil {
            for _, err := range err.(validator.ValidationErrors) {
                fmt.Println(err)
            }
            return
        }
        fmt.Println("success")
    })
    r.Run()
}

示例2:

package main

import (
    "net/http"
    "reflect"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "gopkg.in/go-playground/validator.v8"
)

// Booking contains binded and validated data.
type Booking struct {
    //定义一个预约的时间大于今天的时间
    CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
    //gtfield=CheckIn退出的时间大于预约的时间
    CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}

func bookableDate(
    v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
    field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
    //field.Interface().(time.Time)获取参数值并且转换为时间格式
    if date, ok := field.Interface().(time.Time); ok {
        today := time.Now()
        if today.Unix() > date.Unix() {
            return false
        }
    }
    return true
}

func main() {
    route := gin.Default()
    //注册验证
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        //绑定第一个参数是验证的函数第二个参数是自定义的验证函数
        v.RegisterValidation("bookabledate", bookableDate)
    }

    route.GET("/5lmh", getBookable)
    route.Run()
}

func getBookable(c *gin.Context) {
    var b Booking
    if err := c.ShouldBindWith(&b, binding.Query); err == nil {
        c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
    } else {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    }
}

// curl -X GET "http://localhost:8080/5lmh?check_in=2019-11-07&check_out=2019-11-20"
// curl -X GET "http://localhost:8080/5lmh?check_in=2019-09-07&check_out=2019-11-20"
// curl -X GET "http://localhost:8080/5lmh?check_in=2019-11-07&check_out=2019-11-01"

自定义验证v10

介绍

Validator 是基于 tag(标记)实现结构体和单个字段的值验证库,它包含以下功能:

  • 使用验证 tag(标记)或自定义验证器进行跨字段和跨结构体验证。
  • 关于 slice、数组和 map,允许验证多维字段的任何或所有级别。
  • 能够深入 map 键和值进行验证。
  • 通过在验证之前确定接口的基础类型来处理类型接口。
  • 处理自定义字段类型(如 sql 驱动程序 Valuer)。
  • 别名验证标记,它允许将多个验证映射到单个标记,以便更轻松地定义结构体上的验证。
  • 提取自定义的字段名称,例如,可以指定在验证时提取 JSON 名称,并在生成的 FieldError 中使用该名称。
  • 可自定义 i18n 错误消息。
  • Web 框架 gin 的默认验证器。

安装:

使用 go get:

go get github.com/go-playground/validator/v10

然后将 Validator 包导入到代码中:

import "github.com/go-playground/validator/v10"

变量验证

Var 方法使用 tag(标记)验证方式验证单个变量。

func (*validator.Validate).Var(field interface{}, tag string) error

它接收一个 interface{} 空接口类型的 field 和一个 string 类型的 tag,返回传递的非法值得无效验证错误,否则将 nil 或 ValidationErrors 作为错误。如果错误不是 nil,则需要断言错误去访问错误数组,例如:

validationErrors := err.(validator.ValidationErrors)


如果是验证数组、slice 和 map,可能会包含多个错误。

示例代码:

func main() {
  validate := validator.New()
  // 验证变量
  email := "admin#admin.com"
  email := ""
  err := validate.Var(email, "required,email")
  if err != nil {
    validationErrors := err.(validator.ValidationErrors)
    fmt.Println(validationErrors)
    // output: Key: '' Error:Field validation for '' failed on the 'email' tag
    // output: Key: '' Error:Field validation for '' failed on the 'required' tag
    return
  }
}

结构体验证

结构体验证结构体公开的字段,并自动验证嵌套结构体,除非另有说明。

func (*validator.Validate).Struct(s interface{}) error

它接收一个 interface{} 空接口类型的 s,返回传递的非法值得无效验证错误,否则将 nil 或 ValidationErrors 作为错误。如果错误不是 nil,则需要断言错误去访问错误数组,例如:

validationErrors := err.(validator.ValidationErrors)

实际上,Struct 方法是调用的 StructCtx 方法,因为本文不是源码讲解,所以此处不展开赘述,如有兴趣,可以查看源码。

示例代码:

func main() {
  validate = validator.New()
  type User struct {
    ID     int64  `json:"id" validate:"gt=0"`
    Name   string `json:"name" validate:"required"`
    Gender string `json:"gender" validate:"required,oneof=man woman"`
    Age    uint8  `json:"age" validate:"required,gte=0,lte=130"`
    Email  string `json:"email" validate:"required,email"`
  }
  user := &User{
    ID:     1,
    Name:   "frank",
    Gender: "boy",
    Age:    180,
    Email:  "gopher@88.com",
  }
  err = validate.Struct(user)
  if err != nil {
    validationErrors := err.(validator.ValidationErrors)
    // output: Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
    // fmt.Println(validationErrors)
    fmt.Println(validationErrors.Translate(trans))
    return
  }
}

细心的读者可能已经发现,错误输出信息并不友好,错误输出信息中的字段不仅没有使用备用名(首字母小写的字段名),也没有翻译为中文。通过改动代码,使错误输出信息变得友好。

注册一个函数,获取结构体字段的备用名称:

validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
    name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
    if name == "-" {
      return "j"
    }
    return name
  })

错误信息翻译为中文:

zh := zh.New()
uni = ut.New(zh)
trans, _ := uni.GetTranslator("zh")
_ = zh_translations.RegisterDefaultTranslations(validate, trans)

标签

通过以上章节的内容,读者应该已经了解到 Validator 是一个基于 tag(标签),实现结构体和单个字段的值验证库。

本章节列举一些比较常用的标签:

标签 描述
eq 等于
gt 大于
gte 大于等于
lt 小于
lte 小于等于
ne 不等于
max 最大值
min 最小值
oneof 其中一个
required 必需的
unique 唯一的
isDefault 默认值
len 长度
email 邮箱格式

转自: Golang语言开发栈

多语言翻译验证

当业务系统对验证信息有特殊需求时,例如:返回信息需要自定义,手机端返回的信息需要是中文而pc端发挥返回的信息需要时英文,如何做到请求一个接口满足上述三种情况。

package main

import (
    "fmt"

    "github.com/gin-gonic/gin"
    "github.com/go-playground/locales/en"
    "github.com/go-playground/locales/zh"
    "github.com/go-playground/locales/zh_Hant_TW"
    ut "github.com/go-playground/universal-translator"
    "gopkg.in/go-playground/validator.v9"
    en_translations "gopkg.in/go-playground/validator.v9/translations/en"
    zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
    zh_tw_translations "gopkg.in/go-playground/validator.v9/translations/zh_tw"
)

var (
    Uni      *ut.UniversalTranslator
    Validate *validator.Validate
)

type User struct {
    Username string `form:"user_name" validate:"required"`
    Tagline  string `form:"tag_line" validate:"required,lt=10"`
    Tagline2 string `form:"tag_line2" validate:"required,gt=1"`
}

func main() {
    en := en.New()
    zh := zh.New()
    zh_tw := zh_Hant_TW.New()
    Uni = ut.New(en, zh, zh_tw)
    Validate = validator.New()

    route := gin.Default()
    route.GET("/5lmh", startPage)
    route.POST("/5lmh", startPage)
    route.Run(":8080")
}

func startPage(c *gin.Context) {
    //这部分应放到中间件中
    locale := c.DefaultQuery("locale", "zh")
    trans, _ := Uni.GetTranslator(locale)
    switch locale {
    case "zh":
        zh_translations.RegisterDefaultTranslations(Validate, trans)
        break
    case "en":
        en_translations.RegisterDefaultTranslations(Validate, trans)
        break
    case "zh_tw":
        zh_tw_translations.RegisterDefaultTranslations(Validate, trans)
        break
    default:
        zh_translations.RegisterDefaultTranslations(Validate, trans)
        break
    }

    //自定义错误内容
    Validate.RegisterTranslation("required", trans, func(ut ut.Translator) error {
        return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details
    }, func(ut ut.Translator, fe validator.FieldError) string {
        t, _ := ut.T("required", fe.Field())
        return t
    })

    //这块应该放到公共验证方法中
    user := User{}
    c.ShouldBind(&user)
    fmt.Println(user)
    err := Validate.Struct(user)
    if err != nil {
        errs := err.(validator.ValidationErrors)
        sliceErrs := []string{}
        for _, e := range errs {
            sliceErrs = append(sliceErrs, e.Translate(trans))
        }
        c.String(200, fmt.Sprintf("%#v", sliceErrs))
    }
    c.String(200, fmt.Sprintf("%#v", "user"))
}


正确的链接:

http://localhost:8080/testing?user_name=枯藤&tag_line=9&tag_line2=33&locale=zh

http://localhost:8080/testing?user_name=枯藤&tag_line=9&tag_line2=3&locale=en 返回英文的验证信息

http://localhost:8080/testing?user_name=枯藤&tag_line=9&tag_line2=3&locale=zh 返回中文的验证信息

查看更多的功能可以查看官网 gopkg.in/go-playground/validator.v9

文件操作

Gin 并没有提供文件的创建,删除,读写这个操作的专门的接口,所以采用的是常用的ioutil这个包进行文件的读写操作,使用os这个包进行文件的创建和删除。

文件的创建,写入内容,读取内容,删除.(此实例使用的是txt文件):

-controller
	+file.go
-router
	+router.go
main.go

//文件的创建删除和读写
router.GET("/cont/filerw", controllers.Filerwhtml)       //获取文件api操作信息
router.POST("/cont/addfile", controllers.FilerCreate)    //创建文件
router.POST("/cont/writefile", controllers.FilerWrite)   //写入文件
router.POST("/cont/readfile", controllers.FilerRead)     //读取文件
router.POST("/cont/deletefile", controllers.FilerDelete) //删除文件
package controllers

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "io/ioutil"
    "net/http"
    "os"
)

// 定义接收数据的结构体
type FileData struct {
    // binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
    FileName string `form:"filename" json:"filename" uri:"filename" xml:"filename" binding:"required"`
    Content  string `form:"content" json:"content" uri:"content" xml:"content"`
}

//文件操作接口信息
type Data struct {
    Api    string `json:"api"`
    Params string `json:"params"`
    Remark string `json:"remark"`
}

/**文件读写操作接口信息**/
func Filerwhtml(c *gin.Context) {
    list := []Data{
        Data{
            "/cont/addfile",
            "filename",
            "创建文件",
        },
        Data{
            "/cont/writefile",
            "filename,content",
            "写入文件",
        },
        Data{
            "/cont/readfile",
            "filename",
            "读取文件",
        },
        Data{
            "/cont/deletefile",
            "filename",
            "删除文件",
        },
    }
    //返回结果
    c.JSON(http.StatusOK, gin.H{"code": 0, "list": list})
    return
}

创建文件

/**创建文件**/
func FilerCreate(c *gin.Context) {
    // 声明接收的变量
    var data FileData
    // 将request的body中的数据,自动按照json格式解析到结构体
    if err := c.ShouldBindJSON(&data); err != nil {
        // 返回错误信息
        // gin.H封装了生成json数据的工具
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": err.Error()})
        return
    }
    //创建文件
    path := fmt.Sprintf("static/txtfile/%s.txt", data.FileName)
    f, err := os.Create(path)
    fmt.Print(path)
    if err != nil {
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": "创建文件失败"})
        fmt.Print(err.Error())
        return
    }
    defer f.Close()
    //返回结果
    c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "创建成功", "filename": data.FileName})
    return
}

写入文件

/**将内容写入文件**/
func FilerWrite(c *gin.Context) {
    // 声明接收的变量
    var data FileData
    // 将request的body中的数据,自动按照json格式解析到结构体
    if err := c.ShouldBindJSON(&data); err != nil {
        // 返回错误信息
        // gin.H封装了生成json数据的工具
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": err.Error()})
        return
    }
    //需要写入到文件的内容
    content := data.Content
    path := fmt.Sprintf("static/txtfile/%s.txt", data.FileName)
    d1 := []byte(content)
    err := ioutil.WriteFile(path, d1, 0644)
    fmt.Print(path)
    if err != nil {
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": "写入内容失败"})
        fmt.Print(err.Error())
        return
    }
    //返回结果
    c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "写入内容成功", "filename": data.FileName, "content": content})
    return
}

读取文件

/**读取文件内容**/
func FilerRead(c *gin.Context) {
    // 声明接收的变量
    var data FileData
    // 将request的body中的数据,自动按照json格式解析到结构体
    if err := c.ShouldBindJSON(&data); err != nil {
        // 返回错误信息
        // gin.H封装了生成json数据的工具
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": err.Error()})
        return
    }
    path := fmt.Sprintf("static/txtfile/%s.txt", data.FileName)
    //文件读取任务是将文件内容读取到内存中。
    info, err := ioutil.ReadFile(path)
    fmt.Print(path)
    if err != nil {
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": "读取文件内容失败"})
        fmt.Print(err.Error())
        return
    }
    fmt.Println(info)
    result := string(info)
    //返回结果
    c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "读取内容成功", "filename": data.FileName, "content": result})
    return
}

删除文件

/**删除文件**/
func FilerDelete(c *gin.Context) {
    // 声明接收的变量
    var data FileData
    // 将request的body中的数据,自动按照json格式解析到结构体
    if err := c.ShouldBindJSON(&data); err != nil {
        // 返回错误信息
        // gin.H封装了生成json数据的工具
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": err.Error()})
        return
    }
    path := fmt.Sprintf("static/txtfile/%s.txt", data.FileName) //源文件路径
    //删除文件
    cuowu := os.Remove(path)
    fmt.Print(path)
    if cuowu != nil {
        //如果删除失败则输出 file remove Error!
        fmt.Println("file remove Error!")
        //输出错误详细信息
        fmt.Printf("%s", cuowu)
        c.JSON(http.StatusMovedPermanently, gin.H{"code:": 1, "msg: ": "删除文件失败"})
        return
    } else {
        //如果删除成功则输出 file remove OK!
        fmt.Print("file remove OK!")
    }
    //返回结果
    c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "删除文件成功", "filename": data.FileName})
    return
}
文件上传下载
- controller
	+file.go
-uploadFile
	+.....
-router
	+router.go
-main.go

package controller

import (
	"fmt"
	"log"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
)

// AddUploads 上传文件
func AddUploads(c *gin.Context) {
	username := c.PostForm("username")
	// 单个文件
	file, err := c.FormFile("file")
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": err.Error(),
		})
		return
	}

	log.Println(file.Filename)
	// dst := fmt.Sprintf("D:/桌面/文件/updateFile/%s", username+"-"+file.Filename)
	dst := fmt.Sprintf(".updateFile/%s", username+"-"+file.Filename)
	// 上传文件到指定的目录
	c.SaveUploadedFile(file, dst)
	c.JSON(http.StatusOK, gin.H{
		"username": username,
		"message":  fmt.Sprintf("'%s' uploaded!", file.Filename),
	})
}

// DownFile
func DownFile(c *gin.Context) {
	fileName := "hjz-开题报告.docx"
	filepath := "./updateFile/" + fileName
	list := strings.Split(fileName, "-")
	downFileName := list[1]
	c.Writer.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=%s", downFileName))
	c.Writer.Header().Add("Content-Type", "application/octet-stream")
	fmt.Println(filepath)
	c.File(filepath)
}

基本问题:

类型转换

string -- int64

func DeletedFileOne(c *gin.Context) {
	fidStr := c.Param("fid")
	fid, err := strconv.ParseInt(fidStr, 10, 64)
	if err != nil {
		ResponseError(c, CodeInvalidParam)
		return
	}
	if err2 := logic.DeletedFileOne(fid); err2 != nil {
		zap.L().Error("logic.DeletedFileOne () failed ", zap.Error(err))
		ResponseError(c, CodeServerBusy)
		return
	}
	ResponseSuccess(c, CodeSuccess)
}

int64--string

strconv.FormatInt(v.NucleicAcidID, 10)

得到当前时间戳

// GetNowTime 得到现在时间的年月日的时间戳
func GetNowTime() int64 {
	tm := time.Now().Format("2006-01-02")
	tt, _ := time.ParseInLocation("2006-01-02", tm, time.Local)
	return tt.Unix()
}

时间戳---time

time.Unix(v.TodayTime, 0)
// 秒级时间戳转time
func UnixSecondToTime(second int64) time.Time {
	return time.Unix(second, 0)
}

// 毫秒级时间戳转time
func UnixMilliToTime(milli int64) time.Time {
	return time.Unix(milli/1000, (milli%1000)*(1000*1000))
}

// 纳秒级时间戳转time
func UnixNanoToTime(nano int64) time.Time {
	return time.Unix(nano/(1000*1000*1000), nano%(1000*1000*1000))
}

Gin框架解析

路由解析

中间件解析

微信小程序

	uni.login({
				        provider: "weixin",
				        success: function (res) {
						uni.request({
							  method:'POST',	
							  url: 'http://106.15.65.147:8081/api/v1/wx/openid', //仅为示例,并非真实接口地址。
							  data: {
							      "app_id": "wx8d36d8370b6e82f0",
							      "code": res.code,
							      "method": "get",
							      "secret": "092d4b45d6b6c8d2b99bf82c6e23657e",
							      "url": "https://api.weixin.qq.com/sns/jscode2session" 
							  },
							  header: {
							      'content-type': 'application/json' //自定义请求头信息
							  },
							  success: (res) => {
								  var result = res.data
								  	uni.setStorageSync('open_id',result.data.open_id)
									uni.setStorageSync('session_key',result.data.session_key)
							  }
							});
				        },
				      });

转发请求

post

// 发送post请求
func main() {
router := gin.Default()
router.GET("/test", func(c *gin.Context) {
   var body = strings.NewReader("name=test&jab=teache")
   response, err := http.Post("http://localhost:8888/base/captcha","application/json; charset=utf-8", body)
   if err != nil || response.StatusCode != http.StatusOK {
      c.Status(http.StatusServiceUnavailable)
      return
   }

   reader := response.Body
   contentLength := response.ContentLength
   contentType := response.Header.Get("Content-Type")

   extraHeaders := map[string]string{
      //"Content-Disposition": `attachment; filename="gopher.png"`,
   }

   c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
})
router.Run(":8080")
}

golang发送get请求第三方数据

func main() {
   router := gin.Default()
   router.GET("/test", func(c *gin.Context) {
      response, err := http.Get("https://baidu.com")
      if err != nil || response.StatusCode != http.StatusOK {
         c.Status(http.StatusServiceUnavailable)
         return
      }

      reader := response.Body
      contentLength := response.ContentLength
      contentType := response.Header.Get("Content-Type")

      extraHeaders := map[string]string{
         //"Content-Disposition": `attachment; filename="gopher.png"`,
      }

      c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
   })
   router.Run(":8080")
}

跨域:

package router

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func CORSMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") //允许所有IP访问
		ctx.Writer.Header().Set("Access-Control-Max-Age", "86400")
		ctx.Writer.Header().Set("Access-Control-Allow-Headers", "*")
		ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		ctx.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")

		if ctx.Request.Method == http.MethodOptions {
			ctx.AbortWithStatus(200)
		} else {
			ctx.Next()
		}
	}
}

实现404页面

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.GET("/user", func(c *gin.Context) {
        //指定默认值
        //http://localhost:8080/user 才会打印出来默认的值
        name := c.DefaultQuery("name", "枯藤")
        c.String(http.StatusOK, fmt.Sprintf("hello %s", name))2020-08-05 09:22:11 星期三
    })
    r.NoRoute(func(c *gin.Context) {
        c.String(http.StatusNotFound, "404 not found2222")
    })
    r.Run()
}

JSON序列化和反序列化

package main

import (
	"encoding/json"
	"fmt"
	"math"
)

type User struct {
	UserID   int64  `json:"id"`
	UserName string `json:"name"`
}

// 第一层次
// 第二层次
// 第五层次
func main() {
	//json序列化
	user := User{
		UserID:   math.MaxInt64,
		UserName: "hjz",
	}
	b, err := json.Marshal(user)
	if err != nil {
		print(err)
	}
	fmt.Println(string(b))
	//使用Json的反序列化
	s := `{"id":9223372036854775807,"name":"hjz"}`
	var data User
	err = json.Unmarshal([]byte(s), &data)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("userId:%d,userName:%s", user.UserID, user.UserName)
}

翻译Gin框架的日志

-controller
  + validator.go
- models
  + params.go

package controller

//翻译Gin框架的日志
import (
	"fmt"
	"reflect"
	"strings"
	"fileWeb/models"

	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/locales/en"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	enTranslations "github.com/go-playground/validator/v10/translations/en"
	zhTranslations "github.com/go-playground/validator/v10/translations/zh"
)

// 定义一个全局翻译器T
var trans ut.Translator

// InitTrans 初始化翻译器
func InitTrans(locale string) (err error) {
	// 修改gin框架中的Validator引擎属性,实现自定制
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		// 注册一个获取json tag的自定义方法
		v.RegisterTagNameFunc(func(fld reflect.StructField) string {
			name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
			if name == "-" {
				return ""
			}
			return name
		})

		// 为SignUpParam注册自定义校验方法
		v.RegisterStructValidation(SignUpParamStructLevelValidation, models.ParamSignUp{})
		zhT := zh.New() // 中文翻译器
		enT := en.New() // 英文翻译器

		// 第一个参数是备用(fallback)的语言环境
		// 后面的参数是应该支持的语言环境(支持多个)
		// uni := ut.New(zhT, zhT) 也是可以的
		uni := ut.New(enT, zhT, enT)

		// locale 通常取决于 http 请求头的 'Accept-Language'
		var ok bool
		// 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
		trans, ok = uni.GetTranslator(locale)
		if !ok {
			return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
		}

		// 注册翻译器
		switch locale {
		case "en":
			err = enTranslations.RegisterDefaultTranslations(v, trans)
		case "zh":
			err = zhTranslations.RegisterDefaultTranslations(v, trans)
		default:
			err = enTranslations.RegisterDefaultTranslations(v, trans)
		}
		return
	}
	return
}

// RemoveTopStruct 去除提示信息中的结构体名称
func RemoveTopStruct(fields map[string]string) map[string]string {
	res := map[string]string{}
	for field, err := range fields {
		res[field[strings.Index(field, ".")+1:]] = err
	}
	return res
}

// SignUpParamStructLevelValidation 自定义SignUpParam结构体校验函数
func SignUpParamStructLevelValidation(sl validator.StructLevel) {
	su := sl.Current().Interface().(models.ParamSignUp)

	if su.Password != su.RePassword {
		// 输出错误提示信息,最后一个参数就是传递的param
		sl.ReportError(su.RePassword, "re_password", "RePassword", "eqfield", "password")
	}
}
// ParamSignUp 注册请求参数
type ParamSignUp struct {
	Username   string `json:"username" binding:"required"`
	Password   string `json:"password" binding:"required"`
	RePassword string `json:"re_password" binding:"required"`
	Phone      string `json:"phone" binding:"required"`
	Name       string `json:"name" binding:"required"`
}

雪花算法

-pkg
 - snowflake
 	+ snowflake.go

package snowflake

//雪花算法
import (
	"time"

	"github.com/bwmarrin/snowflake"
)

var node *snowflake.Node

func Init(startTime string, machineID int64) (err error) {
	var st time.Time
	//指定时间因子-startTime
	st, err = time.Parse("2006-01-02", startTime)
	if err != nil {
		return
	}
	snowflake.Epoch = st.UnixNano() / 1000000
	node, err = snowflake.NewNode(machineID)
	return
}
func GenID() int64 {
	return node.Generate().Int64()
}

jwtToken

- pkg
  -jwt
  	+ jwt.go

package jwt

import (
	"errors"
	"time"

	"github.com/golang-jwt/jwt/v4"
)

// token的过期时间
const TokenExpireDuration = time.Hour * 2

// token的sercet用于签名的字符串
var CustomSecret []byte = []byte("疫情小程序签名")

type CustomClaims struct {
	jwt.RegisteredClaims        // 内嵌标准的声明
	UserID               int64  `json:"user_id"`
	Username             string `json:"username"`
}

// GenToken 生成JWT
func GenToken(userID int64, username string) (string, error) {
	// 创建一个我们自己的声明
	claims := CustomClaims{
		UserID:   userID,
		Username: username, // 自定义字段
	}
	claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(TokenExpireDuration))
	claims.Issuer = "my-project"
	// 使用指定的签名方法创建签名对象
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// 使用指定的secret签名并获得完整的编码后的字符串token
	return token.SignedString(CustomSecret)
}

// ParseToken 解析JWT
func ParseToken(tokenString string) (*CustomClaims, error) {
	// 解析token
	// 如果是自定义Claim结构体则需要使用 ParseWithClaims 方法
	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (i interface{}, err error) {
		// 直接使用标准的Claim则可以直接使用Parse方法
		//token, err := jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, err error) {
		return CustomSecret, nil
	})
	if err != nil {
		return nil, err
	}
	// 对token对象中的Claim进行类型断言
	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { // 校验token
		return claims, nil
	}
	return nil, errors.New("invalid token")
}

Code 返回的错误

- controller 
	+ Code.go

package controller

// ResCode 定义返回值类型
type ResCode int64

const (
	CodeSuccess ResCode = 1000 + iota
	CodeInvalidParam
	CodeUserExist
	CodeUserNotExist
	CodeInvalidPassword
	CodeServerBusy

	CodeNeedLogin
	CodeInvalidToken
)

var codeMsgMap = map[ResCode]string{
	CodeSuccess:         "success",
	CodeInvalidParam:    "请求参数错误",
	CodeUserExist:       "用户名已存在",
	CodeUserNotExist:    "用户名不存在",
	CodeInvalidPassword: "用户名或密码错误",
	CodeServerBusy:      "服务器繁忙",

	CodeNeedLogin:    "需要登录",
	CodeInvalidToken: "无效的token",
}

// GetMsg 得到对应的错误
func (r ResCode) GetMsg() string {
	msg, ok := codeMsgMap[r]
	if !ok {
		msg = codeMsgMap[CodeServerBusy]
	}
	return msg
}

Response 返回响应方法

- controller 
	+ resopnse.go

package controller

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

/*
	{
		"code":1001,//程序中的错误码
		"msg":xx,提示信息
		"data":{},//数据
	}
*/
type Response struct {
	Code ResCode     `json:"code"`
	Msg  interface{} `json:"msg"`
	Data interface{} `json:"data,omitempty"`
}

// ResponseError 返回错误类型
func ResponseError(c *gin.Context, code ResCode) {
	resp := &Response{
		Code: code,
		Msg:  code.GetMsg(),
		Data: nil,
	}
	c.JSON(http.StatusOK, resp)
}

// ResponseSuccess 返回请求成功
func ResponseSuccess(c *gin.Context, data interface{}) {
	resp := &Response{
		Code: CodeSuccess,
		Msg:  CodeSuccess.GetMsg(),
		Data: data,
	}
	c.JSON(http.StatusOK, resp)
}

// 自定义的返回错误
func ResponseErrorWithMsg(c *gin.Context, code ResCode, msg interface{}) {
	resp := &Response{
		Code: code,
		Msg:  msg,
		Data: nil,
	}
	c.JSON(http.StatusOK, resp)
}

// ResponseSuccessLayUi 返回Layui 数据
func ResponseSuccessLayUi(c *gin.Context, code int, msg string, count int, data interface{}) {
	c.JSON(http.StatusOK, gin.H{
		"code":  code,
		"count": count,
		"data":  data,
		"msg":   msg,
	})
}

Request 解析请求操作

- controller
	+ request.go

package controller

import (
	"errors"
	"strconv"

	"github.com/gin-gonic/gin"
)

const CtxUserIDkey = "userID"

var ErrorUserNotLogin = errors.New("用户未登录")

// GetCyrrentUserID 获取当前用户的ID
func GetCyrrentUserID(c *gin.Context) (userID int64, err error) {
	uid, ok := c.Get(CtxUserIDkey)
	if !ok {
		err = ErrorUserNotLogin
		return
	}
	userID, ok = uid.(int64)
	if !ok {
		err = ErrorUserNotLogin
		return
	}
	return
}

// GetPageInfo 处理分页请求的参数
func GetPageInfo(c *gin.Context) (page, page_size int64, err error) {
	//获得分页参数offer和limit
	pageStr := c.Query("page")
	page_sizeStr := c.Query("size")
	page, err = strconv.ParseInt(pageStr, 10, 32)
	if err != nil {
		page = 1
	}
	page_size, err = strconv.ParseInt(page_sizeStr, 10, 32)
	if err != nil {
		page_size = 10
	}
	return
}

基本请求

1.参数处理
2.业务逻辑
3.返回数据

import (
	"errors"
	"strconv"
	"webGin/dao/mysql"
	"webGin/logic"
	"webGin/models"

	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10" //注意这条正确
	"go.uber.org/zap"
)

// SingUpHandlerInstructor 辅导员注册
func SingUpHandlerInstructor(c *gin.Context) {
	p := new(models.ParamSignUp)
	if err := c.ShouldBindJSON(p); err != nil {
		zap.L().Error("SingUpHandlerInstructor with invalid param ", zap.Error(err))
		//判断是否是校验错误
		errs, ok := err.(validator.ValidationErrors)
		if !ok {
			ResponseError(c, CodeInvalidParam)
			return
		} else {
			ResponseErrorWithMsg(c, CodeInvalidParam, RemoveTopStruct(errs.Translate(trans)))
			return
		}
	}
	//业务处理
	if err := logic.SingUp(p); err != nil {
		zap.L().Error("logic.SingUp() failed", zap.Error(err))
		//依据错误的类型进行返回
		if errors.Is(err, mysql.ErrorUserExist) {
			ResponseError(c, CodeUserExist)
			return
		} else {
			ResponseError(c, CodeServerBusy)
			return
		}
	}
	ResponseSuccess(c, CodeSuccess)
}