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回收的消耗
  • 取出来要用的时候再初始化

  • 后台
package mainimport (    "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 mainimport (    "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中
                Document              上传文件:              
package mainimport (    "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()}

上传多个文件

                Document              上传文件:              
package mainimport (   "github.com/gin-gonic/gin"   "net/http"   "fmt")// gin的helloWorldfunc 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的默认值为kakapwd := 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 mainimport (   "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”)

                Document            用户名
密码
package mainimport (    "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 mainimport (    "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 mainimport ("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()}
                Document    账号是:{{.name}}
密码是:{{.pwd}}

重定向

package mainimport (    "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 mainimport (    "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_async2022/08/01 10:49:17 同步执行:/long_sync[GIN] 2022/08/01 - 10:49:17 | 200 |    3.0071153s |             ::1 | GET      "/long_sync"

中间件全局中间件

  • 所有请求都经过此中间件
package mainimport (    "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()}
中间件开始执行了中间件执行完毕 200time: 516.1µsrequest: 中间件[GIN] 2022/10/02 - 14:56:20 | 200 |       516.1µs |             ::1 | GET      "/ce"

Next()方法

package mainimport (    "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: 中间件   中间件执行完毕 200time: 235.9µs     [GIN] 2022/10/02 - 14:57:46 | 200 |       754.8µs |             ::1 | GET      "/ce"

局部中间件

package mainimport (    "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 mainimport ("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 mainimport (   "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 mainimport ("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 mainimport (   "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 mainimport (    "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 mainimport (    "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 mainimport (    "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 mainimport (    "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 mainimport (    "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.gomain.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 controllersimport (    "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 controllerimport ("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),})}// DownFilefunc DownFile(c *gin.Context) {fileName := "hjz-开题报告.docx"filepath := "./updateFile/" + fileNamelist := 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)
// 秒级时间戳转timefunc UnixSecondToTime(second int64) time.Time {return time.Unix(second, 0)}// 毫秒级时间戳转timefunc UnixMilliToTime(milli int64) time.Time {return time.Unix(milli/1000, (milli%1000)*(1000*1000))}// 纳秒级时间戳转timefunc 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 routerimport ("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 mainimport (    "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 mainimport ("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 Usererr = 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")// 定义一个全局翻译器Tvar 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 {// 输出错误提示信息,最后一个参数就是传递的paramsl.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.Nodefunc Init(startTime string, machineID int64) (err error) {var st time.Time//指定时间因子-startTimest, err = time.Parse("2006-01-02", startTime)if err != nil {return}snowflake.Epoch = st.UnixNano() / 1000000node, err = snowflake.NewNode(machineID)return}func GenID() int64 {return node.Generate().Int64()}

jwtToken

- pkg  -jwt  + jwt.go
package jwtimport ("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 生成JWTfunc 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签名并获得完整的编码后的字符串tokenreturn token.SignedString(CustomSecret)}// ParseToken 解析JWTfunc 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 { // 校验tokenreturn claims, nil}return nil, errors.New("invalid token")}

Code 返回的错误

- controller + Code.go
package controller// ResCode 定义返回值类型type ResCode int64const (CodeSuccess ResCode = 1000 + iotaCodeInvalidParamCodeUserExistCodeUserNotExistCodeInvalidPasswordCodeServerBusyCodeNeedLoginCodeInvalidToken)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 controllerimport ("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 controllerimport ("errors""strconv""github.com/gin-gonic/gin")const CtxUserIDkey = "userID"var ErrorUserNotLogin = errors.New("用户未登录")// GetCyrrentUserID 获取当前用户的IDfunc GetCyrrentUserID(c *gin.Context) (userID int64, err error) {uid, ok := c.Get(CtxUserIDkey)if !ok {err = ErrorUserNotLoginreturn}userID, ok = uid.(int64)if !ok {err = ErrorUserNotLoginreturn}return}// GetPageInfo 处理分页请求的参数func GetPageInfo(c *gin.Context) (page, page_size int64, err error) {//获得分页参数offer和limitpageStr := 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)}