Java Validator自定义日期范围验证注解:实现不超过一年的时间跨度校验
文章目录
- 前言
- 技术积累
- 日期跨度计算的复杂性
- Java 8+ 时间API的优势
- Bean Validation规范
- 实现方案
- 核心验证逻辑
- 自定义验证注解
- 在VO类中的使用
- controller使用VO
- 总结
前言
在实际的业务开发中,我们经常需要对用户输入的时间范围进行校验,比如限制查询时间跨度不能超过一年。虽然Java提供了丰富的日期时间API,但针对特定业务规则的验证仍然需要我们自定义实现。这不,在最近的工作中需要实现dataTracking相关功能,免不了对数据处理跨度进行限制。为了方便在多个VO类中进行复用验证,我们实现了验证注解以满足实际业务需求。本文将详细介绍如何通过自定义注解和验证器来实现"时间跨度不超过一年"的校验逻辑,并探讨其中涉及的日期计算理论。
技术积累
日期跨度计算的复杂性
在处理日期跨度时,简单的天数计算(如365天=1年)并不准确,因为涉及到:
闰年的存在(每4年一个闰年,2月有29天)
不同月份天数的差异(28-31天不等)
跨年计算的复杂性
Java 8+ 时间API的优势
Java 8引入的 java.time 包提供了更加精确和易用的日期时间处理API:
LocalDate:表示不带时区的日期
ChronoUnit:用于计算日期时间单位之间的差异
plusYears():精确处理年份加法,自动处理闰年
Bean Validation规范
通过实现 ConstraintValidator 接口,我们可以创建自定义验证逻辑,这符合Java Bean Validation规范(JSR 380),保证了与Spring等框架的良好集成。
实现方案
实现 ConstraintValidator 接口,创建自己的验证类,并将验证逻辑与Spring注解结合起来。最后,只需要在需要的地方打上注解即可完成验证。
核心验证逻辑
定义一个DateRangeWithinOneYearValidator 实现ConstraintValidator 接口完成核心验证逻辑。
/*** DateRangeWithinOneYearValidator* @author senfel* @version 1.0* @date 2025/7/31 11:19*/
public class DateRangeWithinOneYearValidator implements ConstraintValidator<DateRangeWithinOneYear, LocalDate[]> {@Overridepublic boolean isValid(LocalDate[] dates, ConstraintValidatorContext context) {if (dates == null || dates.length != 2) {// 如果为空或长度不为2,让其他验证注解处理return true;}LocalDate start = dates[0];LocalDate end = dates[1];if (start == null || end == null) {// 如果日期为空,让其他验证注解处理return true;}// 确保开始日期不大于结束日期if (start.isAfter(end)) {return false;}// 检查是否跨越了超过一年的时间// 使用年份差来判断是否超过一年,而不是简单地使用365天if (start.getYear() + 1 < end.getYear()) {// 如果开始年份+1还小于结束年份,说明肯定超过一年了return false;} else if (start.getYear() + 1 == end.getYear()) {// 如果开始年份+1等于结束年份,需要检查具体日期// 比较月日,如果结束日期的月日小于开始日期的月日,则未超过一年if (end.getMonthValue() < start.getMonthValue() ||(end.getMonthValue() == start.getMonthValue() && end.getDayOfMonth() < start.getDayOfMonth())) {// 未超过一年return true;} else {// 可能超过一年,需要进一步检查LocalDate sameDateNextYear = start.plusYears(1);return !end.isAfter(sameDateNextYear);}} else {// 同一年内,肯定不超过一年return true;}}
}
自定义验证注解
自定义DateRangeWithinOneYear 注解,让我们在业务中更方便的插入验证逻辑。
/*** DateRangeWithinOneYear* @author senfel* @version 1.0* @date 2025/7/31 11:23*/
@Constraint(validatedBy = DateRangeWithinOneYearValidator.class)
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DateRangeWithinOneYear {String message() default "时间跨度不能超过一年";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}
在VO类中的使用
在我们的接收验证类中使用验证注解
/*** DateRangeWithinOneYear* @author senfel* @version 1.0* @date 2025/7/31 11:30*/@Schema(description = "管理后台 - DpDataTrackingReqVO")
@Data
@ToString(callSuper = true)
public class DpDataTrackingReqVO {/*** 跟踪日期*/@Schema(description = "跟踪日期")@NotNull(message = "跟踪日期不能为空")@Size(min = 2, max = 2, message = "跟踪日期长度为 2")@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)@DateRangeWithinOneYear()private LocalDate[] trackingDate;/*** 区域*/private List<String> regionList;/*** 精品店*/private List<String> retailLocationCodeList;
}
controller使用VO
controller层引入我们的验证类,并使用@Valid开启验证。
/*** 获得CreationByCategory 统计数据* @param reqVo* @author senfel* @date 2025/7/31 15:00* @return cn.cce.yd.framework.common.pojo.CommonResult<java.util.List < cn.cce.yd.module.moi.controller.admin.dpdatatracking.vo.DpDataTrackingCreationByCategoryReqVO>>*/
@GetMapping("/selectCreationByCategory")
@Operation(summary = "获得CreationByCategory 统计数据")
@PreAuthorize("@ss.hasPermission('dp-data-tracking:creation-by-category:query')")
public CommonResult<List<DpDataTrackingCreationByCategoryReqVO>> selectCreationByCategory(@Valid DpDataTrackingReqVO reqVo) {return CommonResult.success( dpDataTrackingService.getDataTrackingCreationByCategory(reqVo));
}
效果展示
总结
通过自定义验证注解和验证器,我们成功实现了"时间跨度不超过一年"的业务规则校验。该方案采用Java 8的时间API,精确处理闰年和月份差异,并通过注解方式可在多个VO类中复用。而且改方案符合Bean Validation规范,与Spring等框架无缝集成,逻辑清晰,易于理解和后续维护。在实际应用中,这种自定义验证方式不仅能处理时间跨度校验,还可以扩展到其他复杂的业务规则验证,为构建高质量的Java应用提供了有效手段。