gin进阶
# 表单验证
若要将请求主体绑定到结构体中,请使用模型绑定,目前支持JSON、XML、YAML和标准表单值(foo=bar&boo=baz)的绑定。
Gin使用 go-playground/validator (opens new window) 验证参数,查看完整文档 (opens new window)。
需要在绑定的字段上设置tag,比如,绑定格式为json,需要这样设置
json:"fieldname"
Gin还提供了两套绑定方法:
Must bind
Methods -
Bind
,BindJSON
,BindXML
,BindQuery
,BindYAML
Behavior - 这些方法底层使用
MustBindWith
,如果存在绑定错误,请求将被以下指令中止c.AbortWithError(400, err).SetType(ErrorTypeBind)
,响应状态代码会被设置为400,请求头Content-Type
被设置为text/plain; charset=utf-8
。注意,如果你试图在此之后设置响应代码,将会发出一个警告[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422
,如果你希望更好地控制行为,请使用ShouldBind
相关的方法Should bind
Methods -
ShouldBind
,ShouldBindJSON
,ShouldBindXML
,ShouldBindQuery
,ShouldBindYAML
Behavior - 这些方法底层使用
ShouldBindWith
,如果存在绑定错误,则返回错误,开发人员可以正确处理请求和错误。
当我们使用绑定方法时,Gin会根据Content-Type推断出使用哪种绑定器,如果你确定你绑定的是什么,你可以使用
MustBindWith
或者BindingWith
。你还可以给字段指定特定规则的修饰符,如果一个字段用
binding:"required"
修饰,并且在绑定时该字段的值为空,那么将返回一个错误。demo
// 绑定为json type Login struct { User string `form:"user" json:"user" xml:"user" binding:"required"` Password string `form:"password" json:"password" xml:"password" binding:"required"` } type SignUpParam struct { Age uint8 `json:"age" binding:"gte=1,lte=130"` Name string `json:"name" binding:"required"` Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required"` RePassword string `json:"re_password" binding:"required,eqfield=Password"` } func main() { router := gin.Default() // Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login if err := c.ShouldBindJSON(&json); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if json.User != "manu" || json.Password != "123" { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) return } c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) // Example for binding a HTML form (user=manu&password=123) router.POST("/loginForm", func(c *gin.Context) { var form Login // This will infer what binder to use depending on the content-type header. if err := c.ShouldBind(&form); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if form.User != "manu" || form.Password != "123" { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) return } c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) r.POST("/signup", func(c *gin.Context) { var u SignUpParam if err := c.ShouldBind(&u); err != nil { c.JSON(http.StatusOK, gin.H{ "msg": err.Error(), }) return } // 保存入库等业务逻辑代码... c.JSON(http.StatusOK, "success") }) // Listen and serve on 0.0.0.0:8080 router.Run(":8080")
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"
"reflect"
"strings"
"github.com/gin-gonic/gin"
"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"
en_translations "github.com/go-playground/validator/v10/translations/en"
zh_translations "github.com/go-playground/validator/v10/translations/zh"
)
var trans ut.Translator
type LoginForm struct {
User string `json:"user" binding:"required,min=3,max=10"`
Password string `json:"password" binding:"required"`
}
type SignUpForm struct {
Age uint8 `json:"age" binding:"gte=1,lte=130"`
Name string `json:"name" binding:"required,min=3"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
RePassword string `json:"re_password" binding:"required,eqfield=Password"` //跨字段
}
// 将返回的map的key格式化
func removeTopStruct(fileds map[string]string) map[string]string {
rsp := map[string]string{}
for field, err := range fileds {
rsp[field[strings.Index(field, ".")+1:]] = err
}
return rsp
}
// 翻译
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
})
zhT := zh.New() //中文翻译器
enT := en.New() //英文翻译器
//第一个参数是备用的语言环境,后面的参数是应该支持的语言环境
uni := ut.New(enT, zhT, enT)
trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf("uni.GetTranslator(%s)", locale)
}
switch locale {
case "en":
en_translations.RegisterDefaultTranslations(v, trans)
case "zh":
zh_translations.RegisterDefaultTranslations(v, trans)
default:
en_translations.RegisterDefaultTranslations(v, trans)
}
return
}
return
}
func main() {
//代码侵入性很强 中间件
if err := InitTrans("zh"); err != nil {
fmt.Println("初始化翻译器错误")
return
}
router := gin.Default()
router.POST("/loginJSON", func(c *gin.Context) {
var loginForm LoginForm
if err := c.ShouldBind(&loginForm); err != nil {
errs, ok := err.(validator.ValidationErrors)
if !ok {
c.JSON(http.StatusOK, gin.H{
"msg": err.Error(),
})
}
c.JSON(http.StatusBadRequest, gin.H{
"error": removeTopStruct(errs.Translate(trans)),
})
return
}
c.JSON(http.StatusOK, gin.H{
"msg": "登录成功",
})
})
router.POST("/signup", func(c *gin.Context) {
var signUpFrom SignUpForm
if err := c.ShouldBind(&signUpFrom); err != nil {
fmt.Println(err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"msg": "注册成功",
})
})
_ = router.Run(":8083")
}
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# 自定义校验器
自定义mobile校验器
^1([38][0-9]|14[579]|5[^4]|16[6]|7[1-35-8]|9[189])\d{8}$
form.go
package forms type PassWordLoginForm struct { Mobile string `form:"mobile" json:"mobile" binding:"required,mobile"` //手机号码格式有规范可寻, 自定义validator - mobile PassWord string `form:"password" json:"password" binding:"required,min=3,max=20"` Captcha string `form:"captcha" json:"captcha" binding:"required,min=5,max=5"` CaptchaId string `form:"captcha_id" json:"captcha_id" binding:"required"` }
1
2
3
4
5
6
7
8// validator.go package validator import ( "github.com/go-playground/validator/v10" "regexp" ) // 校验器 func ValidateMobile(f1 validator.FieldLevel) bool { mobile := f1.Field().String() ok, _ := regexp.MatchString(`^1([38][0-9]|14[579]|5[^4]|16[6]|7[1-35-8]|9[189])\d{8}$`, mobile) if !ok { return false } return true }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16main.go
//"github.com/gin-gonic/gin/binding" //myvalidator "mxshop-api/user-web/validator" //注册验证器 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { _ = v.RegisterValidation("mobile", myvalidator.ValidateMobile) // 自定义返回信息 _ = v.RegisterTranslation("mobile", global.Trans, func(ut ut.Translator) error { return ut.Add("mobile", "{0} 非法的手机号码!", true) // see universal-translator for details }, func(ut ut.Translator, fe validator.FieldError) string { t, _ := ut.T("mobile", fe.Field()) return t }) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# grpc的code 转为http的状态码
func HandleGrpcErrorToHttp(err error, c *gin.Context) {
// 将grpc 的code 转为http的状态码
if err != nil {
if e, ok := status.FromError(err); ok {
switch e.Code() {
case codes.NotFound:
c.JSON(http.StatusNotFound, gin.H{
"msg": e.Message(),
})
case codes.InvalidArgument:
c.JSON(http.StatusBadRequest, gin.H{
"msg": "参数错误",
})
case codes.Internal:
c.JSON(http.StatusInternalServerError, gin.H{
"msg": "服务器内部错误",
})
case codes.Unavailable:
c.JSON(http.StatusInternalServerError, gin.H{
"msg": "用户服务不可用",
})
default:
c.JSON(http.StatusInternalServerError, gin.H{
"msg": fmt.Sprintf("其他错误:%s", e.Message()),
})
return
}
}
}
}
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
# 跨域解决
import (
"github.com/gin-gonic/gin"
"net/http"
)
func CORSMiddleware() gin.HandlerFunc {
return func(context *gin.Context) {
context.Writer.Header().Set("Access-Control-Allow-Origin","*")
context.Writer.Header().Set("Access-Control-Allow-Origin","*")
context.Writer.Header().Set("Access-Control-Max-Age","86400")
context.Writer.Header().Set("Access-Control-Allow-Methods","*")
context.Writer.Header().Set("Access-Control-Allow-Headers","*")
context.Writer.Header().Set("Access-Control-Allow-Credentials","true")
if context.Request.Method == http.MethodOptions{
context.AbortWithStatus(http.StatusOK)
}else{
context.Next()
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 图片验证码
- doc: https://mojotv.cn/go/refactor-base64-captcha
import (
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
"go.uber.org/zap"
"net/http"
)
var store = base64Captcha.DefaultMemStore
func GetCaptcha(ctx *gin.Context) {
driver := base64Captcha.NewDriverDigit(80, 240, 5, 0.7, 80)
cp := base64Captcha.NewCaptcha(driver, store)
id, b64s, err := cp.Generate()
if err != nil {
zap.S().Errorf("生成验证码错误:%s", err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": "验证码错误",
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"captchaId": id,
"picPath": b64s,
})
}
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 (
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi"
)
func main(){
client, err := dysmsapi.NewClientWithAccessKey("cn-beijing", "xxxx", "xxx")
if err != nil {
panic(err)
}
request := requests.NewCommonRequest()
request.Method = "POST"
request.Scheme = "https" // https | http
request.Domain = "dysmsapi.aliyuncs.com"
request.Version = "2017-05-25"
request.ApiName = "SendSms"
request.QueryParams["RegionId"] = "cn-beijing"
request.QueryParams["PhoneNumbers"] = "xxx" //手机号
request.QueryParams["SignName"] = "xxx" //阿里云验证过的项目名 自己设置
request.QueryParams["TemplateCode"] = "xxx" //阿里云的短信模板号 自己设置
request.QueryParams["TemplateParam"] = "{\"code\":" + "777777" + "}" //短信模板中的验证码内容 自己生成 之前试过直接返回,但是失败,加上code成功。
response, err := client.ProcessCommonRequest(request)
fmt.Print( client.DoAction(request, response))
// fmt.Print(response)
if err != nil {
fmt.Print(err.Error())
}
fmt.Printf("response is %#v\n", response)
//json数据解析
}
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