gin框架(表单验证,JWT,跨域)


一、表单验证

1.1登录和注册表单验证的基础

注意规范,binding后面的参数不要留空格,
声明一个Login对象,用shouldBind()绑定,返回的err值来判断表单

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"` //跨字段
}

type Login struct {
	User 		string `json:"user" binding:"required"`
	Password    string `json:"password" binding:"required,min=3,max=10"`
}

var json Login
if err := c.ShouldBind(&json); err != nil {
	c.JSON(http.StatusBadRequest, gin.H{
			"error": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"msg": "你成功登录",
	})

1.2表单验证错误信息中英翻译

通用的翻译器函数

import (
	"fmt"
	"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"
	"net/http"
)
var trans ut.Translator //设置为全局,trans用在路由里
//翻译中英文函数,
func InitTrans(local string)(err error){
	if v,ok :=binding.Validator.Engine().(*validator.Validate); ok{
		 zhT :=zh.New()
		 enT :=en.New()
		 uni :=ut.New(enT,zhT,enT)
		 trans ,ok =uni.GetTranslator(local)
		 if !ok{
		 	return fmt.Errorf("uni.GetTranslator(%s)",local)
		 }
		switch local {
		case "en":
			en_translations.RegisterDefaultTranslations(v,trans)
		case "zh":
			zh_translations.RegisterDefaultTranslations(v,trans)
		default:
			en_translations.RegisterDefaultTranslations(v,trans)
		}
		return
	}
	return
}

调用函数初始化,实现翻译,分三步走

//第一步初始化翻译函数
if err :=InitTrans("en");err !=nil{
		fmt.Println("初始化翻译错误")
	}
//第二步,在绑定出错时,加个errs,ok :=err.(validator.ValidationErrors)
//第三步,返回错误信息时,套个翻译器errs.Translate(trans)。注意trans要设置全局变量
if err := c.ShouldBind(&json); 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": errs.Translate(trans),
			})

完整代码示例

func main() {
	//初始化翻译器
	if err :=InitTrans("en");err !=nil{
		fmt.Println("初始化翻译错误")
	}
	router := gin.Default()
	router.POST("/loginBindJSON", func(c *gin.Context) {
		var json Login
		if err := c.ShouldBind(&json); 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": errs.Translate(trans),
			})
			return
		}
		c.JSON(http.StatusOK, gin.H{
			"msg": "你成功登录",
		})
		return

	})
	router.POST("/signUpBindJSON", func(c *gin.Context) {
		var signForm SignUpForm
		if err := c.ShouldBind(&signForm); 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": errs.Translate(trans),
			})
			return
		}
		c.JSON(http.StatusOK, gin.H{
			"msg": "你成功登录",
		})
		return


	})

	_ = router.Run(":8080")
}

1.3 表单错误返回数据的格式化细节

上述情况返回的数据为:字母开头是大写的
在这里插入图片描述

//在初始化翻译器的函数加这一段。
//注册一个json的tag自定义方法
		v.RegisterTagNameFunc(func(fld reflect.StructField)string{
			name :=strings.SplitN(fld.Tag.Get("json"),",",2)[0]
			if name =="-"{
				return ""
			}
			return name
		})
		//注册一个json的tag自定义方法

在这里插入图片描述
再用一个函数removeTopstruct来对返回值切片

func removeTopStruct(fields map[string]string) map[string]string{
	rsp :=map[string]string{}
	for field,err :=range fields{
		rsp[field[strings.Index(field,".")+1:]] =err
	}
	return rsp
}
//返回表单验证错误时调用该函数
c.JSON(http.StatusBadRequest, gin.H{
	"error": removeTopStruct(errs.Translate(trans)),
    })

在这里插入图片描述

1.4完整代码示例

package main

import (
	"fmt"
	"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"
	"net/http"
	"reflect"
	"strings"
)

type Login struct {
	User     string `json:"user" binding:"required"`
	Password string `json:"password" binding:"required,min=3,max=10"`
}
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"` //跨字段
}
func removeTopStruct(fields map[string]string) map[string]string{
	rsp :=map[string]string{}
	for field,err :=range fields{
		rsp[field[strings.Index(field,".")+1:]] =err
	}
	return rsp
}
var trans ut.Translator
func InitTrans(local string)(err error){
	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
		})
		//注册一个json的tag自定义方法
		 zhT :=zh.New()
		 enT :=en.New()
		 uni :=ut.New(enT,zhT,enT)
		 trans ,ok =uni.GetTranslator(local)
		 if !ok{
		 	return fmt.Errorf("uni.GetTranslator(%s)",local)
		 }
		switch local {
		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("初始化翻译错误")
	}
	router := gin.Default()
	router.POST("/loginBindJSON", func(c *gin.Context) {
		var json Login
		if err := c.ShouldBind(&json); 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": "你成功登录",
		})
		return

	})
	router.POST("/signUpBindJSON", func(c *gin.Context) {
		var signForm SignUpForm
		if err := c.ShouldBind(&signForm); 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": "你成功登录",
		})
		return


	})

	_ = router.Run(":8080")
}

1.5 注册自定制的验证器

样例:手机号码是否正规的验证
①validators.go文件写个函数制定验证规则

package validator

