Files
tyapi-server/internal/shared/validator
2025-10-11 19:10:09 +08:00
..
2025-07-28 01:46:39 +08:00
fix
2025-10-11 19:10:09 +08:00
fix
2025-08-27 22:19:19 +08:00
2025-08-26 14:43:27 +08:00
fix
2025-08-27 22:19:19 +08:00

Validator 验证器包

这是一个功能完整的验证器包提供了HTTP请求验证和业务逻辑验证的完整解决方案。

📁 包结构

internal/shared/validator/
├── validator.go           # 统一校验器主逻辑(包含所有功能)
├── custom_validators.go   # 自定义验证器实现
├── translations.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中使用

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中使用验证标签

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中使用统一的校验方法

import "tyapi-server/internal/shared/validator"

type UserService struct {
    // 不再需要单独的businessValidator
}

func (s *UserService) ValidateUserData(phone, password string) error {
    // 直接使用包级别的校验方法
    if err := validator.ValidatePhone(phone); err != nil {
        return fmt.Errorf("手机号验证失败: %w", err)
    }
    
    if err := validator.ValidatePassword(password); err != nil {
        return fmt.Errorf("密码验证失败: %w", err)
    }
    
    // 也可以使用结构体验证
    userData := struct {
        Phone    string `validate:"phone"`
        Password string `validate:"strong_password"`
    }{Phone: phone, Password: password}
    
    if err := validator.GetGlobalValidator().Struct(userData); err != nil {
        return fmt.Errorf("用户数据验证失败: %w", err)
    }
    
    return nil
}

func (s *UserService) ValidateEnterpriseInfo(code, idCard string) error {
    // 直接使用包级别的校验方法
    if err := validator.ValidateSocialCreditCode(code); err != nil {
        return err
    }
    
    if err := validator.ValidateIDCard(idCard); err != nil {
        return err
    }
    
    return nil
}

4. 处理器中的验证

在API处理器中可以直接使用结构体验证

// 在 flxg5a3b_processor.go 中
func ProcessFLXG5A3BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
    var paramsDto dto.FLXG5A3BReq
    if err := json.Unmarshal(params, &paramsDto); err != nil {
        return nil, errors.Join(processors.ErrSystem, err)
    }

    // 使用统一的校验器验证
    if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
        return nil, errors.Join(processors.ErrInvalidParam, err)
    }
    
    // ... 继续业务逻辑
}

🔧 可用的验证规则

标准验证规则

  • 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)

🌐 错误消息

所有错误消息都已本地化为中文:

{
  "code": 422,
  "message": "请求参数验证失败",
  "data": null,
  "errors": {
    "phone": ["手机号必须是有效的手机号"],
    "password": ["密码强度不足必须包含大小写字母和数字且不少于8位"],
    "confirm_password": ["确认密码必须与密码一致"]
  }
}

🔄 依赖注入

container.go 中已配置:

fx.Provide(
    validator.NewRequestValidator,  // HTTP请求验证器
),

📝 最佳实践

  1. DTO验证: 在DTO中使用binding标签进行声明式验证
  2. 业务验证: 在业务逻辑中直接使用 validator.ValidateXXX() 方法
  3. 统一性: 所有校验都使用同一个校验器实例,确保规则一致
  4. 错误处理: 验证错误会自动返回统一格式的HTTP响应

🧪 测试示例

// 测试自定义校验规则
func TestCustomValidators(t *testing.T) {
    validator.InitGlobalValidator()
    
    // 测试手机号验证
    err := validator.ValidatePhone("13800138000")
    if err != nil {
        t.Errorf("有效手机号验证失败: %v", err)
    }
    
    err = validator.ValidatePhone("12345")
    if err == nil {
        t.Error("无效手机号应该验证失败")
    }
}

这个验证器包现在提供了完整的统一解决方案既可以用于HTTP请求的自动验证也可以在业务逻辑中进行程序化验证确保数据的完整性和正确性。