[029][公共模块]基于 Jakarta Validation 实现的自定义日期时间格式校验

简介: 本文基于Jakarta Validation实现可复用的日期时间格式校验,支持LocalDateTime/LocalDate/LocalTime三类字符串校验,含自定义注解、国际化消息及Spring Boot自动配置,空值友好,开箱即用。(239字)

[029][公共模块]基于 Jakarta Validation 实现的自定义日期时间格式校验

在实际项目开发中,我们经常需要对用户输入的日期、时间或日期时间字符串进行格式校验。虽然 Jakarta Bean Validation(即 JSR 380)提供了 @Past@Future 等内置注解,但并没有直接支持按指定格式校验字符串的注解。为此,我们可以自行扩展,实现一个类似 @DateTimeFormat 但结合校验能力的自定义约束。本文介绍了一套完整的实现方案,包含自定义注解、校验器、枚举类型及 Spring Boot 自动配置。

一、整体设计思路

需求要点:

  • 支持对 String 类型的字段进行校验,判断其是否符合指定的日期/时间格式。
  • 能够区分三种场景:日期+时间LocalDateTime)、纯日期LocalDate)、纯时间LocalTime)。
  • 允许空值或空字符串通过校验(可视为选填字段)。
  • 校验失败时支持国际化错误消息。
  • 能够与 Spring Boot 的校验框架无缝集成。

基于以上需求,我们设计了以下几个核心组件:

组件 作用
@LocalDateTimeFormat 自定义约束注解,标注在需要校验的字段上
DateTimeType 枚举,指定字段属于哪种时间类型
LocalDateTimeValidator 实现了 ConstraintValidator 的校验逻辑
ValidatorsConfiguration Spring Boot 配置类,将校验器注册为 Bean
ValidationMessages*.properties 国际化消息文件

二、自定义注解 @LocalDateTimeFormat

该注解是约束的入口,必须使用 @Constraint(validatedBy = LocalDateTimeValidator.class) 指定校验器。

