From c2ee4f9955c8be05cf2268a91192e97e4c3ab1d4 Mon Sep 17 00:00:00 2001 From: "meilin.huang" <954537473@qq.com> Date: Mon, 31 Jul 2023 17:34:32 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=90=8E=E7=AB=AFvalidator?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E9=94=99=E8=AF=AF=E8=BD=AC=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mayfly_go_web/package.json | 2 +- mayfly_go_web/src/common/pattern.ts | 4 + .../views/login/component/AccountLogin.vue | 10 +- .../src/views/system/account/AccountEdit.vue | 20 +++- mayfly_go_web/yarn.lock | 8 +- server/initialize/initialize.go | 2 +- .../machine/{initialize => init}/init.go | 2 +- server/internal/sys/api/form/account.go | 6 +- server/pkg/ginx/ginx.go | 16 +++- server/pkg/logger/logger.go | 17 ++-- server/pkg/req/log_handler.go | 6 +- server/pkg/req/token.go | 24 +++-- server/pkg/starter/run.go | 8 +- server/pkg/validatorx/pattern.go | 41 +++++++++ server/pkg/validatorx/validatorx.go | 92 +++++++++++++++++++ 15 files changed, 216 insertions(+), 42 deletions(-) create mode 100644 mayfly_go_web/src/common/pattern.ts rename server/internal/machine/{initialize => init}/init.go (85%) create mode 100644 server/pkg/validatorx/pattern.go create mode 100644 server/pkg/validatorx/validatorx.go diff --git a/mayfly_go_web/package.json b/mayfly_go_web/package.json index ee438291..b9813f7a 100644 --- a/mayfly_go_web/package.json +++ b/mayfly_go_web/package.json @@ -23,7 +23,7 @@ "monaco-sql-languages": "^0.11.0", "monaco-themes": "^0.4.4", "nprogress": "^0.2.0", - "pinia": "^2.1.4", + "pinia": "^2.1.6", "qrcode.vue": "^3.4.0", "screenfull": "^6.0.2", "sortablejs": "^1.13.0", diff --git a/mayfly_go_web/src/common/pattern.ts b/mayfly_go_web/src/common/pattern.ts new file mode 100644 index 00000000..7c0943aa --- /dev/null +++ b/mayfly_go_web/src/common/pattern.ts @@ -0,0 +1,4 @@ +export const AccountUsernamePattern = { + pattern: /^[a-zA-Z0-9_]{5,20}$/g, + message: '只允许输入5-20位大小写字母、数字、下划线', +}; diff --git a/mayfly_go_web/src/views/login/component/AccountLogin.vue b/mayfly_go_web/src/views/login/component/AccountLogin.vue index 5fd35804..4b46cb9b 100644 --- a/mayfly_go_web/src/views/login/component/AccountLogin.vue +++ b/mayfly_go_web/src/views/login/component/AccountLogin.vue @@ -138,6 +138,7 @@ import { letterAvatar } from '@/common/utils/string'; import { useUserInfo } from '@/store/userInfo'; import QrcodeVue from 'qrcode.vue'; import { personApi } from '@/views/personal/api'; +import { AccountUsernamePattern } from '@/common/pattern'; const rules = { username: [{ required: true, message: '请输入用户名', trigger: 'blur' }], @@ -205,7 +206,14 @@ const state = reactive({ name: '', }, rules: { - username: [{ required: true, message: '请输入用户名', trigger: 'blur' }], + username: [ + { required: true, message: '请输入用户名', trigger: 'blur' }, + { + pattern: AccountUsernamePattern.pattern, + message: AccountUsernamePattern.message, + trigger: ['blur'], + }, + ], name: [{ required: true, message: '请输入姓名', trigger: 'blur' }], }, }, diff --git a/mayfly_go_web/src/views/system/account/AccountEdit.vue b/mayfly_go_web/src/views/system/account/AccountEdit.vue index d6244e31..f6af246b 100755 --- a/mayfly_go_web/src/views/system/account/AccountEdit.vue +++ b/mayfly_go_web/src/views/system/account/AccountEdit.vue @@ -2,11 +2,17 @@
- - + + - - + + @@ -27,6 +33,7 @@ import { toRefs, reactive, watch, ref } from 'vue'; import { accountApi } from '../api'; import { ElMessage } from 'element-plus'; +import { AccountUsernamePattern } from '@/common/pattern'; const props = defineProps({ visible: { @@ -59,6 +66,11 @@ const rules = { message: '请输入用户名', trigger: ['change', 'blur'], }, + { + pattern: AccountUsernamePattern.pattern, + message: AccountUsernamePattern.message, + trigger: ['blur'], + }, ], }; diff --git a/mayfly_go_web/yarn.lock b/mayfly_go_web/yarn.lock index c4d66d7d..32d9dcfa 100644 --- a/mayfly_go_web/yarn.lock +++ b/mayfly_go_web/yarn.lock @@ -1558,10 +1558,10 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: resolved "https://registry.nlark.com/picomatch/download/picomatch-2.3.0.tgz?cache=0&sync_timestamp=1621648246651&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fpicomatch%2Fdownload%2Fpicomatch-2.3.0.tgz" integrity sha1-8fBh3o9qS/AiiS4tEoI0+5gwKXI= -pinia@^2.1.4: - version "2.1.4" - resolved "https://registry.npmmirror.com/pinia/-/pinia-2.1.4.tgz#a642adfe6208e10c36d3dc16184a91064788142a" - integrity sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ== +pinia@^2.1.6: + version "2.1.6" + resolved "https://registry.npmmirror.com/pinia/-/pinia-2.1.6.tgz#e88959f14b61c4debd9c42d0c9944e2875cbe0fa" + integrity sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ== dependencies: "@vue/devtools-api" "^6.5.0" vue-demi ">=0.14.5" diff --git a/server/initialize/initialize.go b/server/initialize/initialize.go index 7ebaf724..7380a461 100644 --- a/server/initialize/initialize.go +++ b/server/initialize/initialize.go @@ -1,6 +1,6 @@ package initialize -import machineInit "mayfly-go/internal/machine/initialize" +import machineInit "mayfly-go/internal/machine/init" func InitOther() { machineInit.Init() diff --git a/server/internal/machine/initialize/init.go b/server/internal/machine/init/init.go similarity index 85% rename from server/internal/machine/initialize/init.go rename to server/internal/machine/init/init.go index cb6e1f81..1e458a52 100644 --- a/server/internal/machine/initialize/init.go +++ b/server/internal/machine/init/init.go @@ -1,4 +1,4 @@ -package initialize +package init import "mayfly-go/internal/machine/application" diff --git a/server/internal/sys/api/form/account.go b/server/internal/sys/api/form/account.go index 42fb0f06..dc8ddf70 100644 --- a/server/internal/sys/api/form/account.go +++ b/server/internal/sys/api/form/account.go @@ -2,14 +2,14 @@ package form type AccountCreateForm struct { Id uint64 `json:"id"` - Name string `json:"name" binding:"required"` - Username string `json:"username" binding:"required,min=4,max=16"` + Name string `json:"name" binding:"required,max=16"` + Username string `json:"username" binding:"pattern=account_username"` Password string `json:"password"` } type AccountUpdateForm struct { Name string `json:"name" binding:"max=16"` // 姓名 - Username string `json:"username" binding:"max=20"` + Username string `json:"username" binding:"omitempty,pattern=account_username"` Password *string `json:"password"` } diff --git a/server/pkg/ginx/ginx.go b/server/pkg/ginx/ginx.go index b3997782..e15dfa31 100644 --- a/server/pkg/ginx/ginx.go +++ b/server/pkg/ginx/ginx.go @@ -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 +} diff --git a/server/pkg/logger/logger.go b/server/pkg/logger/logger.go index 5e07f23e..d4c07841 100644 --- a/server/pkg/logger/logger.go +++ b/server/pkg/logger/logger.go @@ -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{} diff --git a/server/pkg/req/log_handler.go b/server/pkg/req/log_handler.go index 533e67ce..d54f34d7 100644 --- a/server/pkg/req/log_handler.go +++ b/server/pkg/req/log_handler.go @@ -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 } diff --git a/server/pkg/req/token.go b/server/pkg/req/token.go index 86b0c687..2529f443 100644 --- a/server/pkg/req/token.go +++ b/server/pkg/req/token.go @@ -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 diff --git a/server/pkg/starter/run.go b/server/pkg/starter/run.go index 14f2d875..549460bc 100644 --- a/server/pkg/starter/run.go +++ b/server/pkg/starter/run.go @@ -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() diff --git a/server/pkg/validatorx/pattern.go b/server/pkg/validatorx/pattern.go new file mode 100644 index 00000000..e94dc639 --- /dev/null +++ b/server/pkg/validatorx/pattern.go @@ -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()) +} diff --git a/server/pkg/validatorx/validatorx.go b/server/pkg/validatorx/validatorx.go new file mode 100644 index 00000000..0d16b47b --- /dev/null +++ b/server/pkg/validatorx/validatorx.go @@ -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, ", ") +}