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 | 长度 |
邮箱格式 |
转自: 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)}