import (
	"github.com/go-playground/validator/v10"
	"regexp"
)

func ValidateMobile(fl validator.FieldLevel) bool{
	mobile :=fl.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
}

②在mian.go注册该函数和binding的参数

// 注册验证器
	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)
		},func(ut ut.Translator,fe validator.FieldError)string{
			t,_ :=ut.T("mobile",fe.Field())
			return t
		})
	}//注册验证器结束

③把binding参数添加到form结构体中

package forms

type PassWordLoginForm struct {
	Mobile string `json:"mobile" binding:"required,mobile"`
	PassWord string `json:"password" binding:"required,min=3,max=20"`
}

二、JWT(json web token)

2.1.引入两个包

①包models文件里的request.go,需要安装github.com/dgrijalva/jwt-go第三方

package models

import (
	"github.com/dgrijalva/jwt-go"
)

type CustomClaims struct {
	ID          uint
	NickName    string
	AuthorityId uint
	jwt.StandardClaims
}

②包jwt.go,放在middlewares里

package middlewares

import (
	"errors"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"mxshop_api/user-web/global" //修改生成key密钥
	"mxshop_api/user-web/models" // 包1的位置
	"net/http"
	"time"
)

func JWTAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localSstorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
		token := c.Request.Header.Get("x-token")
		if token == "" {
			c.JSON(http.StatusUnauthorized, map[string]string{
				"msg":"请登录",
			})
			c.Abort()
			return
		}
		j := NewJWT()
		// parseToken 解析token包含的信息
		claims, err := j.ParseToken(token)
		if err != nil {
			if err == TokenExpired {
				if err == TokenExpired {
					c.JSON(http.StatusUnauthorized, map[string]string{
						"msg":"授权已过期",
					})
					c.Abort()
					return
				}
			}

			c.JSON(http.StatusUnauthorized, "未登陆")
			c.Abort()
			return
		}
		c.Set("claims", claims)
		c.Set("userId", claims.ID)
		c.Next()
	}
}

type JWT struct {
	SigningKey []byte
}

var (
	TokenExpired     = errors.New("Token is expired")
	TokenNotValidYet = errors.New("Token not active yet")
	TokenMalformed   = errors.New("That's not even a token")
	TokenInvalid     = errors.New("Couldn't handle this token:")
)

func NewJWT() *JWT {
	return &JWT{
		[]byte(global.ServerConfig.JWTInfo.SigningKey), //可以设置过期时间,全局的密钥。修改位置
	}
}

// 创建一个token
func (j *JWT) CreateToken(claims models.CustomClaims) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(j.SigningKey)
}

// 解析 token
func (j *JWT) ParseToken(tokenString string) (*models.CustomClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &models.CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) {
		return j.SigningKey, nil
	})
	if err != nil {
		if ve, ok := err.(*jwt.ValidationError); ok {
			if ve.Errors&jwt.ValidationErrorMalformed != 0 {
				return nil, TokenMalformed
			} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
				// Token is expired
				return nil, TokenExpired
			} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
				return nil, TokenNotValidYet
			} else {
				return nil, TokenInvalid
			}
		}
	}
	if token != nil {
		if claims, ok := token.Claims.(*models.CustomClaims); ok && token.Valid {
			return claims, nil
		}
		return nil, TokenInvalid

	} else {
		return nil, TokenInvalid

	}

}

// 更新token
func (j *JWT) RefreshToken(tokenString string) (string, error) {
	jwt.TimeFunc = func() time.Time {
		return time.Unix(0, 0)
	}
	token, err := jwt.ParseWithClaims(tokenString, &models.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return j.SigningKey, nil
	})
	if err != nil {
		return "", err
	}
	if claims, ok := token.Claims.(*models.CustomClaims); ok && token.Valid {
		jwt.TimeFunc = time.Now
		claims.StandardClaims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix()
		return j.CreateToken(*claims)
	}
	return "", TokenInvalid
}

2.2全局global文件设置密钥

全局global文件设置密钥key
随机生成密钥网站

https://suijimimashengcheng.51240.com/

2.3 密码正确时,返回token数据

if passRsp.Success{
				//生成token
				j :=middlewares.NewJWT()
				claims :=models.CustomClaims{
					ID:             uint(rsp.Id),
					NickName:       rsp.NickName,
					AuthorityId:    uint(rsp.Role),
					StandardClaims: jwt.StandardClaims{
						NotBefore: time.Now().Unix(), //生成的生效时间
						ExpiresAt: time.Now().Unix()+60*60*24*30,
						Issuer: "imooc",
					},
				}
				token,err :=j.CreateToken(claims)
				if err != nil {
					c.JSON(http.StatusInternalServerError,gin.H{
						"msg":"生成的token失败",
					})
					return
				}
				c.JSON(http.StatusOK,gin.H{
					"id":rsp.Id,
					"nick_name":rsp.NickName,
					"token":token,
					"expired_at":(time.Now().Unix()+60*60*24*30)*1000,
				})

三、跨域的配置

package middlewares

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

func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method

		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token, x-token")
		c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH, PUT")
		c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
		c.Header("Access-Control-Allow-Credentials", "true")

		if method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
		}
	}
}

然后注册到中间件即可

Router.Use(middlewares.Cors())
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值