mirror of
https://gitee.com/dromara/mayfly-go
synced 2026-02-17 09:45:36 +08:00
refactor: 后端validator校验错误转译
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
41
server/pkg/validatorx/pattern.go
Normal file
41
server/pkg/validatorx/pattern.go
Normal 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())
|
||||
}
|
||||
92
server/pkg/validatorx/validatorx.go
Normal file
92
server/pkg/validatorx/validatorx.go
Normal 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, ", ")
|
||||
}
|
||||
Reference in New Issue
Block a user