2025-07-20 20:53:26 +08:00
|
|
|
|
# Validator 验证器包
|
|
|
|
|
|
|
|
|
|
|
|
这是一个功能完整的验证器包,提供了HTTP请求验证和业务逻辑验证的完整解决方案。
|
|
|
|
|
|
|
|
|
|
|
|
## 📁 包结构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
internal/shared/validator/
|
2025-08-27 22:19:19 +08:00
|
|
|
|
├── validator.go # 统一校验器主逻辑(包含所有功能)
|
2025-07-20 20:53:26 +08:00
|
|
|
|
├── custom_validators.go # 自定义验证器实现
|
|
|
|
|
|
├── translations.go # 中文翻译
|
|
|
|
|
|
└── README.md # 使用说明
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 🚀 特性
|
|
|
|
|
|
|
|
|
|
|
|
### 1. HTTP请求验证
|
|
|
|
|
|
- 自动绑定和验证请求体、查询参数、路径参数
|
|
|
|
|
|
- 中文错误消息
|
|
|
|
|
|
- 集成到Gin框架
|
|
|
|
|
|
- 统一的错误响应格式
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 业务逻辑验证
|
2025-08-27 22:19:19 +08:00
|
|
|
|
- 独立的业务验证方法,可在任何地方调用
|
2025-07-20 20:53:26 +08:00
|
|
|
|
- 丰富的预定义验证规则
|
2025-08-27 22:19:19 +08:00
|
|
|
|
- 与标签验证使用相同的校验逻辑
|
2025-07-20 20:53:26 +08:00
|
|
|
|
|
|
|
|
|
|
### 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, ¶m); 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. 业务逻辑验证
|
|
|
|
|
|
|
2025-08-27 22:19:19 +08:00
|
|
|
|
在Service中使用统一的校验方法:
|
2025-07-20 20:53:26 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
import "tyapi-server/internal/shared/validator"
|
|
|
|
|
|
|
|
|
|
|
|
type UserService struct {
|
2025-08-27 22:19:19 +08:00
|
|
|
|
// 不再需要单独的businessValidator
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *UserService) ValidateUserData(phone, password string) error {
|
2025-08-27 22:19:19 +08:00
|
|
|
|
// 直接使用包级别的校验方法
|
|
|
|
|
|
if err := validator.ValidatePhone(phone); err != nil {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return fmt.Errorf("手机号验证失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-27 22:19:19 +08:00
|
|
|
|
if err := validator.ValidatePassword(password); err != nil {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return fmt.Errorf("密码验证失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-27 22:19:19 +08:00
|
|
|
|
// 也可以使用结构体验证
|
|
|
|
|
|
userData := struct {
|
|
|
|
|
|
Phone string `validate:"phone"`
|
|
|
|
|
|
Password string `validate:"strong_password"`
|
|
|
|
|
|
}{Phone: phone, Password: password}
|
|
|
|
|
|
|
|
|
|
|
|
if err := validator.GetGlobalValidator().Struct(userData); err != nil {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return fmt.Errorf("用户数据验证失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *UserService) ValidateEnterpriseInfo(code, idCard string) error {
|
2025-08-27 22:19:19 +08:00
|
|
|
|
// 直接使用包级别的校验方法
|
|
|
|
|
|
if err := validator.ValidateSocialCreditCode(code); err != nil {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-27 22:19:19 +08:00
|
|
|
|
if err := validator.ValidateIDCard(idCard); err != nil {
|
2025-07-20 20:53:26 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-08-27 22:19:19 +08:00
|
|
|
|
### 4. 处理器中的验证
|
|
|
|
|
|
|
|
|
|
|
|
在API处理器中,可以直接使用结构体验证:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// 在 flxg5a3b_processor.go 中
|
|
|
|
|
|
func ProcessFLXG5A3BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
|
|
|
|
|
var paramsDto dto.FLXG5A3BReq
|
|
|
|
|
|
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
|
|
|
|
|
return nil, errors.Join(processors.ErrSystem, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用统一的校验器验证
|
|
|
|
|
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
|
|
|
|
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ... 继续业务逻辑
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-07-20 20:53:26 +08:00
|
|
|
|
## 🔧 可用的验证规则
|
|
|
|
|
|
|
|
|
|
|
|
### 标准验证规则
|
|
|
|
|
|
- `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请求验证器
|
|
|
|
|
|
),
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 📝 最佳实践
|
|
|
|
|
|
|
|
|
|
|
|
1. **DTO验证**: 在DTO中使用binding标签进行声明式验证
|
2025-08-27 22:19:19 +08:00
|
|
|
|
2. **业务验证**: 在业务逻辑中直接使用 `validator.ValidateXXX()` 方法
|
|
|
|
|
|
3. **统一性**: 所有校验都使用同一个校验器实例,确保规则一致
|
|
|
|
|
|
4. **错误处理**: 验证错误会自动返回统一格式的HTTP响应
|
2025-07-20 20:53:26 +08:00
|
|
|
|
|
|
|
|
|
|
## 🧪 测试示例
|
|
|
|
|
|
|
2025-08-27 22:19:19 +08:00
|
|
|
|
```go
|
|
|
|
|
|
// 测试自定义校验规则
|
|
|
|
|
|
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("无效手机号应该验证失败")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2025-07-20 20:53:26 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2025-08-27 22:19:19 +08:00
|
|
|
|
这个验证器包现在提供了完整的统一解决方案,既可以用于HTTP请求的自动验证,也可以在业务逻辑中进行程序化验证,确保数据的完整性和正确性。
|