@Target({
    ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = LocalDateTimeValidator.class)
@Documented
public @interface LocalDateTimeFormat {
   
    String message() default "{tutorials4j.framework.common.core.validation.LocalDateTimeFormat.message}";
    Class<?>[] groups() default {
   };
    Class<? extends Payload>[] payload() default {
   };
    String pattern();
    DateTimeType dateTimeType() default DateTimeType.DateTime;
}
  • pattern:日期时间格式,遵循 DateTimeFormatter 语法,如 "yyyy-MM-dd HH:mm:ss"
  • dateTimeType:指定待校验字符串的实际类型,默认为 DateTime
  • message:使用消息键,便于国际化。

三、枚举 DateTimeType

用于区分三种校验目标类型,也便于用户阅读和维护。

public enum DateTimeType {
   
    DateTime,  // LocalDateTime
    Date,      // LocalDate
    Time       // LocalTime
}

四、校验器实现 LocalDateTimeValidator

校验器是核心,实现 ConstraintValidator<LocalDateTimeFormat, String> 接口。

public class LocalDateTimeValidator implements ConstraintValidator<LocalDateTimeFormat, String> {
   
    private String pattern;
    private DateTimeType dateTimeType;
    private DateTimeFormatter formatter;

    @Override
    public void initialize(LocalDateTimeFormat constraintAnnotation) {
   
        this.pattern = constraintAnnotation.pattern();
        this.dateTimeType = constraintAnnotation.dateTimeType();
        this.formatter = DateTimeFormatter.ofPattern(pattern);
    }

    @Override
    public boolean isValid(String object, ConstraintValidatorContext context) {
   
        if (StringUtils.isBlank(object)) {
   
            return true;   // 空值视为合法
        }
        try {
   
            if (DateTimeType.Time.equals(dateTimeType)) {
   
                LocalTime.parse(object, formatter);
            } else if (DateTimeType.Date.equals(dateTimeType)) {
   
                LocalDate.parse(object, formatter);
            } else {
   
                LocalDateTime.parse(object, formatter);
            }
            return true;
        } catch (Exception e) {
   
            return false;
        }
    }
}

要点说明

  • 空值或纯空白字符串直接返回 true,即不报错(可根据业务需求修改)。
  • 使用 DateTimeFormatter 进行解析,依赖 Java 8+ 时间 API。
  • 捕获任何异常(格式错误、无效日期如 2 月 30 日等)并返回 false

五、国际化消息配置

src/main/resources/ 下创建 ValidationMessages.properties(中文)和 ValidationMessages_en.properties(英文):

ValidationMessages.properties

tutorials4j.framework.common.core.validation.LocalDateTimeFormat.message = 日期时间格式无效

ValidationMessages_en.properties

tutorials4j.framework.common.core.validation.LocalDateTimeFormat.message = Invalid date/time format

Spring Boot 会自动读取类路径下的 ValidationMessages 资源文件,并在校验失败时替换消息中的占位符(本示例未使用占位符,直接输出固定消息)。

六、Spring Boot 自动配置类

为了让校验器能够在 Spring 容器中被扫描到(尤其当项目希望支持依赖注入到 ConstraintValidator 中时),提供一个配置类将校验器实例化为 Bean。本例中校验器无外部依赖,但配置类仍是一个好习惯。

@Slf4j
@Configuration(proxyBeanMethods = false)
public class ValidatorsConfiguration {
   
    @PostConstruct
    public void postConstruct() {
   
        log.debug("[COMMON-CORE] Validators Configuration");
    }

    @Bean
    @ConditionalOnMissingBean
    LocalDateTimeValidator localDateTimeValidator() {
   
        log.debug("[COMMON-CORE] Local DateTime Validator");
        return new LocalDateTimeValidator();
    }
}
  • @ConditionalOnMissingBean 允许用户覆盖该 Bean。
  • 如果校验器需要依赖其他 Service(如从数据库读取格式配置),则可以通过 @Bean 方法注入。

七、使用示例

在 DTO 或 Form 类中直接使用注解:

public class OrderCreateForm {
   
    @LocalDateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss", dateTimeType = DateTimeType.DateTime)
    private String expectDeliveryTime;

    @LocalDateTimeFormat(pattern = "yyyy-MM-dd", dateTimeType = DateTimeType.Date)
    private String customerBirthday;

    @LocalDateTimeFormat(pattern = "HH:mm:ss", dateTimeType = DateTimeType.Time)
    private String remindTime;
}

配合 Controller 中使用 @Valid 触发校验:

@PostMapping("/order")
public Result createOrder(@RequestBody @Valid OrderCreateForm form) {
   
    // 业务逻辑
}

若输入 "2025-13-32" 作为生日,将收到国际化消息“日期时间格式无效”或 “Invalid date/time format”。

八、扩展与优化建议

  1. 支持多格式校验
    可以在注解中添加 String[] patterns(),在校验器中依次尝试解析,任一成功即通过。

  2. 允许自定义错误消息中的具体值
    使用 Hibernate Validator 的 ConstraintValidatorContext 动态构建错误信息,例如显示期望的格式。

  3. 与 Spring 的 @DateTimeFormat 结合
    如果同时使用 Spring MVC 的数据绑定转换,注意避免重复转换。本方案专注 @Valid 阶段校验,可与 @DateTimeFormat 并存。

  4. 时区支持
    对于跨时区场景,可在注解中添加 zoneId 属性,并在解析时应用指定的时区(虽然 LocalDateTime 本身不带时区)。

九、总结

通过上述实现,我们在项目中获得了一个高度可复用的日期时间格式校验组件。它遵循 Jakarta Bean Validation 规范,与 Spring Boot 完美集成,支持国际化,且代码清晰易于扩展。开发人员只需通过一个注解即可完成繁琐的格式校验,提高了代码的简洁性和维护性。

这种自定义约束的思路同样适用于其他业务校验场景(如身份证号、手机号、枚举值映射等),希望本文能为您提供有益的参考。

目录
相关文章
|
27天前
|
数据采集 Web App开发 监控
Python跨境爬虫实战:日本电商数据抓取合规方案与踩坑复盘
本文详解Python合规爬虫实战:专攻日本电商数据采集,解决403封禁、Shift_JIS乱码、动态参数、IP风控等核心难题;含完整可运行源码、双编码适配、UA模拟、重试机制及Bidfins合规对接,实现“采集—核验—版本甄别—物流匹配”业务闭环。(239字)
123 0
|
25天前
|
存储 缓存 NoSQL
[051][缓存模块]基于 StringRedisTemplate 的多租户 Key 隔离设计与实践——以 RedisBitmapUtils 为例
本文介绍基于StringRedisTemplate的多租户Redis Key隔离方案:通过自定义TenantStringRedisSerializer,在Key序列化时自动注入租户前缀,实现透明、低侵入的租户数据隔离。以RedisBitmapUtils为例,业务代码无需感知租户ID,所有操作自动适配,兼顾安全性与易用性。(239字)
101 0
|
1月前
|
前端开发 NoSQL Java
[027][Web模块]基于 Spring MVC 的 API 签名校验拦截器设计与实现
本文介绍基于Spring MVC的API签名校验拦截器,支持HmacSHA256签名、时间窗校验、nonce防重放及密钥动态加载,通过`@RequiredSignature`注解无侵入集成,具备高扩展性与生产可用性。(239字)
131 1
|
1月前
|
JSON 前端开发 Java
[048][Crypto模块]Spring Boot 请求体自动解密:@Crypto 注解 + RequestBodyAdvice 实现
本文介绍基于Spring Boot的请求体自动解密方案:通过自定义`@Crypto`注解与`RequestBodyAdvice`,在Controller入参前透明解密RSA/SM2加密的JSON请求体,实现业务代码零侵入、算法可插拔、开关灵活的安全传输机制。(239字)
117 1
|
1月前
|
算法 安全 Java
[047][Crypto模块]基于 Hutool 的常见加解密算法封装与密钥自动生成
本文基于Hutool封装统一加解密框架,提供AES/RSA/SM2/SM4/HMAC等算法的`CryptoProcessor`标准接口,支持密钥自动生成与动态切换,解耦业务代码,兼顾国密合规与易用性,提升安全性与可测试性。(239字)
89 1
|
23天前
|
监控 安全 Java
[052][核心模块]Java线程池封装实践:`ExecutorServiceHolder` 设计与实现
本文介绍轻量级线程池封装工具`ExecutorServiceHolder`,通过`ExecutionOption`统一配置核心参数,支持`ThreadPoolExecutor`与`ScheduledThreadPoolExecutor`的创建、命名、拒绝策略及超时优雅关闭,提升Java多线程开发的安全性与规范性。(239字)
75 0
|
23天前
|
缓存 NoSQL Redis
[031][缓存模块]RedisTemplate工具的租户隔离设计:自动Key前缀机制
本文介绍一种轻量级Redis缓存租户隔离方案:通过自定义`PrefixKeyStringRedisSerializer`,结合`TenantContextHolder`自动为所有缓存Key添加租户前缀(如`&quot;acme:users::user:123&quot;`),实现业务无感、零侵入的多租户数据隔离,兼顾安全性与复用性。(239字)
75 0
|
前端开发 Java 数据库连接
[030][Web模块]Spring Boot 验证与 OpenAPI 集成实战:从校验规则到文档生成
本文详解Spring Boot中Jakarta Validation与Springdoc OpenAPI的深度集成:通过自定义ModelResolver,将`@Min`、`@Pattern`及自定义注解(如`@CreditCardNumber`、`@LocalDateTimeFormat`)自动映射为OpenAPI扩展字段,实现校验规则与文档同步,提升API规范性与协作效率。(239字)
47 0
|
27天前
|
NoSQL 算法 Java
[050][功能模块]基于 Redis Bitmap 的高性能签到系统设计
本文详解基于Redis Bitmap的高性能签到系统设计,利用位图将用户月签到状态压缩至几字节,支持毫秒级签到、连续天数计算(BITFIELD)、日/月活统计等。代码开源,模块化强,支持多业务隔离与灵活扩展。(239字)
105 0
|
27天前
|
数据采集 人工智能 数据挖掘
企业有多个AI应用,员工却不知道怎么用:一次AI工作助理路由改造实践
当一个任务能够被拆解、调用、评估、人工确认并持续改进时,智能体才真正从Demo进入业务。
157 1