246 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			246 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
|  | # AuthDate 授权日期验证器
 | |||
|  | 
 | |||
|  | ## 概述
 | |||
|  | 
 | |||
|  | `authDate` 是一个自定义验证器,用于验证授权日期格式和有效性。该验证器确保日期格式正确,并且日期范围必须包括今天。 | |||
|  | 
 | |||
|  | ## 验证规则
 | |||
|  | 
 | |||
|  | ### 1. 格式要求
 | |||
|  | - 必须为 `YYYYMMDD-YYYYMMDD` 格式 | |||
|  | - 两个日期之间用连字符 `-` 分隔 | |||
|  | - 每个日期必须是8位数字 | |||
|  | 
 | |||
|  | ### 2. 日期有效性
 | |||
|  | - 开始日期不能晚于结束日期 | |||
|  | - 日期范围必须包括今天(如果两个日期都是今天也行) | |||
|  | - 支持闰年验证 | |||
|  | - 验证月份和日期的有效性 | |||
|  | 
 | |||
|  | ### 3. 业务逻辑
 | |||
|  | - 空值由 `required` 标签处理,本验证器返回 `true` | |||
|  | - 日期范围必须覆盖今天,确保授权在有效期内 | |||
|  | 
 | |||
|  | ## 使用示例
 | |||
|  | 
 | |||
|  | ### 在 DTO 中使用
 | |||
|  | 
 | |||
