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, ", ")
+}