This commit is contained in:
2025-07-20 20:53:26 +08:00
parent 83bf9aea7d
commit 8ad1d7288e
158 changed files with 18156 additions and 13188 deletions

View File

@@ -0,0 +1,230 @@
# Validator 验证器包
这是一个功能完整的验证器包提供了HTTP请求验证和业务逻辑验证的完整解决方案。
## 📁 包结构
```
internal/shared/validator/
├── validator.go # HTTP请求验证器主逻辑
├── custom_validators.go # 自定义验证器实现
├── translations.go # 中文翻译
├── business.go # 业务逻辑验证接口
└── README.md # 使用说明
```
## 🚀 特性
### 1. HTTP请求验证
- 自动绑定和验证请求体、查询参数、路径参数
- 中文错误消息
- 集成到Gin框架
- 统一的错误响应格式
### 2. 业务逻辑验证
- 独立的业务验证器
- 可在任何地方调用的验证方法
- 丰富的预定义验证规则
### 3. 自定义验证规则
- 手机号验证 (`phone`)
- 强密码验证 (`strong_password`)
- 用户名验证 (`username`)
- 统一社会信用代码验证 (`social_credit_code`)
- 身份证号验证 (`id_card`)
- UUID验证 (`uuid`)
- URL验证 (`url`)
- 产品代码验证 (`product_code`)
- 价格验证 (`price`)
- 排序方向验证 (`sort_order`)
## 📖 使用方法
### 1. HTTP请求验证
在Handler中使用
```go
type UserHandler struct {
validator interfaces.RequestValidator
// ... 其他依赖
}
func (h *UserHandler) Register(c *gin.Context) {
var cmd commands.RegisterUserCommand
// 自动绑定和验证请求体
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
return // 验证失败会自动返回错误响应
}
// 验证查询参数
var query queries.UserListQuery
if err := h.validator.ValidateQuery(c, &query); err != nil {
return
}
// 验证路径参数
var param queries.UserIDParam
if err := h.validator.ValidateParam(c, &param); err != nil {
return
}
// 继续业务逻辑...
}
```
### 2. DTO定义
在DTO中使用验证标签
```go
type RegisterUserCommand struct {
Phone string `json:"phone" binding:"required,phone"`
Password string `json:"password" binding:"required,strong_password"`
ConfirmPassword string `json:"confirm_password" binding:"required,eqfield=Password"`
Email string `json:"email" binding:"omitempty,email"`
Username string `json:"username" binding:"required,username"`
}
type EnterpriseInfoCommand struct {
CompanyName string `json:"company_name" binding:"required,min=2,max=100"`
UnifiedSocialCode string `json:"unified_social_code" binding:"required,social_credit_code"`
LegalPersonName string `json:"legal_person_name" binding:"required,min=2,max=20"`
LegalPersonID string `json:"legal_person_id" binding:"required,id_card"`
LegalPersonPhone string `json:"legal_person_phone" binding:"required,phone"`
}
type ProductCommand struct {
Name string `json:"name" binding:"required,min=2,max=100"`
Code string `json:"code" binding:"required,product_code"`
Price float64 `json:"price" binding:"price,min=0"`
CategoryID string `json:"category_id" binding:"required,uuid"`
WebsiteURL string `json:"website_url" binding:"omitempty,url"`
}
```
### 3. 业务逻辑验证
在Service中使用
```go
import "tyapi-server/internal/shared/validator"
type UserService struct {
businessValidator *validator.BusinessValidator
}
func NewUserService() *UserService {
return &UserService{
businessValidator: validator.NewBusinessValidator(),
}
}
func (s *UserService) ValidateUserData(phone, password string) error {
// 验证手机号
if err := s.businessValidator.ValidatePhone(phone); err != nil {
return fmt.Errorf("手机号验证失败: %w", err)
}
// 验证密码强度
if err := s.businessValidator.ValidatePassword(password); err != nil {
return fmt.Errorf("密码验证失败: %w", err)
}
// 验证结构体
userData := UserData{Phone: phone, Password: password}
if err := s.businessValidator.ValidateStruct(userData); err != nil {
return fmt.Errorf("用户数据验证失败: %w", err)
}
return nil
}
func (s *UserService) ValidateEnterpriseInfo(code, idCard string) error {
// 验证统一社会信用代码
if err := s.businessValidator.ValidateSocialCreditCode(code); err != nil {
return err
}
// 验证身份证号
if err := s.businessValidator.ValidateIDCard(idCard); err != nil {
return err
}
return nil
}
```
## 🔧 可用的验证规则
### 标准验证规则
- `required` - 必填
- `omitempty` - 可为空
- `min=n` - 最小长度/值
- `max=n` - 最大长度/值
- `len=n` - 固定长度
- `email` - 邮箱格式
- `oneof=a b c` - 枚举值
- `eqfield=Field` - 字段相等
- `gt=n` - 大于某值
### 自定义验证规则
- `phone` - 中国手机号 (1[3-9]xxxxxxxxx)
- `strong_password` - 强密码 (8位以上包含大小写字母和数字)
- `username` - 用户名 (字母开头3-20位字母数字下划线)
- `social_credit_code` - 统一社会信用代码 (18位)
- `id_card` - 身份证号 (18位)
- `uuid` - UUID格式
- `url` - URL格式
- `product_code` - 产品代码 (3-50位字母数字下划线连字符)
- `price` - 价格 (非负数)
- `sort_order` - 排序方向 (asc/desc)
## 🌐 错误消息
所有错误消息都已本地化为中文:
```json
{
"code": 422,
"message": "请求参数验证失败",
"data": null,
"errors": {
"phone": ["手机号必须是有效的手机号"],
"password": ["密码强度不足必须包含大小写字母和数字且不少于8位"],
"confirm_password": ["确认密码必须与密码一致"]
}
}
```
## 🔄 依赖注入
`container.go` 中已配置:
```go
fx.Provide(
validator.NewRequestValidator, // HTTP请求验证器
),
```
业务验证器可以在需要时创建:
```go
bv := validator.NewBusinessValidator()
```
## 📝 最佳实践
1. **DTO验证**: 在DTO中使用binding标签进行声明式验证
2. **业务验证**: 在业务逻辑中使用BusinessValidator进行程序化验证
3. **错误处理**: 验证错误会自动返回统一格式的HTTP响应
4. **性能**: 验证器实例可以复用,建议在依赖注入中管理
## 🧪 测试示例
参考 `examples/validator_usage.go` 文件中的完整使用示例。
---
这个验证器包提供了完整的验证解决方案既可以用于HTTP请求的自动验证也可以在业务逻辑中进行程序化验证确保数据的完整性和正确性。