|  | ```go | |||
|  | type FLXG0V4BReq struct { | |||
|  |     Name     string `json:"name" validate:"required,name"` | |||
|  |     IDCard   string `json:"id_card" validate:"required,idCard"` | |||
|  |     AuthDate string `json:"auth_date" validate:"required,authDate"` | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ### 在结构体中使用
 | |||
|  | 
 | |||
|  | ```go | |||
|  | type AuthorizationRequest struct { | |||
|  |     UserID   string `json:"user_id" validate:"required"` | |||
|  |     AuthDate string `json:"auth_date" validate:"required,authDate"` | |||
|  |     Scope    string `json:"scope" validate:"required"` | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ## 有效示例
 | |||
|  | 
 | |||
|  | ### ✅ 有效的日期范围
 | |||
|  | 
 | |||
|  | ```json | |||
|  | { | |||
|  |     "auth_date": "20240101-20240131"  // 1月1日到1月31日(如果今天是1月15日) | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ```json | |||
|  | { | |||
|  |     "auth_date": "20240115-20240115"  // 今天到今天 | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ```json | |||
|  | { | |||
|  |     "auth_date": "20240110-20240120"  // 昨天到明天(如果今天是1月15日) | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ```json | |||
|  | { | |||
|  |     "auth_date": "20240101-20240201"  // 上个月到下个月(如果今天是1月15日) | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ### ❌ 无效的日期范围
 | |||
|  | 
 | |||
|  | ```json | |||
|  | { | |||
|  |     "auth_date": "20240116-20240120"  // 明天到后天(不包括今天) | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ```json | |||
|  | { | |||
|  |     "auth_date": "20240101-20240114"  // 上个月到昨天(不包括今天) | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ```json | |||
|  | { | |||
|  |     "auth_date": "20240131-20240101"  // 开始日期晚于结束日期 | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ```json | |||
|  | { | |||
|  |     "auth_date": "20240101-2024013A"  // 非数字字符 | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ```json | |||
|  | { | |||
|  |     "auth_date": "202401-20240131"    // 日期长度不对 | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ```json | |||
|  | { | |||
|  |     "auth_date": "2024010120240131"   // 缺少连字符 | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ```json | |||
|  | { | |||
|  |     "auth_date": "20240230-20240301"  // 无效日期(2月30日) | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ## 错误消息
 | |||
|  | 
 | |||
|  | 当验证失败时,会返回中文错误消息: | |||
|  | 
 | |||
|  | ``` | |||
|  | "授权日期格式不正确,必须是YYYYMMDD-YYYYMMDD格式,且日期范围必须包括今天" | |||
|  | ``` | |||
|  | 
 | |||
|  | ## 测试用例
 | |||
|  | 
 | |||
|  | 验证器包含完整的测试用例,覆盖以下场景: | |||
|  | 
 | |||
|  | ### 有效场景
 | |||
|  | - 今天到今天 | |||
|  | - 昨天到今天 | |||
|  | - 今天到明天 | |||
|  | - 上周到今天 | |||
|  | - 今天到下周 | |||
|  | - 昨天到明天 | |||
|  | 
 | |||
|  | ### 无效场景
 | |||
|  | - 明天到后天(不包括今天) | |||
|  | - 上周到昨天(不包括今天) | |||
|  | - 格式错误(缺少连字符、多个连字符、长度不对、非数字) | |||
|  | - 无效日期(2月30日、13月等) | |||
|  | - 开始日期晚于结束日期 | |||
|  | 
 | |||
|  | ## 实现细节
 | |||
|  | 
 | |||
|  | ### 核心验证逻辑
 | |||
|  | 
 | |||
|  | ```go | |||
|  | func validateAuthDate(fl validator.FieldLevel) bool { | |||
|  |     authDate := fl.Field().String() | |||
|  |     if authDate == "" { | |||
|  |         return true // 空值由required标签处理 | |||
|  |     } | |||
|  | 
 | |||
|  |     // 1. 检查格式:YYYYMMDD-YYYYMMDD | |||
|  |     parts := strings.Split(authDate, "-") | |||
|  |     if len(parts) != 2 { | |||
|  |         return false | |||
|  |     } | |||
|  | 
 | |||
|  |     // 2. 解析日期 | |||
|  |     startDate, err := parseYYYYMMDD(parts[0]) | |||
|  |     if err != nil { | |||
|  |         return false | |||
|  |     } | |||
|  | 
 | |||
|  |     endDate, err := parseYYYYMMDD(parts[1]) | |||
|  |     if err != nil { | |||
|  |         return false | |||
|  |     } | |||
|  | 
 | |||
|  |     // 3. 检查日期顺序 | |||
|  |     if startDate.After(endDate) { | |||
|  |         return false | |||
|  |     } | |||
|  | 
 | |||
|  |     // 4. 检查是否包括今天 | |||
|  |     today := time.Now().Truncate(24 * time.Hour) | |||
|  |     return !startDate.After(today) && !endDate.Before(today) | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ### 日期解析
 | |||
|  | 
 | |||
|  | ```go | |||
|  | func parseYYYYMMDD(dateStr string) (time.Time, error) { | |||
|  |     if len(dateStr) != 8 { | |||
|  |         return time.Time{}, fmt.Errorf("日期格式错误") | |||
|  |     } | |||
|  | 
 | |||
|  |     year, _ := strconv.Atoi(dateStr[:4]) | |||
|  |     month, _ := strconv.Atoi(dateStr[4:6]) | |||
|  |     day, _ := strconv.Atoi(dateStr[6:8]) | |||
|  | 
 | |||
|  |     date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) | |||
|  |      | |||
|  |     // 验证日期有效性 | |||
|  |     expectedDateStr := date.Format("20060102") | |||
|  |     if expectedDateStr != dateStr { | |||
|  |         return time.Time{}, fmt.Errorf("无效日期") | |||
|  |     } | |||
|  | 
 | |||
|  |     return date, nil | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | ## 注册方式
 | |||
|  | 
 | |||
|  | 验证器已在 `RegisterCustomValidators` 函数中自动注册: | |||
|  | 
 | |||
|  | ```go | |||
|  | func RegisterCustomValidators(validate *validator.Validate) { | |||
|  |     // ... 其他验证器 | |||
|  |     validate.RegisterValidation("auth_date", validateAuthDate) | |||
|  | } | |||
|  | ``` | |||
|  | 
 | |||
|  | 翻译也已自动注册: | |||
|  | 
 | |||
|  | ```go | |||
|  | validate.RegisterTranslation("auth_date", trans, func(ut ut.Translator) error { | |||
|  |     return ut.Add("auth_date", "{0}格式不正确,必须是YYYYMMDD-YYYYMMDD格式,且日期范围必须包括今天", true) | |||
|  | }, func(ut ut.Translator, fe validator.FieldError) string { | |||
|  |     t, _ := ut.T("auth_date", getFieldDisplayName(fe.Field())) | |||
|  |     return t | |||
|  | }) | |||
|  | ``` | |||
|  | 
 | |||
|  | ## 注意事项
 | |||
|  | 
 | |||
|  | 1. **时区处理**:验证器使用 UTC 时区进行日期比较 | |||
|  | 2. **空值处理**:空字符串由 `required` 标签处理,本验证器返回 `true` | |||
|  | 3. **日期精度**:只比较日期部分,忽略时间部分 | |||
|  | 4. **闰年支持**:自动处理闰年验证 | |||
|  | 5. **错误消息**:提供中文错误消息,便于用户理解 | |||
|  | 
 | |||
|  | ## 运行测试
 | |||
|  | 
 | |||
|  | ```bash | |||
|  | # 运行所有 authDate 相关测试
 | |||
|  | go test ./internal/shared/validator -v -run TestValidateAuthDate | |||
|  | 
 | |||
|  | # 运行所有验证器测试
 | |||
|  | go test ./internal/shared/validator -v | |||
|  | ```  |