Files
tyapi-server/internal/shared/validator/AUTH_DATE_VALIDATOR.md
2025-07-28 01:46:39 +08:00

5.4 KiB
Raw Blame History

AuthDate 授权日期验证器

概述

authDate 是一个自定义验证器,用于验证授权日期格式和有效性。该验证器确保日期格式正确,并且日期范围必须包括今天。

验证规则

1. 格式要求

  • 必须为 YYYYMMDD-YYYYMMDD 格式
  • 两个日期之间用连字符 - 分隔
  • 每个日期必须是8位数字

2. 日期有效性

  • 开始日期不能晚于结束日期
  • 日期范围必须包括今天(如果两个日期都是今天也行)
  • 支持闰年验证
  • 验证月份和日期的有效性

3. 业务逻辑

  • 空值由 required 标签处理,本验证器返回 true
  • 日期范围必须覆盖今天,确保授权在有效期内

使用示例

在 DTO 中使用

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"`
}

在结构体中使用

type AuthorizationRequest struct {
    UserID   string `json:"user_id" validate:"required"`
    AuthDate string `json:"auth_date" validate:"required,authDate"`
    Scope    string `json:"scope" validate:"required"`
}

有效示例

有效的日期范围

{
    "auth_date": "20240101-20240131"  // 1月1日到1月31日如果今天是1月15日
}
{
    "auth_date": "20240115-20240115"  // 今天到今天
}
{
    "auth_date": "20240110-20240120"  // 昨天到明天如果今天是1月15日
}
{
    "auth_date": "20240101-20240201"  // 上个月到下个月如果今天是1月15日
}

无效的日期范围

{
    "auth_date": "20240116-20240120"  // 明天到后天(不包括今天)
}
{
    "auth_date": "20240101-20240114"  // 上个月到昨天(不包括今天)
}
{
    "auth_date": "20240131-20240101"  // 开始日期晚于结束日期
}
{
    "auth_date": "20240101-2024013A"  // 非数字字符
}
{
    "auth_date": "202401-20240131"    // 日期长度不对
}
{
    "auth_date": "2024010120240131"   // 缺少连字符
}
{
    "auth_date": "20240230-20240301"  // 无效日期2月30日
}

错误消息

当验证失败时,会返回中文错误消息:

"授权日期格式不正确必须是YYYYMMDD-YYYYMMDD格式且日期范围必须包括今天"

测试用例

验证器包含完整的测试用例,覆盖以下场景:

有效场景

  • 今天到今天
  • 昨天到今天
  • 今天到明天
  • 上周到今天
  • 今天到下周
  • 昨天到明天

无效场景

  • 明天到后天(不包括今天)
  • 上周到昨天(不包括今天)
  • 格式错误(缺少连字符、多个连字符、长度不对、非数字)
  • 无效日期2月30日、13月等
  • 开始日期晚于结束日期

实现细节

核心验证逻辑

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

日期解析

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 函数中自动注册:

func RegisterCustomValidators(validate *validator.Validate) {
    // ... 其他验证器
    validate.RegisterValidation("auth_date", validateAuthDate)
}

翻译也已自动注册:

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. 错误消息:提供中文错误消息,便于用户理解

运行测试

# 运行所有 authDate 相关测试
go test ./internal/shared/validator -v -run TestValidateAuthDate

# 运行所有验证器测试
go test ./internal/shared/validator -v