refactor: 后端validator校验错误转译

This commit is contained in:
meilin.huang
2023-07-31 17:34:32 +08:00
parent 2479412334
commit c2ee4f9955
15 changed files with 216 additions and 42 deletions

View File

@@ -6,17 +6,19 @@ import (
"mayfly-go/pkg/global"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/structx"
"mayfly-go/pkg/validatorx"
"net/http"
"runtime/debug"
"strconv"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
)
// 绑定并校验请求结构体参数
func BindJsonAndValid[T any](g *gin.Context, data T) T {
if err := g.ShouldBindJSON(data); err != nil {
panic(biz.NewBizErr(err.Error()))
panic(ConvBindValidationError(data, err))
} else {
return data
}
@@ -32,7 +34,7 @@ func BindJsonAndCopyTo[T any](g *gin.Context, form any, toStruct T) T {
// 绑定查询字符串到指定结构体
func BindQuery[T any](g *gin.Context, data T) T {
if err := g.BindQuery(data); err != nil {
panic(biz.NewBizErr(err.Error()))
panic(ConvBindValidationError(data, err))
} else {
return data
}
@@ -41,7 +43,7 @@ func BindQuery[T any](g *gin.Context, data T) T {
// 绑定查询字符串到指定结构体,并将分页信息也返回
func BindQueryAndPage[T any](g *gin.Context, data T) (T, *model.PageParam) {
if err := g.BindQuery(data); err != nil {
panic(biz.NewBizErr(err.Error()))
panic(ConvBindValidationError(data, err))
} else {
return data, GetPageParam(g)
}
@@ -111,3 +113,11 @@ func ErrorRes(g *gin.Context, err any) {
global.Log.Error(t)
}
}
// 转换参数校验错误为业务异常错误
func ConvBindValidationError(data any, err error) error {
if e, ok := err.(validator.ValidationErrors); ok {
return biz.NewBizErrCode(403, validatorx.Translate2Str(data, e))
}
return err
}

View File

@@ -11,16 +11,15 @@ import (
"github.com/sirupsen/logrus"
)
var Log = logrus.New()
func Init() {
Log.SetFormatter(new(LogFormatter))
Log.SetReportCaller(true)
logger := logrus.New()
logger.SetFormatter(new(LogFormatter))
logger.SetReportCaller(true)
logConf := config.Conf.Log
// 如果不存在日志配置信息则默认debug级别
if logConf == nil {
Log.SetLevel(logrus.DebugLevel)
logger.SetLevel(logrus.DebugLevel)
return
}
@@ -30,9 +29,9 @@ func Init() {
if err != nil {
panic(fmt.Sprintf("日志级别不存在: %s", level))
}
Log.SetLevel(l)
logger.SetLevel(l)
} else {
Log.SetLevel(logrus.DebugLevel)
logger.SetLevel(logrus.DebugLevel)
}
if logFile := logConf.File; logFile != nil {
@@ -42,10 +41,10 @@ func Init() {
panic(fmt.Sprintf("创建日志文件失败: %s", err.Error()))
}
Log.Out = file
logger.Out = file
}
global.Log = Log
global.Log = logger
}
type LogFormatter struct{}

View File

@@ -3,7 +3,7 @@ package req
import (
"fmt"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/logger"
"mayfly-go/pkg/global"
"mayfly-go/pkg/utils/anyx"
"mayfly-go/pkg/utils/stringx"
@@ -63,10 +63,10 @@ func LogHandler(rc *Ctx) error {
go saveLog(rc)
}
if err := rc.Err; err != nil {
logger.Log.WithFields(lfs).Error(getErrMsg(rc, err))
global.Log.WithFields(lfs).Error(getErrMsg(rc, err))
return nil
}
logger.Log.WithFields(lfs).Info(getLogMsg(rc))
global.Log.WithFields(lfs).Info(getLogMsg(rc))
return nil
}

View File

@@ -13,9 +13,18 @@ import (
"github.com/golang-jwt/jwt/v5"
)
// 初始化jwt key与expire time等
func InitTokenConfig() {
JwtKey = config.Conf.Jwt.Key
ExpTime = config.Conf.Jwt.ExpireTime
if ExpTime == 0 {
JwtKey = config.Conf.Jwt.Key
ExpTime = config.Conf.Jwt.ExpireTime
// 如果配置文件中的jwt key为空则随机生成字符串
if JwtKey == "" {
JwtKey = stringx.Rand(32)
global.Log.Infof("config.yml未配置jwt.key, 随机生成key为: %s", JwtKey)
}
}
}
var (
@@ -25,6 +34,8 @@ var (
// 创建用户token
func CreateToken(userId uint64, username string) string {
InitTokenConfig()
// 带权限创建令牌
// 设置有效期过期需要重新登录获取token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
@@ -33,14 +44,9 @@ func CreateToken(userId uint64, username string) string {
"exp": time.Now().Add(time.Minute * time.Duration(ExpTime)).Unix(),
})
// 如果配置文件中的jwt key为空则随机生成字符串
if JwtKey == "" {
JwtKey = stringx.Rand(32)
global.Log.Infof("config.yml未配置jwt.key, 随机生成key为: %s", JwtKey)
}
// 使用自定义字符串加密 and get the complete encoded token as a string
tokenString, err := token.SignedString([]byte(JwtKey))
biz.ErrIsNil(err, "token创建失败")
biz.ErrIsNilAppendErr(err, "token创建失败: %s")
return tokenString
}
@@ -49,6 +55,8 @@ func ParseToken(tokenStr string) (*model.LoginAccount, error) {
if tokenStr == "" {
return nil, errors.New("token error")
}
InitTokenConfig()
// Parse token
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (any, error) {
return []byte(JwtKey), nil

View File

@@ -6,7 +6,7 @@ import (
"mayfly-go/pkg/config"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logger"
"mayfly-go/pkg/req"
"mayfly-go/pkg/validatorx"
)
func RunWebServer() {
@@ -16,9 +16,6 @@ func RunWebServer() {
// 初始化日志配置信息
logger.Init()
// 初始化jwt key与expire time等
req.InitTokenConfig()
// 打印banner
printBanner()
@@ -33,6 +30,9 @@ func RunWebServer() {
global.Log.Fatalf("数据库升级失败: %v", err)
}
// 参数校验器初始化、如错误提示中文转译等
validatorx.Init()
// 初始化其他需要启动时运行的方法
initialize.InitOther()

View File

@@ -0,0 +1,41 @@
package validatorx
import (
"mayfly-go/pkg/global"
"regexp"
"github.com/go-playground/validator/v10"
)
const CustomPatternTagName = "pattern"
var (
regexpMap map[string]*regexp.Regexp
patternErrMsg map[string]string
)
// 注册自定义正则表达式校验规则
func RegisterCustomPatterns() {
// 账号用户名校验
RegisterPattern("account_username", "^[a-zA-Z0-9_]{5,20}$", "只允许输入5-20位大小写字母、数字、下划线")
}
// 注册自定义正则表达式
func RegisterPattern(patternName string, regexpStr string, errMsg string) {
if regexpMap == nil {
regexpMap = make(map[string]*regexp.Regexp, 0)
patternErrMsg = make(map[string]string)
}
regexpMap[patternName] = regexp.MustCompile(regexpStr)
patternErrMsg[patternName] = errMsg
}
func patternValidFunc(f validator.FieldLevel) bool {
reg := regexpMap[f.Param()]
if reg == nil {
global.Log.Warnf("%s的正则校验规则不存在!", f.Param())
return false
}
return reg.MatchString(f.Field().String())
}

View File

@@ -0,0 +1,92 @@
package validatorx
import (
"mayfly-go/pkg/utils/structx"
"reflect"
"strings"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zh_trans "github.com/go-playground/validator/v10/translations/zh"
)
var (
trans ut.Translator
)
func Init() {
// 获取gin的校验器
validate, ok := binding.Validator.Engine().(*validator.Validate)
if !ok {
return
}
// 修改返回字段key的格式
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
// 如果存在校验错误提示消息,则使用字段名,后续需要通过该字段名获取相应错误消息
if _, ok := fld.Tag.Lookup("valid_msg"); ok {
return fld.Name
}
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
// 注册翻译器
zh := zh.New()
uni := ut.New(zh, zh)
trans, _ = uni.GetTranslator("zh")
// 注册翻译器
zh_trans.RegisterDefaultTranslations(validate, trans)
// 注册自定义校验器
validate.RegisterValidation(CustomPatternTagName, patternValidFunc)
// 注册自定义正则校验规则
RegisterCustomPatterns()
}
// Translate 翻译错误信息
func Translate(data any, err error) map[string][]string {
var result = make(map[string][]string)
errors := err.(validator.ValidationErrors)
for _, err := range errors {
fieldName := err.Field()
// 判断该字段是否设置了自定义的错误描述信息,存在则使用自定义错误信息进行提示
if field, ok := structx.IndirectType(reflect.TypeOf(data)).FieldByName(fieldName); ok {
if errMsg, ok := field.Tag.Lookup("valid_msg"); ok {
result[fieldName] = append(result[fieldName], errMsg)
break
}
}
// 如果是自定义正则校验规则,则使用自定义的错误描述信息
if err.Tag() == CustomPatternTagName {
result[fieldName] = append(result[fieldName], fieldName+patternErrMsg[err.Param()])
break
}
result[fieldName] = append(result[fieldName], err.Translate(trans))
}
return result
}
// Translate 翻译错误信息为字符串
func Translate2Str(data any, err error) string {
res := Translate(data, err)
errMsgs := make([]string, 0)
for _, v := range res {
errMsgs = append(errMsgs, v...)
}
return strings.Join(errMsgs, ", ")
}