View File

@@ -0,0 +1,239 @@
package validator
import (
"fmt"
"net/url"
"regexp"
"strings"
"github.com/go-playground/validator/v10"
)
// BusinessValidator 业务验证器
type BusinessValidator struct {
validator *validator.Validate
}
// NewBusinessValidator 创建业务验证器
func NewBusinessValidator() *BusinessValidator {
validate := validator.New()
RegisterCustomValidators(validate)
return &BusinessValidator{
validator: validate,
}
}
// ValidateStruct 验证结构体
func (bv *BusinessValidator) ValidateStruct(data interface{}) error {
return bv.validator.Struct(data)
}
// ValidateField 验证单个字段
func (bv *BusinessValidator) ValidateField(field interface{}, tag string) error {
return bv.validator.Var(field, tag)
}
// 以下是具体的业务验证方法,可以在业务逻辑中直接调用
// ValidatePhone 验证手机号
func (bv *BusinessValidator) ValidatePhone(phone string) error {
if phone == "" {
return fmt.Errorf("手机号不能为空")
}
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
if !matched {
return fmt.Errorf("手机号格式不正确")
}
return nil
}
// ValidatePassword 验证密码强度
func (bv *BusinessValidator) ValidatePassword(password string) error {
if password == "" {
return fmt.Errorf("密码不能为空")
}
if len(password) < 8 {
return fmt.Errorf("密码长度不能少于8位")
}
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
hasDigit := regexp.MustCompile(`\d`).MatchString(password)
if !hasUpper {
return fmt.Errorf("密码必须包含大写字母")
}
if !hasLower {
return fmt.Errorf("密码必须包含小写字母")
}
if !hasDigit {
return fmt.Errorf("密码必须包含数字")
}
return nil
}
// ValidateUsername 验证用户名
func (bv *BusinessValidator) ValidateUsername(username string) error {
if username == "" {
return fmt.Errorf("用户名不能为空")
}
matched, _ := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9_]{2,19}$`, username)
if !matched {
return fmt.Errorf("用户名格式不正确只能包含字母、数字、下划线且必须以字母开头长度3-20位")
}
return nil
}
// ValidateSocialCreditCode 验证统一社会信用代码
func (bv *BusinessValidator) ValidateSocialCreditCode(code string) error {
if code == "" {
return fmt.Errorf("统一社会信用代码不能为空")
}
matched, _ := regexp.MatchString(`^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$`, code)
if !matched {
return fmt.Errorf("统一社会信用代码格式不正确必须是18位统一社会信用代码")
}
return nil
}
// ValidateIDCard 验证身份证号
func (bv *BusinessValidator) ValidateIDCard(idCard string) error {
if idCard == "" {
return fmt.Errorf("身份证号不能为空")
}
matched, _ := regexp.MatchString(`^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[\dXx]$`, idCard)
if !matched {
return fmt.Errorf("身份证号格式不正确必须是18位身份证号")
}
return nil
}
// ValidateUUID 验证UUID
func (bv *BusinessValidator) ValidateUUID(uuid string) error {
if uuid == "" {
return fmt.Errorf("UUID不能为空")
}
matched, _ := regexp.MatchString(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, uuid)
if !matched {
return fmt.Errorf("UUID格式不正确")
}
return nil
}
// ValidateURL 验证URL
func (bv *BusinessValidator) ValidateURL(urlStr string) error {
if urlStr == "" {
return fmt.Errorf("URL不能为空")
}
_, err := url.ParseRequestURI(urlStr)
if err != nil {
return fmt.Errorf("URL格式不正确: %v", err)
}
return nil
}
// ValidateProductCode 验证产品代码
func (bv *BusinessValidator) ValidateProductCode(code string) error {
if code == "" {
return fmt.Errorf("产品代码不能为空")
}
matched, _ := regexp.MatchString(`^[a-zA-Z0-9_-]{3,50}$`, code)
if !matched {
return fmt.Errorf("产品代码格式不正确只能包含字母、数字、下划线、连字符长度3-50位")
}
return nil
}
// ValidateEmail 验证邮箱
func (bv *BusinessValidator) ValidateEmail(email string) error {
if email == "" {
return fmt.Errorf("邮箱不能为空")
}
matched, _ := regexp.MatchString(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`, email)
if !matched {
return fmt.Errorf("邮箱格式不正确")
}
return nil
}
// ValidateSortOrder 验证排序方向
func (bv *BusinessValidator) ValidateSortOrder(sortOrder string) error {
if sortOrder == "" {
return nil // 允许为空
}
if sortOrder != "asc" && sortOrder != "desc" {
return fmt.Errorf("排序方向必须是 asc 或 desc")
}
return nil
}
// ValidatePrice 验证价格
func (bv *BusinessValidator) ValidatePrice(price float64) error {
if price < 0 {
return fmt.Errorf("价格不能为负数")
}
return nil
}
// ValidateStringLength 验证字符串长度
func (bv *BusinessValidator) ValidateStringLength(str string, fieldName string, min, max int) error {
length := len(strings.TrimSpace(str))
if min > 0 && length < min {
return fmt.Errorf("%s长度不能少于%d位", fieldName, min)
}
if max > 0 && length > max {
return fmt.Errorf("%s长度不能超过%d位", fieldName, max)
}
return nil
}
// ValidateRequired 验证必填字段
func (bv *BusinessValidator) ValidateRequired(value interface{}, fieldName string) error {
if value == nil {
return fmt.Errorf("%s不能为空", fieldName)
}
switch v := value.(type) {
case string:
if strings.TrimSpace(v) == "" {
return fmt.Errorf("%s不能为空", fieldName)
}
case *string:
if v == nil || strings.TrimSpace(*v) == "" {
return fmt.Errorf("%s不能为空", fieldName)
}
}
return nil
}
// ValidateRange 验证数值范围
func (bv *BusinessValidator) ValidateRange(value float64, fieldName string, min, max float64) error {
if value < min {
return fmt.Errorf("%s不能小于%v", fieldName, min)
}
if value > max {
return fmt.Errorf("%s不能大于%v", fieldName, max)
}
return nil
}
// ValidateSliceNotEmpty 验证切片不为空
func (bv *BusinessValidator) ValidateSliceNotEmpty(slice interface{}, fieldName string) error {
if slice == nil {
return fmt.Errorf("%s不能为空", fieldName)
}
switch v := slice.(type) {
case []string:
if len(v) == 0 {
return fmt.Errorf("%s不能为空", fieldName)
}
case []int:
if len(v) == 0 {
return fmt.Errorf("%s不能为空", fieldName)
}
}
return nil
}

