- 安装
- 路由配置
- 路由参数
- 表单参数
- 文件上传
- routes group分组管理
- 404页面配置
- 数据格式响应
- 模板渲染
- 静态资源引入
- 重定向
- 同步异步
- 中间件
- 会话控制(cookie session)
- 其他
安装
下载安装
1
| go get -u github.com/gin-gonic/gin
|
引入
1
| import "github.com/gin-gonic/gin"
|
可选 如果需要http.StatusOK之类的常量
基本路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main
import ( "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("/", func(ctx *gin.Context) { ctx.String(http.StatusOK, "hello world") }) router.POST("/", func(ctx *gin.Context) { ctx.String(http.StatusOK, "post datas") }) router.PUT("/put") router.Run(":8080") }
|
API参数路由
- 可以通过Context的Param方法来获取API参数
- localhost:8000/xxx/zhangsan
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package main import ( "net/http" "strings" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("user/:name/*action", func(ctx *gin.Context) { name := ctx.Param("name") action := ctx.Param("action") action = strings.Trim(action, "/") ctx.String(http.StatusOK, name+"is"+action) }) router.Run() }
|
url参数路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("/user", func(ctx *gin.Context) { name := ctx.DefaultQuery("name", "默认值") ctx.String(http.StatusOK, fmt.Sprintf("hello %s", name)) }) router.Run() }
|
1 2
| http://localhost:8080/user 默认值
|
1 2
| http://localhost:8080/user?name=abc abc
|
表单参数
- 表单传输为post请求,http常见的传输格式为四种:
- application/json
- application/x-www-form-urlencoded
- application/xml
- multipart/form-data
- 表单参数可以通过
PostForm()
方法获取,该方法默认解析的是x-www-form-urlencoded
或from-data
格式的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.LoadHTMLFiles("template/formview.html") router.GET("/index", func(ctx *gin.Context) { ctx.HTML(http.StatusOK, "formview.html", gin.H{ "title": "index pages", }) }) router.POST("/form", func(ctx *gin.Context) { types := ctx.DefaultPostForm("type", "post") username := ctx.PostForm("username") password := ctx.PostForm("password") ctx.String(http.StatusOK, fmt.Sprintf("username:%s,password:%s,type:%s", username, password, types)) }) router.Run() }
|
1 2 3 4 5
| <form action="/form" method="post" > <input type="text" name="username" placeholder="用户名"> <input type="text" name="password" placeholder="密码"> <input type="submit" value="提交"> </form>
|
方法2 接收数据并判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" )
type Login struct { User string `form:"username" json:"username" uri:"username" xml:"username" binding:"required"` Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"` } func main() { r := gin.Default() r.LoadHTMLFiles("template/formview.html") r.GET("/form", func(ctx *gin.Context) { ctx.HTML(http.StatusOK, "formview.html", gin.H{ "title": "formview page", }) }) r.POST("/formdata", func(ctx *gin.Context) { var form Login if err := ctx.Bind(&form); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if form.User != "admin" || form.Password != "123456" { ctx.JSON(http.StatusBadRequest, gin.H{"error": "用户名或密码错误"}) return } ctx.String(http.StatusOK, fmt.Sprintf("%v -- %v", ctx.PostForm("username"), ctx.PostForm("password"))) }) r.Run() }
|
单文件上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.LoadHTMLFiles("template/formview.html") router.GET("/form", func(ctx *gin.Context) { ctx.HTML(http.StatusOK, "formview.html", gin.H{}) }) router.MaxMultipartMemory = 8 << 20 router.POST("/upload", func(ctx *gin.Context) { file, err := ctx.FormFile("file") if err != nil { ctx.String(500, "上传图片出错") } ctx.SaveUploadedFile(file, "uploads/"+file.Filename) ctx.String(http.StatusOK, "uploads/"+file.Filename) }) router.Run() }
|
上传指定类型文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package main import ( "log" "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.LoadHTMLFiles("template/formview.html") router.GET("/form", func(ctx *gin.Context) { ctx.HTML(http.StatusOK, "formview.html", "") }) router.POST("/upload", func(ctx *gin.Context) { _, headers, err := ctx.Request.FormFile("file") if err != nil { log.Printf("error:%v", err) } if headers.Size > 1024*1024*2 { ctx.String(500, "文件太大了") return } if headers.Header.Get("content-Type") != "image/png" { ctx.String(500, "文件类型不正确") return } ctx.SaveUploadedFile(headers, "uploads/"+headers.Filename) ctx.String(http.StatusOK, headers.Filename) }) router.Run() }
|
上传多个文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.MaxMultipartMemory = 8 << 20 router.LoadHTMLFiles("template/formview.html") router.GET("/form", func(ctx *gin.Context) { ctx.HTML(http.StatusOK, "formview.html", "") }) router.POST("/upload", func(ctx *gin.Context) { form, err := ctx.MultipartForm() if err != nil { ctx.String(http.StatusBadRequest, fmt.Sprintf("error:%s", err.Error())) } files := form.File["files"] for _, file := range files { if err := ctx.SaveUploadedFile(file, "uploads/"+file.Filename); err != nil { ctx.String(http.StatusBadRequest, fmt.Sprintf("upload error:%s", err.Error())) return } } ctx.String(200, fmt.Sprintf("uploads ok %d", len(files))) }) router.Run() }
|
routes group 管理相同的url
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package main import ( "fmt" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() v1 := router.Group("/v1") { v1.GET("/login", login) v1.GET("/submit", submit) } v2 := router.Group("/v2") { v2.POST("/login", login) v2.POST("/submit", submit) } router.Run() } func login(ctx *gin.Context) { name := ctx.DefaultQuery("name", "张三") ctx.String(200, fmt.Sprintf("hello %s\n", name)) } func submit(ctx *gin.Context) { name := ctx.DefaultQuery("name", "李四") ctx.String(200, fmt.Sprintf("hello %s\n", name)) }
|
404页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("/user", func(ctx *gin.Context) { name := ctx.DefaultQuery("name", "garywang") ctx.String(http.StatusOK, fmt.Sprintf("%v", name)) }) router.NoRoute(func(ctx *gin.Context) { ctx.String(http.StatusNotFound, "404 not found!!!") }) router.Run() }
|
json数据解析和绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" )
type Login struct { User string `form:"username" json:"username" uri:"username" xml:"username" binding:"required"` Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"` } func main() { r := gin.Default() r.POST("/post", func(c *gin.Context) { var json Login fmt.Println(json) err := c.ShouldBindJSON(&json) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"status": err.Error()}) return } if json.User != "root" || json.Pssword != "admin" { c.JSON(http.StatusBadRequest, gin.H{"status": "用户名或密码错误"}) return } c.JSON(http.StatusOK, gin.H{"status": "200"}) }) r.Run() }
|
1 2 3 4 5 6 7 8 9
| http://localhost:8080/post 自定义格式 application/json application/xml text/plain { "username":"root", "password":"admin" }
|
各种数据格式的响应
json 结构体 XML YAML protobuf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package main import ( "net/http" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/testdata/protoexample" ) func main() { r := gin.Default() r.GET("/json", func(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{"message": "msg", "status": "200"}) }) r.GET("/struct", func(ctx *gin.Context) { var msg struct { Name string Message string Number int } msg.Name = "garywang" msg.Message = "msg" msg.Number = 66 ctx.JSON(200, msg) }) r.GET("/xml", func(ctx *gin.Context) { ctx.XML(200, gin.H{"message": 666}) }) r.GET("/yaml", func(ctx *gin.Context) { ctx.YAML(200, gin.H{"message": "yaml"}) }) r.GET("/protobuf", func(ctx *gin.Context) { reps := []int64{int64(1), int64(2)} label := "label" data := &protoexample.Test{ Label: &label, Reps: reps, } ctx.ProtoBuf(200, data) }) r.Run() }
|
HTML模板渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main
import ( "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.LoadHTMLGlob("template/*") router.GET("/index", func(ctx *gin.Context) { ctx.HTML(http.StatusOK, "index.html", gin.H{ "title": "website", }) }) router.Run(":8081") }
|
1 2 3 4 5
| <html> <h1> {{ .title }} </h1> </html>
|
不同目录下相同模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import ( "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.LoadHTMLGlob("template/**/*") router.GET("/post/index", func(ctx *gin.Context) { ctx.HTML(http.StatusOK, "index.html", gin.H{ "title": "Posts", }) }) router.GET("/user/index", func(ctx *gin.Context) { ctx.HTML(http.StatusOK, "index.html", gin.H{ "title": "users", }) }) router.Run() }
|
两层目录以后发现单层/index目录内容是user/index的内容,但是参数还是index的参数
引入静态文件目录
1
| r.Static("/assets", "./assets")
|
自定义模板渲染器
重定向
1 2 3 4 5 6 7 8 9 10 11 12
| package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/index", func(ctx *gin.Context) { ctx.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/") }) r.Run() }
|
同步异步
- goroutine机制
- 在启动新的goroutine时,不应该使用原始上下文,必须使用它的只读副本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package main import ( "log" "time" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/index_async", func(ctx *gin.Context) { copyContext := ctx.Copy() go func() { time.Sleep(3 * time.Second) log.Println("异步执行:" + copyContext.Request.URL.Path) ctx.String(200, "异步执行") }() }) r.GET("/index", func(ctx *gin.Context) { time.Sleep(3 * time.Second) log.Println("同步执行" + ctx.Request.URL.Path) ctx.String(200, "同步执行66") }) r.Run() }
|
中间件
全局中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package main import ( "fmt" "net/http" "time" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.Use(MiddleWare()) { router.GET("/", func(ctx *gin.Context) { req, _ := ctx.Get("request") fmt.Println("获取中间件传过来的request:", req) ctx.JSON(http.StatusOK, gin.H{"request": req}) }) } router.Run() } func MiddleWare() gin.HandlerFunc { return func(ctx *gin.Context) { t := time.Now() fmt.Println("开始执行中间件") ctx.Set("request", "中间件") status := ctx.Writer.Status() fmt.Println("中间件执行完成", status) t2 := time.Since(t) fmt.Println("中间件执行所用时间:", t2) } }
|
Next()方法
可以到Next()位置挂起中间件,执行主程序,等执行完成以后继续执行next()后面的内容
1 2 3 4 5 6 7 8 9 10 11 12 13
| func MiddleWare() gin.HandlerFunc { return func(ctx *gin.Context) { t := time.Now() fmt.Println("开始执行中间件") ctx.Set("request", "中间件") ctx.Next() status := ctx.Writer.Status() fmt.Println("中间件执行完成", status) t2 := time.Since(t) fmt.Println("中间件执行所用时间:", t2) } }
|
局部中间件
1 2 3 4 5
| router.GET("/",MiddleWare(),func(ctx *gin.Context){ req,_ :=c.Get("request") fmt.Println("request:",req) ctx.JSON(200,gin.H{"request":req}) })
|
中间件练习
创建两个页面,在同一个路由组内,命令行打印页面访问所用的时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package main import ( "fmt" "time" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.Use(MidGetTime()) routerGroup := router.Group("/group") { routerGroup.GET("/a", func(ctx *gin.Context) { time.Sleep(5 * time.Second) data, _ := ctx.Get("time") fmt.Println(data) ctx.JSON(200, gin.H{"time": data}) }) routerGroup.GET("/b", func(ctx *gin.Context) { time.Sleep(3 * time.Second) data, _ := ctx.Get("time") fmt.Println(data) ctx.JSON(200, gin.H{"time": data}) }) } router.Run() }
func MidGetTime() gin.HandlerFunc { return func(ctx *gin.Context) { t := time.Now() ctx.Set("time", time.Now()) ctx.Next() t2 := time.Since(t) fmt.Println("中间件执行结束,耗时:", t2) } }
|
会话控制
cookie的使用
测试服务端发送cookie给客户端,客户端请求时携带cookie
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package main import ( "fmt" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("cookie", func(ctx *gin.Context) { cookie, err := ctx.Cookie("key_cookie") if err != nil { cookie = "没有设置cookie" ctx.SetCookie("key_cookie", "value_cookie", 60, "/", "localhost", false, true) } fmt.Printf("cookie的值是:%s\n", cookie) }) router.Run() }
|
cookie的缺点
- 不安全,明文
- 增加带宽消耗
- 可以被禁用
- cookie有上限
cookie练习
模拟权限验证中间件
两个页面 login页面可以访问 home页面需要cookie才能访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| package main
import ( "fmt" "net/http" "time" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.Use(MiddYanzheng()) router.LoadHTMLFiles( "template/user/index.html", "template/public/header.html", "template/public/footer.html") router.GET("/login", func(ctx *gin.Context) { status, _ := ctx.Get("cookie") if status == true { ctx.Redirect(http.StatusMovedPermanently, "/home") } else { ctx.HTML(200, "index.html", gin.H{ "title": "登录页面", }) } }) router.GET("/home", func(ctx *gin.Context) { status, _ := ctx.Get("cookie") if status == false { ctx.Redirect(http.StatusMovedPermanently, "/login") } else { ctx.String(200, "home page") } }) routerGroup := router.Group("/verify") { routerGroup.POST("/login", func(ctx *gin.Context) { name := ctx.PostForm("name") pwd := ctx.PostForm("pwd") if name != "admin" || pwd != "123" { ctx.String(200, "用户名或密码错误") } else { ctx.SetCookie("cookie", "1", 6000, "/", "localhost", false, true) ctx.Redirect(http.StatusMovedPermanently, "/home") } }) } router.Run() }
func MiddYanzheng() gin.HandlerFunc { return func(ctx *gin.Context) { t := time.Now() cookie, err := ctx.Cookie("cookie") if err != nil { fmt.Println(err.Error()) ctx.Set("cookie", false) } else { ctx.Set("cookie", true) } t2 := time.Since(t) fmt.Println("cookie的状态:", cookie, "中间件耗时:", t2) } }
|
session的使用
原文
注意要点:
- session仓库其实是一个map[interface]interface对象,所有session可以存储任意数据
- session使用的编码器是自带gob,所以存储类似:struct/map这些对象时需要先注册对象,不然会报错
gob:type not registered for ...
- session存储引擎支持:cookie、内存、mongodb、redis、postgres、memstore、memcached记忆gom支持的各类数据库(mysql、sqlite)
- session在创建时有一个配置项,可以配置session过期时间,cookie、domain、secure、path等参数
- 调用session方法:Set()、Delete()、Clear()、方法后,必须调用一次Save()方法.否则session数据不会更新
有专门的session中间件可以直接使用
中间件: github.com/gin-contrib/sessions
安装依赖:go get github.com/gin-contrib/sessions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package main import ( "net/http" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() store := cookie.NewStore([]byte("自定义秘钥")) router.Use(sessions.Sessions("mysession", store)) router.GET("session", func(ctx *gin.Context) { session := sessions.Default(ctx) if session.Get("hello") != "world" { session.Set("hello", "world") session.Save() } ctx.JSON(http.StatusOK, gin.H{"hello": session.Get("hello")}) }) router.Run() }
|
gob注册案例
1 2 3 4
| type User struct{ Name string } gob.Register(User{})
|
session配置项案例
1 2 3 4 5 6 7
| store.Options(sessions.Options{ Secure:true, SameSite:4, Path:"/", MaxAge:m.MaxAge })
|
axios设置携带cookie (chrome<80)
axios请求默认是不带cookie的,如果需要携带需要配置
1 2 3 4 5 6 7 8 9 10 11 12
| axios.defaults.withCredentials = true
const service = axios.create({ baseURL: process.env.BASE_API, timeout: 5000, withCredentials: true })
axios.get('/test', { withCredentials: true })
|
axios在跨域请求中携带cookie (chrome<80)
需要给服务器开启CORS跨域,给gin添加CORS中间件
跨域配置中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func(c *gin.Context) { method := c.Request.Method origin := c.GetHeader("Origin") c.Header("Access-Control-Allow-Origin", origin) c.Header("Access-Control-Allow-Credentials", "true") c.Header("Access-Control-Allow-Headers", "Access-Control-Allow-Headers,Cookie, Origin, X-Requested-With, Content-Type, Accept, Authorization, Token, Timestamp, UserId") c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT") c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type,cache-control") if method == "OPTIONS" { c.AbortWithStatus(http.StatusOK) } c.Next() }
|
如果是chrome版本>80,上面方法不生效,因为chrome增加了安全性修改了携带cookie的方式.
分两种情况:
- 线上情况
- 设置:samesite:name,也就是session的store在设定options时设定的字段配置为:SameSite:4,(这里4就代表none)
- secure配置为true
- 服务端开启https(必须)
- 配置案例查看上面的session配置项案例
优化方案
可以利用cookie机制发送sessionid.使用自定义header字段携带sessionid
基本逻辑:
前端:请求返回后,判断是否有我们命名的session名称的cookie,(就是创建session时的自定义名称:mysession),如果有将值存储到内存.发送请求时,将存储的session值添加到http请求的header中.
服务端:将校验session的中间件中的获取session修改为header中获取.
chrome>80是发不出cookie不是收不到cookie.
session基本配置
过期时间:
store.Options(sessions.Options{MaxAge: 86400*30})
刷新超时时间基本思路:
每次获取到请求后,将该用户的session重新赋值,并返回给客户端,客户端下次请求时携带新的session请求.
当前session的用户数量
有时候我们需要统计当前在线人数,这里就不能用cookie或者内存存储session了.需要用数据库存储.
把session存储到数据库后,通过查询当前没过期session数量就可以确定在线人数
注销
调用session的Delete()方法,然后调用Save()方法保存
vue3浏览器在关闭时发送退出登录请求
在app.vue文件中添加这样的代码,其中:gapTime <=12
中的12是经验值.vue文件使用setup语法糖模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| onMounted(() => { window.addEventListener('beforeunload', beforeunloadHandler) window.addEventListener('unload', unloadHandler) }) function beforeunloadHandler (e) { beforeUnloadTime = new Date().getTime() } function unloadHandler (e) { gapTime = new Date().getTime() - beforeUnloadTime console.log('====gapTime=======', gapTime) if (gapTime <= 12) { navigator.sendBeacon(`/user/logout`, data) } debugger }
|
session的Flashes讲解
flash主要是存储一下临时数据
- 我们调用AddFlash时,会往flash里存储一个键值对.
- 当我们调用Flashs读取了闪存内容后,这个闪存数据就会被删除.(调用Save()方法生效)
结构体验证
用gin框架进行数据验证,可以不用解析数据减少if使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main import ( "fmt" "time" "github.com/gin-gonic/gin" ) type Person struct { Name string `form:"name" binding:"required"` Age int `form:"age" binding:"required"` Birthday time.Time `form:"birthady" time_format:"2006-01-02" time_utc:"1"` } func main() { router := gin.Default() router.GET("/verify", func(ctx *gin.Context) { var person Person if err := ctx.ShouldBind(&person); err != nil { ctx.String(500, fmt.Sprint(err)) return } ctx.String(200, fmt.Sprintf("%#v", person)) }) router.Run() }
|
自定义验证
没看懂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| package main import ( "fmt" "github.com/go-playground/validator/v10" )
type User struct { FirstName string `validate:"required"` Age uint8 `validate:"required,gte=0,lte=130"` Email string `validate:"required,email"` Color string `validate:"iscolor"` Addresses []*Address `validate:"required,dive,required"` } type Address struct { Street string `validate:"required"` City string `validate:"required"` } var validate *validator.Validate func main() { validate = validator.New() validateStruct() validateVariable() } func validateStruct() { address := &Address{ Street: "昆仑山南路", City: "青岛市", } user := &User{ FirstName: "Gary", Age: 33, Email: "[email protected]", Color: "rgba(0,0,0,0)", Addresses: []*Address{address}, } err := validate.Struct(user) if err != nil { if _, ok := err.(*validator.InvalidValidationError); ok { fmt.Println(err) return } for _, err := range err.(validator.ValidationErrors) { fmt.Println(err.Namespace()) fmt.Println(err.Field()) fmt.Println(err.StructNamespace()) fmt.Println(err.StructField()) fmt.Println(err.Tag()) fmt.Println(err.ActualTag()) fmt.Println(err.Kind()) fmt.Println(err.Type()) fmt.Println(err.Value()) fmt.Println(err.Param()) } return } } func validateVariable() { myEmail := "[email protected]" errs := validate.Var(myEmail, "required,email") if errs != nil { fmt.Println(errs) return } }
|
多语言翻译验证
没看懂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| 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" "github.com/go-playground/validator/v10" en_translations "github.com/go-playground/validator/v10/translations/en" zh_translations "github.com/go-playground/validator/v10/translations/zh" zh_tw_translations "github.com/go-playground/validator/v10/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() router := gin.Default() router.GET("/yy", startPage) router.POST("/yy", startPage) router.Run() } func startPage(ctx *gin.Context) { local := ctx.DefaultQuery("locale", "zh") trans, _ := Uni.GetTranslator(local) switch local { case "zh": zh_translations.RegisterDefaultTranslations(Validate, trans) case "en": en_translations.RegisterDefaultTranslations(Validate, trans) case "zh_tw": zh_tw_translations.RegisterDefaultTranslations(Validate, trans) default: zh_translations.RegisterDefaultTranslations(Validate, trans) } Validate.RegisterTranslation("required", trans, func(ut ut.Translator) error { return ut.Add("required", "{0} 必须有一个值!", true) }, func(ut ut.Translator, fe validator.FieldError) string { t, _ := ut.T("required", fe.Field()) return t }) user := User{} ctx.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)) } ctx.String(500, fmt.Sprintf("%#v", sliceErrs)) return } ctx.String(200, fmt.Sprintf("%#v", "user")) }
|
http://localhost:8080/yy?user_name=garywang&tag_line=11&tag_line2=10
日志文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package main import ( "io" "os" "github.com/gin-gonic/gin" ) func main() { gin.DisableConsoleColor() f, _ := os.Create("./log/gin.log") gin.DefaultWriter = io.MultiWriter(f) r := gin.Default() r.GET("/ping", func(ctx *gin.Context) { ctx.String(200, "pong") }) r.Run() }
|
gin验证码
github.com/dchest/captcha
库
web端实现原理
- 提供一个路由,先在session里写入键值对(k->v),把值写在图片上,然后生成图片,显示在浏览器上面
- 前端将图片中的内容发送给后后端,后端根据session中的k取得v,比对校验。如果通过继续下一步的逻辑,失败给出错误提示
api端实现
API接口验证码实现方式类似,可以把键值对存储在起来,验证的时候把键值对传输过来一起校验。这里我只给出了web端的方法,爱动手的小伙伴可以自己尝试一下。
后端