fix
This commit is contained in:
@@ -88,6 +88,9 @@ westdex:
|
||||
info: { max_size: 100, max_backups: 3, max_age: 28, compress: true }
|
||||
error: { max_size: 200, max_backups: 10, max_age: 90, compress: true }
|
||||
warn: { max_size: 100, max_backups: 3, max_age: 28, compress: true }
|
||||
# 新增:请求和响应日志的独立配置
|
||||
request_log_config: { max_size: 100, max_backups: 5, max_age: 30, compress: true }
|
||||
response_log_config: { max_size: 100, max_backups: 5, max_age: 30, compress: true }
|
||||
|
||||
zhicha:
|
||||
logging:
|
||||
@@ -100,6 +103,9 @@ zhicha:
|
||||
info: { max_size: 100, max_backups: 3, max_age: 28, compress: true }
|
||||
error: { max_size: 200, max_backups: 10, max_age: 90, compress: true }
|
||||
warn: { max_size: 100, max_backups: 3, max_age: 28, compress: true }
|
||||
# 新增:请求和响应日志的独立配置
|
||||
request_log_config: { max_size: 100, max_backups: 5, max_age: 30, compress: true }
|
||||
response_log_config: { max_size: 100, max_backups: 5, max_age: 30, compress: true }
|
||||
|
||||
yushan:
|
||||
logging:
|
||||
|
||||
@@ -14,15 +14,21 @@ logs/
|
||||
│ ├── westdex/ # westdex 服务日志
|
||||
│ │ ├── westdex_info.log
|
||||
│ │ ├── westdex_error.log
|
||||
│ │ └── westdex_warn.log
|
||||
│ │ ├── westdex_warn.log
|
||||
│ │ ├── westdex_request.log # 新增:请求日志文件
|
||||
│ │ └── westdex_response.log # 新增:响应日志文件
|
||||
│ ├── zhicha/ # zhicha 服务日志
|
||||
│ │ ├── zhicha_info.log
|
||||
│ │ ├── zhicha_error.log
|
||||
│ │ └── zhicha_warn.log
|
||||
│ │ ├── zhicha_warn.log
|
||||
│ │ ├── zhicha_request.log # 新增:请求日志文件
|
||||
│ │ └── zhicha_response.log # 新增:响应日志文件
|
||||
│ └── yushan/ # yushan 服务日志
|
||||
│ ├── yushan_info.log
|
||||
│ ├── yushan_error.log
|
||||
│ └── yushan_warn.log
|
||||
│ ├── yushan_warn.log
|
||||
│ ├── yushan_request.log # 新增:请求日志文件
|
||||
│ └── yushan_response.log # 新增:响应日志文件
|
||||
```
|
||||
|
||||
## 配置示例
|
||||
@@ -59,6 +65,17 @@ westdex:
|
||||
max_backups: 3
|
||||
max_age: 28
|
||||
compress: true
|
||||
# 新增:请求和响应日志的独立配置
|
||||
request_log_config:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
response_log_config:
|
||||
max_size: 100
|
||||
max_backups: 5
|
||||
max_age: 30
|
||||
compress: true
|
||||
|
||||
# zhicha 配置
|
||||
zhicha:
|
||||
|
||||
@@ -19,6 +19,9 @@ type ExternalServiceLoggingConfig struct {
|
||||
UseDaily bool `yaml:"use_daily"`
|
||||
EnableLevelSeparation bool `yaml:"enable_level_separation"`
|
||||
LevelConfigs map[string]ExternalServiceLevelFileConfig `yaml:"level_configs"`
|
||||
// 新增:请求和响应日志的独立配置
|
||||
RequestLogConfig ExternalServiceLevelFileConfig `yaml:"request_log_config"`
|
||||
ResponseLogConfig ExternalServiceLevelFileConfig `yaml:"response_log_config"`
|
||||
}
|
||||
|
||||
// ExternalServiceLevelFileConfig 外部服务级别文件配置
|
||||
@@ -34,6 +37,9 @@ type ExternalServiceLogger struct {
|
||||
logger *zap.Logger
|
||||
config ExternalServiceLoggingConfig
|
||||
serviceName string
|
||||
// 新增:用于区分请求和响应日志的字段
|
||||
requestLogger *zap.Logger
|
||||
responseLogger *zap.Logger
|
||||
}
|
||||
|
||||
// NewExternalServiceLogger 创建外部服务日志器
|
||||
@@ -64,6 +70,21 @@ func NewExternalServiceLogger(config ExternalServiceLoggingConfig) (*ExternalSer
|
||||
return nil, fmt.Errorf("创建基础logger失败: %w", err)
|
||||
}
|
||||
|
||||
// 创建请求和响应日志器
|
||||
requestLogger, err := createRequestLogger(serviceLogDir, config)
|
||||
if err != nil {
|
||||
// 如果创建失败,使用基础logger作为备选
|
||||
requestLogger = baseLogger
|
||||
fmt.Printf("创建请求日志器失败,使用基础logger: %v\n", err)
|
||||
}
|
||||
|
||||
responseLogger, err := createResponseLogger(serviceLogDir, config)
|
||||
if err != nil {
|
||||
// 如果创建失败,使用基础logger作为备选
|
||||
responseLogger = baseLogger
|
||||
fmt.Printf("创建响应日志器失败,使用基础logger: %v\n", err)
|
||||
}
|
||||
|
||||
// 如果启用级别分离,创建文件输出
|
||||
if config.EnableLevelSeparation {
|
||||
core := createSeparatedCore(serviceLogDir, config)
|
||||
@@ -72,9 +93,11 @@ func NewExternalServiceLogger(config ExternalServiceLoggingConfig) (*ExternalSer
|
||||
|
||||
// 创建日志器实例
|
||||
logger := &ExternalServiceLogger{
|
||||
logger: baseLogger,
|
||||
config: config,
|
||||
serviceName: config.ServiceName,
|
||||
logger: baseLogger,
|
||||
config: config,
|
||||
serviceName: config.ServiceName,
|
||||
requestLogger: requestLogger,
|
||||
responseLogger: responseLogger,
|
||||
}
|
||||
|
||||
// 如果启用按天分隔,启动定时清理任务
|
||||
@@ -85,6 +108,72 @@ func NewExternalServiceLogger(config ExternalServiceLoggingConfig) (*ExternalSer
|
||||
return logger, nil
|
||||
}
|
||||
|
||||
// createRequestLogger 创建请求日志器
|
||||
func createRequestLogger(logDir string, config ExternalServiceLoggingConfig) (*zap.Logger, error) {
|
||||
// 创建编码器
|
||||
encoderConfig := zap.NewProductionEncoderConfig()
|
||||
encoderConfig.TimeKey = "timestamp"
|
||||
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
|
||||
// 使用默认配置如果未指定
|
||||
requestConfig := config.RequestLogConfig
|
||||
if requestConfig.MaxSize == 0 {
|
||||
requestConfig.MaxSize = 100
|
||||
}
|
||||
if requestConfig.MaxBackups == 0 {
|
||||
requestConfig.MaxBackups = 5
|
||||
}
|
||||
if requestConfig.MaxAge == 0 {
|
||||
requestConfig.MaxAge = 30
|
||||
}
|
||||
|
||||
// 创建请求日志文件写入器
|
||||
requestWriter := createFileWriter(logDir, "request", requestConfig, config.ServiceName, config.UseDaily)
|
||||
|
||||
// 创建请求日志核心
|
||||
requestCore := zapcore.NewCore(
|
||||
zapcore.NewJSONEncoder(encoderConfig),
|
||||
zapcore.AddSync(requestWriter),
|
||||
zapcore.InfoLevel,
|
||||
)
|
||||
|
||||
return zap.New(requestCore), nil
|
||||
}
|
||||
|
||||
// createResponseLogger 创建响应日志器
|
||||
func createResponseLogger(logDir string, config ExternalServiceLoggingConfig) (*zap.Logger, error) {
|
||||
// 创建编码器
|
||||
encoderConfig := zap.NewProductionEncoderConfig()
|
||||
encoderConfig.TimeKey = "timestamp"
|
||||
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
|
||||
// 使用默认配置如果未指定
|
||||
responseConfig := config.ResponseLogConfig
|
||||
if responseConfig.MaxSize == 0 {
|
||||
responseConfig.MaxSize = 100
|
||||
}
|
||||
if responseConfig.MaxBackups == 0 {
|
||||
responseConfig.MaxBackups = 5
|
||||
}
|
||||
if responseConfig.MaxAge == 0 {
|
||||
responseConfig.MaxAge = 30
|
||||
}
|
||||
|
||||
// 创建响应日志文件写入器
|
||||
responseWriter := createFileWriter(logDir, "response", responseConfig, config.ServiceName, config.UseDaily)
|
||||
|
||||
// 创建响应日志核心
|
||||
responseCore := zapcore.NewCore(
|
||||
zapcore.NewJSONEncoder(encoderConfig),
|
||||
zapcore.AddSync(responseWriter),
|
||||
zapcore.InfoLevel,
|
||||
)
|
||||
|
||||
return zap.New(responseCore), nil
|
||||
}
|
||||
|
||||
// createSeparatedCore 创建分离的日志核心
|
||||
func createSeparatedCore(logDir string, config ExternalServiceLoggingConfig) zapcore.Core {
|
||||
// 创建编码器
|
||||
@@ -97,6 +186,10 @@ func createSeparatedCore(logDir string, config ExternalServiceLoggingConfig) zap
|
||||
infoWriter := createFileWriter(logDir, "info", config.LevelConfigs["info"], config.ServiceName, config.UseDaily)
|
||||
errorWriter := createFileWriter(logDir, "error", config.LevelConfigs["error"], config.ServiceName, config.UseDaily)
|
||||
warnWriter := createFileWriter(logDir, "warn", config.LevelConfigs["warn"], config.ServiceName, config.UseDaily)
|
||||
|
||||
// 新增:请求和响应日志的独立文件输出
|
||||
requestWriter := createFileWriter(logDir, "request", config.RequestLogConfig, config.ServiceName, config.UseDaily)
|
||||
responseWriter := createFileWriter(logDir, "response", config.ResponseLogConfig, config.ServiceName, config.UseDaily)
|
||||
|
||||
// 修复:创建真正的级别分离核心
|
||||
// 使用自定义的LevelEnabler来确保每个Core只处理特定级别的日志
|
||||
@@ -118,8 +211,21 @@ func createSeparatedCore(logDir string, config ExternalServiceLoggingConfig) zap
|
||||
&levelEnabler{minLevel: zapcore.WarnLevel, maxLevel: zapcore.WarnLevel}, // 只接受WARN级别
|
||||
)
|
||||
|
||||
// 使用 zapcore.NewTee 合并核心,现在每个核心只会处理自己级别的日志
|
||||
return zapcore.NewTee(infoCore, errorCore, warnCore)
|
||||
// 新增:请求和响应日志核心
|
||||
requestCore := zapcore.NewCore(
|
||||
zapcore.NewJSONEncoder(encoderConfig),
|
||||
zapcore.AddSync(requestWriter),
|
||||
&requestResponseEnabler{logType: "request"}, // 只接受请求日志
|
||||
)
|
||||
|
||||
responseCore := zapcore.NewCore(
|
||||
zapcore.NewJSONEncoder(encoderConfig),
|
||||
zapcore.AddSync(responseWriter),
|
||||
&requestResponseEnabler{logType: "response"}, // 只接受响应日志
|
||||
)
|
||||
|
||||
// 使用 zapcore.NewTee 合并核心,现在每个核心只会处理自己类型的日志
|
||||
return zapcore.NewTee(infoCore, errorCore, warnCore, requestCore, responseCore)
|
||||
}
|
||||
|
||||
// levelEnabler 自定义级别过滤器,确保只接受指定级别的日志
|
||||
@@ -133,6 +239,17 @@ func (l *levelEnabler) Enabled(level zapcore.Level) bool {
|
||||
return level >= l.minLevel && level <= l.maxLevel
|
||||
}
|
||||
|
||||
// requestResponseEnabler 自定义日志类型过滤器,确保只接受特定类型的日志
|
||||
type requestResponseEnabler struct {
|
||||
logType string
|
||||
}
|
||||
|
||||
// Enabled 实现 zapcore.LevelEnabler 接口
|
||||
func (r *requestResponseEnabler) Enabled(level zapcore.Level) bool {
|
||||
// 请求和响应日志通常是INFO级别
|
||||
return level == zapcore.InfoLevel
|
||||
}
|
||||
|
||||
// createFileWriter 创建文件写入器
|
||||
func createFileWriter(logDir, level string, config ExternalServiceLevelFileConfig, serviceName string, useDaily bool) *lumberjack.Logger {
|
||||
// 使用默认配置如果未指定
|
||||
@@ -176,7 +293,7 @@ func createFileWriter(logDir, level string, config ExternalServiceLevelFileConfi
|
||||
|
||||
// LogRequest 记录请求日志
|
||||
func (e *ExternalServiceLogger) LogRequest(requestID, transactionID, apiCode string, url interface{}, params interface{}) {
|
||||
e.logger.Info(fmt.Sprintf("%s API请求", e.serviceName),
|
||||
e.requestLogger.Info(fmt.Sprintf("%s API请求", e.serviceName),
|
||||
zap.String("service", e.serviceName),
|
||||
zap.String("request_id", requestID),
|
||||
zap.String("transaction_id", transactionID),
|
||||
@@ -189,7 +306,7 @@ func (e *ExternalServiceLogger) LogRequest(requestID, transactionID, apiCode str
|
||||
|
||||
// LogResponse 记录响应日志
|
||||
func (e *ExternalServiceLogger) LogResponse(requestID, transactionID, apiCode string, statusCode int, response []byte, duration time.Duration) {
|
||||
e.logger.Info(fmt.Sprintf("%s API响应", e.serviceName),
|
||||
e.responseLogger.Info(fmt.Sprintf("%s API响应", e.serviceName),
|
||||
zap.String("service", e.serviceName),
|
||||
zap.String("request_id", requestID),
|
||||
zap.String("transaction_id", transactionID),
|
||||
@@ -203,7 +320,7 @@ func (e *ExternalServiceLogger) LogResponse(requestID, transactionID, apiCode st
|
||||
|
||||
// LogResponseWithID 记录包含响应ID的响应日志
|
||||
func (e *ExternalServiceLogger) LogResponseWithID(requestID, transactionID, apiCode string, statusCode int, response []byte, duration time.Duration, responseID string) {
|
||||
e.logger.Info(fmt.Sprintf("%s API响应", e.serviceName),
|
||||
e.responseLogger.Info(fmt.Sprintf("%s API响应", e.serviceName),
|
||||
zap.String("service", e.serviceName),
|
||||
zap.String("request_id", requestID),
|
||||
zap.String("transaction_id", transactionID),
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
|
||||
```
|
||||
internal/shared/validator/
|
||||
├── validator.go # HTTP请求验证器主逻辑
|
||||
├── validator.go # 统一校验器主逻辑(包含所有功能)
|
||||
├── custom_validators.go # 自定义验证器实现
|
||||
├── translations.go # 中文翻译
|
||||
├── business.go # 业务逻辑验证接口
|
||||
└── README.md # 使用说明
|
||||
```
|
||||
|
||||
@@ -22,9 +21,9 @@ internal/shared/validator/
|
||||
- 统一的错误响应格式
|
||||
|
||||
### 2. 业务逻辑验证
|
||||
- 独立的业务验证器
|
||||
- 可在任何地方调用的验证方法
|
||||
- 独立的业务验证方法,可在任何地方调用
|
||||
- 丰富的预定义验证规则
|
||||
- 与标签验证使用相同的校验逻辑
|
||||
|
||||
### 3. 自定义验证规则
|
||||
- 手机号验证 (`phone`)
|
||||
@@ -106,35 +105,32 @@ type ProductCommand struct {
|
||||
|
||||
### 3. 业务逻辑验证
|
||||
|
||||
在Service中使用:
|
||||
在Service中使用统一的校验方法:
|
||||
|
||||
```go
|
||||
import "tyapi-server/internal/shared/validator"
|
||||
|
||||
type UserService struct {
|
||||
businessValidator *validator.BusinessValidator
|
||||
}
|
||||
|
||||
func NewUserService() *UserService {
|
||||
return &UserService{
|
||||
businessValidator: validator.NewBusinessValidator(),
|
||||
}
|
||||
// 不再需要单独的businessValidator
|
||||
}
|
||||
|
||||
func (s *UserService) ValidateUserData(phone, password string) error {
|
||||
// 验证手机号
|
||||
if err := s.businessValidator.ValidatePhone(phone); err != nil {
|
||||
// 直接使用包级别的校验方法
|
||||
if err := validator.ValidatePhone(phone); err != nil {
|
||||
return fmt.Errorf("手机号验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 验证密码强度
|
||||
if err := s.businessValidator.ValidatePassword(password); err != nil {
|
||||
if err := validator.ValidatePassword(password); err != nil {
|
||||
return fmt.Errorf("密码验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 验证结构体
|
||||
userData := UserData{Phone: phone, Password: password}
|
||||
if err := s.businessValidator.ValidateStruct(userData); err != nil {
|
||||
// 也可以使用结构体验证
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -142,13 +138,12 @@ func (s *UserService) ValidateUserData(phone, password string) error {
|
||||
}
|
||||
|
||||
func (s *UserService) ValidateEnterpriseInfo(code, idCard string) error {
|
||||
// 验证统一社会信用代码
|
||||
if err := s.businessValidator.ValidateSocialCreditCode(code); err != nil {
|
||||
// 直接使用包级别的校验方法
|
||||
if err := validator.ValidateSocialCreditCode(code); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 验证身份证号
|
||||
if err := s.businessValidator.ValidateIDCard(idCard); err != nil {
|
||||
if err := validator.ValidateIDCard(idCard); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -156,6 +151,27 @@ func (s *UserService) ValidateEnterpriseInfo(code, idCard string) error {
|
||||
}
|
||||
```
|
||||
|
||||
### 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)
|
||||
}
|
||||
|
||||
// ... 继续业务逻辑
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 可用的验证规则
|
||||
|
||||
### 标准验证规则
|
||||
@@ -208,23 +224,33 @@ fx.Provide(
|
||||
),
|
||||
```
|
||||
|
||||
业务验证器可以在需要时创建:
|
||||
|
||||
```go
|
||||
bv := validator.NewBusinessValidator()
|
||||
```
|
||||
|
||||
## 📝 最佳实践
|
||||
|
||||
1. **DTO验证**: 在DTO中使用binding标签进行声明式验证
|
||||
2. **业务验证**: 在业务逻辑中使用BusinessValidator进行程序化验证
|
||||
3. **错误处理**: 验证错误会自动返回统一格式的HTTP响应
|
||||
4. **性能**: 验证器实例可以复用,建议在依赖注入中管理
|
||||
2. **业务验证**: 在业务逻辑中直接使用 `validator.ValidateXXX()` 方法
|
||||
3. **统一性**: 所有校验都使用同一个校验器实例,确保规则一致
|
||||
4. **错误处理**: 验证错误会自动返回统一格式的HTTP响应
|
||||
|
||||
## 🧪 测试示例
|
||||
|
||||
参考 `examples/validator_usage.go` 文件中的完整使用示例。
|
||||
```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("无效手机号应该验证失败")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
这个验证器包提供了完整的验证解决方案,既可以用于HTTP请求的自动验证,也可以在业务逻辑中进行程序化验证,确保数据的完整性和正确性。
|
||||
这个验证器包现在提供了完整的统一解决方案,既可以用于HTTP请求的自动验证,也可以在业务逻辑中进行程序化验证,确保数据的完整性和正确性。
|
||||
@@ -1,180 +0,0 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
func TestValidateAuthDate(t *testing.T) {
|
||||
validate := validator.New()
|
||||
validate.RegisterValidation("auth_date", validateAuthDate)
|
||||
|
||||
today := time.Now().Format("20060102")
|
||||
yesterday := time.Now().AddDate(0, 0, -1).Format("20060102")
|
||||
tomorrow := time.Now().AddDate(0, 0, 1).Format("20060102")
|
||||
lastWeek := time.Now().AddDate(0, 0, -7).Format("20060102")
|
||||
nextWeek := time.Now().AddDate(0, 0, 7).Format("20060102")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
authDate string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "今天到今天 - 有效",
|
||||
authDate: today + "-" + today,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "昨天到今天 - 有效",
|
||||
authDate: yesterday + "-" + today,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "今天到明天 - 有效",
|
||||
authDate: today + "-" + tomorrow,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "上周到今天 - 有效",
|
||||
authDate: lastWeek + "-" + today,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "今天到下周 - 有效",
|
||||
authDate: today + "-" + nextWeek,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "昨天到明天 - 有效",
|
||||
authDate: yesterday + "-" + tomorrow,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "明天到后天 - 无效(不包括今天)",
|
||||
authDate: tomorrow + "-" + time.Now().AddDate(0, 0, 2).Format("20060102"),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "上周到昨天 - 无效(不包括今天)",
|
||||
authDate: lastWeek + "-" + yesterday,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "格式错误 - 缺少连字符",
|
||||
authDate: "2024010120240131",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "格式错误 - 多个连字符",
|
||||
authDate: "20240101-20240131-20240201",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "格式错误 - 日期长度不对",
|
||||
authDate: "202401-20240131",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "格式错误 - 非数字",
|
||||
authDate: "20240101-2024013A",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "无效日期 - 2月30日",
|
||||
authDate: "20240230-20240301",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "无效日期 - 13月",
|
||||
authDate: "20241301-20241331",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "开始日期晚于结束日期",
|
||||
authDate: "20240131-20240101",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "空字符串 - 由required处理",
|
||||
authDate: "",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validate.Var(tt.authDate, "auth_date")
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateAuthDate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYYYYMMDD(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dateStr string
|
||||
want time.Time
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "有效日期",
|
||||
dateStr: "20240101",
|
||||
want: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "闰年2月29日",
|
||||
dateStr: "20240229",
|
||||
want: time.Date(2024, 2, 29, 0, 0, 0, 0, time.UTC),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "非闰年2月29日",
|
||||
dateStr: "20230229",
|
||||
want: time.Time{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "长度错误",
|
||||
dateStr: "202401",
|
||||
want: time.Time{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "非数字",
|
||||
dateStr: "2024010A",
|
||||
want: time.Time{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "无效月份",
|
||||
dateStr: "20241301",
|
||||
want: time.Time{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "无效日期",
|
||||
dateStr: "20240230",
|
||||
want: time.Time{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseYYYYMMDD(tt.dateStr)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseYYYYMMDD() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !tt.wantErr && !got.Equal(tt.want) {
|
||||
t.Errorf("parseYYYYMMDD() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
func TestValidateAuthorizationURL(t *testing.T) {
|
||||
validate := validator.New()
|
||||
RegisterCustomValidators(validate)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "有效的PDF URL",
|
||||
url: "https://example.com/document.pdf",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "有效的JPG URL",
|
||||
url: "https://example.com/image.jpg",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "有效的JPEG URL",
|
||||
url: "https://example.com/image.jpeg",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "有效的PNG URL",
|
||||
url: "https://example.com/image.png",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "有效的BMP URL",
|
||||
url: "https://example.com/image.bmp",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "HTTP协议的PDF URL",
|
||||
url: "http://example.com/document.pdf",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "带查询参数的PDF URL",
|
||||
url: "https://example.com/document.pdf?version=1.0",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "带路径的PDF URL",
|
||||
url: "https://example.com/files/documents/contract.pdf",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "无效的URL格式",
|
||||
url: "not-a-url",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "不支持的文件类型",
|
||||
url: "https://example.com/document.doc",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "不支持的文件类型2",
|
||||
url: "https://example.com/document.txt",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "没有文件扩展名",
|
||||
url: "https://example.com/document",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "FTP协议(不支持)",
|
||||
url: "ftp://example.com/document.pdf",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "空字符串",
|
||||
url: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "大写扩展名",
|
||||
url: "https://example.com/document.PDF",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "混合大小写扩展名",
|
||||
url: "https://example.com/document.JpG",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validate.Var(tt.url, "authorization_url")
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateAuthorizationURL() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -488,3 +488,234 @@ func validateReturnURL(fl validator.FieldLevel) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ================ 统一的业务校验方法 ================
|
||||
|
||||
// ValidatePhone 验证手机号
|
||||
func 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 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 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 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 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 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 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 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 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 ValidateSortOrder(sortOrder string) error {
|
||||
if sortOrder == "" {
|
||||
return nil // 允许为空
|
||||
}
|
||||
if sortOrder != "asc" && sortOrder != "desc" {
|
||||
return fmt.Errorf("排序方向必须是 asc 或 desc")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePrice 验证价格
|
||||
func ValidatePrice(price float64) error {
|
||||
if price < 0 {
|
||||
return fmt.Errorf("价格不能为负数")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateStringLength 验证字符串长度
|
||||
func 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 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 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 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
|
||||
}
|
||||
|
||||
// ================ 便捷的校验器创建函数 ================
|
||||
|
||||
// NewBusinessValidator 创建业务验证器(保持向后兼容)
|
||||
func NewBusinessValidator() *BusinessValidator {
|
||||
// 确保全局校验器已初始化
|
||||
InitGlobalValidator()
|
||||
|
||||
return &BusinessValidator{
|
||||
validator: GetGlobalValidator(), // 使用全局校验器
|
||||
}
|
||||
}
|
||||
|
||||
// BusinessValidator 业务验证器(保持向后兼容)
|
||||
type BusinessValidator struct {
|
||||
validator *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)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package validator
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
@@ -14,6 +15,58 @@ import (
|
||||
zh_translations "github.com/go-playground/validator/v10/translations/zh"
|
||||
)
|
||||
|
||||
// 全局变量声明
|
||||
var (
|
||||
globalValidator *validator.Validate
|
||||
globalTranslator ut.Translator
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// InitGlobalValidator 初始化全局校验器(线程安全)
|
||||
func InitGlobalValidator() {
|
||||
once.Do(func() {
|
||||
// 1. 创建新的校验器实例
|
||||
globalValidator = validator.New()
|
||||
|
||||
// 2. 创建中文翻译器
|
||||
zhLocale := zh.New()
|
||||
uni := ut.New(zhLocale, zhLocale)
|
||||
globalTranslator, _ = uni.GetTranslator("zh")
|
||||
|
||||
// 3. 注册官方中文翻译
|
||||
zh_translations.RegisterDefaultTranslations(globalValidator, globalTranslator)
|
||||
|
||||
// 4. 注册自定义校验规则
|
||||
RegisterCustomValidators(globalValidator)
|
||||
|
||||
// 5. 注册自定义中文翻译
|
||||
RegisterCustomTranslations(globalValidator, globalTranslator)
|
||||
|
||||
// 6. 设置到Gin全局校验器(确保Gin使用我们的校验器)
|
||||
if binding.Validator.Engine() != nil {
|
||||
// 如果Gin已经初始化,则替换其校验器
|
||||
ginValidator := binding.Validator.Engine().(*validator.Validate)
|
||||
*ginValidator = *globalValidator
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// GetGlobalValidator 获取全局校验器实例
|
||||
func GetGlobalValidator() *validator.Validate {
|
||||
if globalValidator == nil {
|
||||
InitGlobalValidator()
|
||||
}
|
||||
return globalValidator
|
||||
}
|
||||
|
||||
// GetGlobalTranslator 获取全局翻译器实例
|
||||
func GetGlobalTranslator() ut.Translator {
|
||||
if globalTranslator == nil {
|
||||
InitGlobalValidator()
|
||||
}
|
||||
return globalTranslator
|
||||
}
|
||||
|
||||
// RequestValidator HTTP请求验证器
|
||||
type RequestValidator struct {
|
||||
response interfaces.ResponseBuilder
|
||||
@@ -23,29 +76,13 @@ type RequestValidator struct {
|
||||
|
||||
// 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)
|
||||
|
||||
// 确保全局校验器已初始化
|
||||
InitGlobalValidator()
|
||||
|
||||
return &RequestValidator{
|
||||
response: response,
|
||||
translator: trans,
|
||||
validator: ginValidator,
|
||||
translator: globalTranslator, // 使用全局翻译器
|
||||
validator: globalValidator, // 使用全局校验器
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user