View File

@@ -0,0 +1,114 @@
package validator
import (
"net/url"
"regexp"
"github.com/go-playground/validator/v10"
)
// RegisterCustomValidators 注册所有自定义验证器
func RegisterCustomValidators(validate *validator.Validate) {
// 手机号验证器
validate.RegisterValidation("phone", validatePhone)
// 用户名验证器字母开头允许字母数字下划线3-20位
validate.RegisterValidation("username", validateUsername)
// 强密码验证器至少8位包含大小写字母和数字
validate.RegisterValidation("strong_password", validateStrongPassword)
// 统一社会信用代码验证器
validate.RegisterValidation("social_credit_code", validateSocialCreditCode)
// 身份证号验证器
validate.RegisterValidation("id_card", validateIDCard)
// 价格验证器(非负数)
validate.RegisterValidation("price", validatePrice)
// 排序方向验证器
validate.RegisterValidation("sort_order", validateSortOrder)
// 产品代码验证器字母数字下划线连字符3-50位
validate.RegisterValidation("product_code", validateProductCode)
// UUID验证器
validate.RegisterValidation("uuid", validateUUID)
// URL验证器
validate.RegisterValidation("url", validateURL)
}
// validatePhone 手机号验证
func validatePhone(fl validator.FieldLevel) bool {
phone := fl.Field().String()
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
return matched
}
// validateUsername 用户名验证
func validateUsername(fl validator.FieldLevel) bool {
username := fl.Field().String()
matched, _ := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9_]{2,19}$`, username)
return matched
}
// validateStrongPassword 强密码验证
func validateStrongPassword(fl validator.FieldLevel) bool {
password := fl.Field().String()
if len(password) < 8 {
return false
}
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
hasDigit := regexp.MustCompile(`\d`).MatchString(password)
return hasUpper && hasLower && hasDigit
}
// validateSocialCreditCode 统一社会信用代码验证
func validateSocialCreditCode(fl validator.FieldLevel) bool {
code := fl.Field().String()
matched, _ := regexp.MatchString(`^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$`, code)
return matched
}
// validateIDCard 身份证号验证
func validateIDCard(fl validator.FieldLevel) bool {
idCard := fl.Field().String()
matched, _ := regexp.MatchString(`^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[\dXx]$`, idCard)
return matched
}
// validatePrice 价格验证
func validatePrice(fl validator.FieldLevel) bool {
price := fl.Field().Float()
return price >= 0
}
// validateSortOrder 排序方向验证
func validateSortOrder(fl validator.FieldLevel) bool {
sortOrder := fl.Field().String()
return sortOrder == "" || sortOrder == "asc" || sortOrder == "desc"
}
// validateProductCode 产品代码验证
func validateProductCode(fl validator.FieldLevel) bool {
code := fl.Field().String()
matched, _ := regexp.MatchString(`^[a-zA-Z0-9_-]{3,50}$`, code)
return matched
}
// validateUUID UUID验证
func validateUUID(fl validator.FieldLevel) bool {
uuid := fl.Field().String()
matched, _ := regexp.MatchString(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, uuid)
return matched
}
// validateURL URL验证
func validateURL(fl validator.FieldLevel) bool {
urlStr := fl.Field().String()
_, err := url.ParseRequestURI(urlStr)
return err == nil
}

View File

@@ -0,0 +1,253 @@
package validator
import (
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
)
// RegisterCustomTranslations 注册所有自定义翻译
func RegisterCustomTranslations(validate *validator.Validate, trans ut.Translator) {
// 注册标准字段翻译
registerStandardTranslations(validate, trans)
// 注册自定义字段翻译
registerCustomFieldTranslations(validate, trans)
}
// registerStandardTranslations 注册标准翻译
func registerStandardTranslations(validate *validator.Validate, trans ut.Translator) {
// 必填字段翻译
validate.RegisterTranslation("required", trans, func(ut ut.Translator) error {
return ut.Add("required", "{0}不能为空", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required", getFieldDisplayName(fe.Field()))
return t
})
// 字段相等翻译
validate.RegisterTranslation("eqfield", trans, func(ut ut.Translator) error {
return ut.Add("eqfield", "{0}必须与{1}一致", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("eqfield", getFieldDisplayName(fe.Field()), getFieldDisplayName(fe.Param()))
return t
})
// 最小长度翻译
validate.RegisterTranslation("min", trans, func(ut ut.Translator) error {
return ut.Add("min", "{0}长度不能少于{1}位", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("min", getFieldDisplayName(fe.Field()), fe.Param())
return t
})
// 最大长度翻译
validate.RegisterTranslation("max", trans, func(ut ut.Translator) error {
return ut.Add("max", "{0}长度不能超过{1}位", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("max", getFieldDisplayName(fe.Field()), fe.Param())
return t
})
// 固定长度翻译
validate.RegisterTranslation("len", trans, func(ut ut.Translator) error {
return ut.Add("len", "{0}长度必须为{1}位", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("len", getFieldDisplayName(fe.Field()), fe.Param())
return t
})
// 邮箱翻译
validate.RegisterTranslation("email", trans, func(ut ut.Translator) error {
return ut.Add("email", "{0}必须是有效的邮箱地址", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("email", getFieldDisplayName(fe.Field()))
return t
})
// 枚举值翻译
validate.RegisterTranslation("oneof", trans, func(ut ut.Translator) error {
return ut.Add("oneof", "{0}必须是以下值之一: {1}", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("oneof", getFieldDisplayName(fe.Field()), fe.Param())
return t
})
// 大于翻译
validate.RegisterTranslation("gt", trans, func(ut ut.Translator) error {
return ut.Add("gt", "{0}必须大于{1}", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("gt", getFieldDisplayName(fe.Field()), fe.Param())
return t
})
}
// registerCustomFieldTranslations 注册自定义字段翻译
func registerCustomFieldTranslations(validate *validator.Validate, trans ut.Translator) {
// 手机号翻译
validate.RegisterTranslation("phone", trans, func(ut ut.Translator) error {
return ut.Add("phone", "{0}必须是有效的手机号", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("phone", getFieldDisplayName(fe.Field()))
return t
})
// 用户名翻译
validate.RegisterTranslation("username", trans, func(ut ut.Translator) error {
return ut.Add("username", "{0}格式不正确只能包含字母、数字、下划线且必须以字母开头长度3-20位", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("username", getFieldDisplayName(fe.Field()))
return t
})
// 强密码翻译
validate.RegisterTranslation("strong_password", trans, func(ut ut.Translator) error {
return ut.Add("strong_password", "{0}强度不足必须包含大小写字母和数字且不少于8位", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("strong_password", getFieldDisplayName(fe.Field()))
return t
})
// 统一社会信用代码翻译
validate.RegisterTranslation("social_credit_code", trans, func(ut ut.Translator) error {
return ut.Add("social_credit_code", "{0}格式不正确必须是18位统一社会信用代码", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("social_credit_code", getFieldDisplayName(fe.Field()))
return t
})
// 身份证号翻译
validate.RegisterTranslation("id_card", trans, func(ut ut.Translator) error {
return ut.Add("id_card", "{0}格式不正确必须是18位身份证号", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("id_card", getFieldDisplayName(fe.Field()))
return t
})
// 价格翻译
validate.RegisterTranslation("price", trans, func(ut ut.Translator) error {
return ut.Add("price", "{0}必须是非负数", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("price", getFieldDisplayName(fe.Field()))
return t
})
// 排序方向翻译
validate.RegisterTranslation("sort_order", trans, func(ut ut.Translator) error {
return ut.Add("sort_order", "{0}必须是 asc 或 desc", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("sort_order", getFieldDisplayName(fe.Field()))
return t
})
// 产品代码翻译
validate.RegisterTranslation("product_code", trans, func(ut ut.Translator) error {
return ut.Add("product_code", "{0}格式不正确只能包含字母、数字、下划线、连字符长度3-50位", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("product_code", getFieldDisplayName(fe.Field()))
return t
})
// UUID翻译
validate.RegisterTranslation("uuid", trans, func(ut ut.Translator) error {
return ut.Add("uuid", "{0}必须是有效的UUID格式", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("uuid", getFieldDisplayName(fe.Field()))
return t
})
// URL翻译
validate.RegisterTranslation("url", trans, func(ut ut.Translator) error {
return ut.Add("url", "{0}必须是有效的URL地址", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("url", getFieldDisplayName(fe.Field()))
return t
})
}
// getFieldDisplayName 获取字段显示名称(中文)
func getFieldDisplayName(field string) string {
fieldNames := map[string]string{
"phone": "手机号",
"password": "密码",
"confirm_password": "确认密码",
"old_password": "原密码",
"new_password": "新密码",
"confirm_new_password": "确认新密码",
"code": "验证码",
"username": "用户名",
"email": "邮箱",
"display_name": "显示名称",
"scene": "使用场景",
"Password": "密码",
"NewPassword": "新密码",
"ConfirmPassword": "确认密码",
"name": "名称",
"Name": "名称",
"description": "描述",
"Description": "描述",
"price": "价格",
"Price": "价格",
"category_id": "分类ID",
"CategoryID": "分类ID",
"product_id": "产品ID",
"ProductID": "产品ID",
"user_id": "用户ID",
"UserID": "用户ID",
"page": "页码",
"Page": "页码",
"page_size": "每页数量",
"PageSize": "每页数量",
"keyword": "关键词",
"Keyword": "关键词",
"sort_by": "排序字段",
"SortBy": "排序字段",
"sort_order": "排序方向",
"SortOrder": "排序方向",
"company_name": "企业名称",
"CompanyName": "企业名称",
"unified_social_code": "统一社会信用代码",
"UnifiedSocialCode": "统一社会信用代码",
"legal_person_name": "法定代表人姓名",
"LegalPersonName": "法定代表人姓名",
"legal_person_id": "法定代表人身份证号",
"LegalPersonID": "法定代表人身份证号",
"legal_person_phone": "法定代表人手机号",
"LegalPersonPhone": "法定代表人手机号",
"verification_code": "验证码",
"VerificationCode": "验证码",
"contract_url": "合同URL",
"ContractURL": "合同URL",
"amount": "金额",
"Amount": "金额",
"balance": "余额",
"Balance": "余额",
"is_active": "是否激活",
"IsActive": "是否激活",
"is_enabled": "是否启用",
"IsEnabled": "是否启用",
"is_visible": "是否可见",
"IsVisible": "是否可见",
"is_package": "是否组合包",
"IsPackage": "是否组合包",
"Code": "编号",
"content": "内容",
"Content": "内容",
"sort": "排序",
"Sort": "排序",
"seo_title": "SEO标题",
"SEOTitle": "SEO标题",
"seo_description": "SEO描述",
"SEODescription": "SEO描述",
"seo_keywords": "SEO关键词",
"SEOKeywords": "SEO关键词",
"id": "ID",
"ID": "ID",
"ids": "ID列表",
"IDs": "ID列表",
}
if displayName, exists := fieldNames[field]; exists {
return displayName
}
return field
}

View File

@@ -0,0 +1,216 @@
package validator
import (
"fmt"
"strings"
"tyapi-server/internal/shared/interfaces"
"github.com/gin-gonic/gin"
"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_translations "github.com/go-playground/validator/v10/translations/zh"
)
// RequestValidator HTTP请求验证器
type RequestValidator struct {
response interfaces.ResponseBuilder
translator ut.Translator
validator *validator.Validate
}
// NewRequestValidator 创建HTTP请求验证器
func NewRequestValidator(response interfaces.ResponseBuilder) interfaces.RequestValidator {
// 创建中文locale
zhLocale := zh.New()
uni := ut.New(zhLocale, zhLocale)
// 获取中文翻译器
trans, _ := uni.GetTranslator("zh")
// 获取gin默认的validator实例
ginValidator := binding.Validator.Engine().(*validator.Validate)
// 注册官方中文翻译
zh_translations.RegisterDefaultTranslations(ginValidator, trans)
// 注册自定义验证器到gin的全局validator
RegisterCustomValidators(ginValidator)
// 注册自定义翻译
RegisterCustomTranslations(ginValidator, trans)
return &RequestValidator{
response: response,
translator: trans,
validator: ginValidator,
}
}
// Validate 验证请求体
func (v *RequestValidator) Validate(c *gin.Context, dto interface{}) error {
return v.BindAndValidate(c, dto)
}
// ValidateQuery 验证查询参数
func (v *RequestValidator) ValidateQuery(c *gin.Context, dto interface{}) error {
if err := c.ShouldBindQuery(dto); err != nil {
if validationErrors, ok := err.(validator.ValidationErrors); ok {
validationErrorsMap := v.formatValidationErrors(validationErrors)
v.response.ValidationError(c, validationErrorsMap)
} else {
v.response.BadRequest(c, "查询参数格式错误")
}
return err
}
return nil
}
// ValidateParam 验证路径参数
func (v *RequestValidator) ValidateParam(c *gin.Context, dto interface{}) error {
if err := c.ShouldBindUri(dto); err != nil {
if validationErrors, ok := err.(validator.ValidationErrors); ok {
validationErrorsMap := v.formatValidationErrors(validationErrors)
v.response.ValidationError(c, validationErrorsMap)
} else {
v.response.BadRequest(c, "路径参数格式错误")
}
return err
}
return nil
}
// BindAndValidate 绑定并验证请求
func (v *RequestValidator) BindAndValidate(c *gin.Context, dto interface{}) error {
if err := c.ShouldBindJSON(dto); err != nil {
if validationErrors, ok := err.(validator.ValidationErrors); ok {
validationErrorsMap := v.formatValidationErrors(validationErrors)
v.response.ValidationError(c, validationErrorsMap)
} else {
v.response.BadRequest(c, "请求体格式错误")
}
return err
}
return nil
}
// formatValidationErrors 格式化验证错误
func (v *RequestValidator) formatValidationErrors(validationErrors validator.ValidationErrors) map[string][]string {
errors := make(map[string][]string)
for _, fieldError := range validationErrors {
fieldName := v.getFieldName(fieldError)
errorMessage := v.getErrorMessage(fieldError)
if _, exists := errors[fieldName]; !exists {
errors[fieldName] = []string{}
}
errors[fieldName] = append(errors[fieldName], errorMessage)
}
return errors
}
// getErrorMessage 获取错误消息
func (v *RequestValidator) getErrorMessage(fieldError validator.FieldError) string {
fieldDisplayName := getFieldDisplayName(fieldError.Field())
// 优先使用翻译器
errorMessage := fieldError.Translate(v.translator)
if errorMessage != fieldError.Error() {
// 替换字段名为中文
if fieldDisplayName != fieldError.Field() {
errorMessage = strings.ReplaceAll(errorMessage, fieldError.Field(), fieldDisplayName)
}
return errorMessage
}
// 回退到手动翻译
return v.getFallbackErrorMessage(fieldError, fieldDisplayName)
}
// getFallbackErrorMessage 获取回退错误消息
func (v *RequestValidator) getFallbackErrorMessage(fieldError validator.FieldError, fieldDisplayName string) string {
tag := fieldError.Tag()
param := fieldError.Param()
switch tag {
case "required":
return fmt.Sprintf("%s不能为空", fieldDisplayName)
case "email":
return fmt.Sprintf("%s必须是有效的邮箱地址", fieldDisplayName)
case "min":
return fmt.Sprintf("%s长度不能少于%s位", fieldDisplayName, param)
case "max":
return fmt.Sprintf("%s长度不能超过%s位", fieldDisplayName, param)
case "len":
return fmt.Sprintf("%s长度必须为%s位", fieldDisplayName, param)
case "eqfield":
paramDisplayName := getFieldDisplayName(param)
return fmt.Sprintf("%s必须与%s一致", fieldDisplayName, paramDisplayName)
case "phone":
return fmt.Sprintf("%s必须是有效的手机号", fieldDisplayName)
case "username":
return fmt.Sprintf("%s格式不正确只能包含字母、数字、下划线且必须以字母开头长度3-20位", fieldDisplayName)
case "strong_password":
return fmt.Sprintf("%s强度不足必须包含大小写字母和数字且不少于8位", fieldDisplayName)
case "social_credit_code":
return fmt.Sprintf("%s格式不正确必须是18位统一社会信用代码", fieldDisplayName)
case "id_card":
return fmt.Sprintf("%s格式不正确必须是18位身份证号", fieldDisplayName)
case "price":
return fmt.Sprintf("%s必须是非负数", fieldDisplayName)
case "sort_order":
return fmt.Sprintf("%s必须是 asc 或 desc", fieldDisplayName)
case "product_code":
return fmt.Sprintf("%s格式不正确只能包含字母、数字、下划线、连字符长度3-50位", fieldDisplayName)
case "uuid":
return fmt.Sprintf("%s必须是有效的UUID格式", fieldDisplayName)
case "url":
return fmt.Sprintf("%s必须是有效的URL地址", fieldDisplayName)
case "oneof":
return fmt.Sprintf("%s必须是以下值之一: %s", fieldDisplayName, param)
case "gt":
return fmt.Sprintf("%s必须大于%s", fieldDisplayName, param)
default:
return fmt.Sprintf("%s格式不正确", fieldDisplayName)
}
}
// getFieldName 获取字段名
func (v *RequestValidator) getFieldName(fieldError validator.FieldError) string {
fieldName := fieldError.Field()
return v.toSnakeCase(fieldName)
}
// toSnakeCase 转换为snake_case
func (v *RequestValidator) toSnakeCase(str string) string {
var result strings.Builder
for i, r := range str {
if i > 0 && (r >= 'A' && r <= 'Z') {
result.WriteRune('_')
}
result.WriteRune(r)
}
return strings.ToLower(result.String())
}
// GetValidator 获取validator实例用于业务逻辑
func (v *RequestValidator) GetValidator() *validator.Validate {
return v.validator
}
// ValidateValue 验证单个值(用于业务逻辑)
func (v *RequestValidator) ValidateValue(field interface{}, tag string) error {
return v.validator.Var(field, tag)
}
// ValidateStruct 验证结构体(用于业务逻辑)
func (v *RequestValidator) ValidateStruct(s interface{}) error {
return v.validator.Struct(s)
}