Skip to content

参数校验、时间传参 ​

这篇解决什么 ​

接口入参要在入库前拦住空值、格式和枚举范围;日期字段还要和前端 DatePicker 对上。读完能在 VO 上挂 jakarta.validation 注解、写自定义校验,以及按 Query / JSON 两种通道传时间。

默认端口 48080,管理端前缀 /admin-api。校验失败走全局异常,返回 code = 400,见 异常处理。VO 分层见 对象转换。

参数校验链路示意图

示意图:Controller 触发校验,失败由 GlobalExceptionHandler 收成 400。

失败响应形如:

json
{
  "code": 400,
  "data": null,
  "msg": "请求参数不正确:密码不能为空"
}

校验注解 ​

依赖已在 Web Starter 里,一般不用再加:

名称说明仓库路径
spring-boot-starter-validationHibernate Validator,包名 jakarta.validationruoyi-office/yudao-framework/yudao-spring-boot-starter-web/pom.xml
@Validated开方法级校验,打在 Controller / Service 类上例如 AuthController
@Valid级联校验 Bean 入参方法参数上

不要再写 javax.validation,Spring Boot 3 只认 Jakarta。

常用注解 ​

多数来自 jakarta.validation.constraints。@Length、@Range、@URL 在 org.hibernate.validator.constraints。

名称说明仓库路径
@NotBlank字符串非 null,trim 后长度大于 0标准库
@NotEmpty字符串 / 集合 / 数组非空AuthLoginReqVO.username
@NotNull不能为 nullSealSaveReqVO.companyId
@Pattern正则AuthLoginReqVO.username
@Size / @Length长度或集合大小UserSaveReqVO.email、AuthLoginReqVO.password
@Min / @Max / @Range数值范围TenantSaveReqVO.accountCount
@Email邮箱UserSaveReqVO.email
@AssertTrue / @AssertFalse布尔或组合校验方法AuthLoginReqVO.isSocialCodeValid

不常用注解 ​

数字精度、正负号、日期先后一般用下面这些,按字段语义选即可。

名称说明仓库路径
@Null必须为 null标准库
@DecimalMin / @DecimalMax / @Digits小数范围与位数标准库
@Positive / @PositiveOrZero正数、正数或 0标准库
@Negative / @NegativeOrZero负数、负数或 0标准库
@Future / @FutureOrPresent将来、将来或现在标准库
@Past / @PastOrPresent过去、过去或现在标准库

项目自定义注解 ​

名称说明仓库路径
@Mobile手机号,空值默认通过yudao-framework/yudao-common/.../validation/Mobile.java
@Telephone电话,空值默认通过同包 Telephone.java
@InEnum值必须落在 ArrayValuable 枚举同包 InEnum.java
@InDict值必须落在指定字典 typeyudao-spring-boot-starter-excel/.../dict/validation/InDict.java

@Mobile 只判格式。必填时再叠 @NotEmpty,见 AuthSmsLoginReqVO。@InEnum 对 null 同样放行,必填再叠 @NotNull。

如何挂上校验 ​

三步:依赖已引入、类上 @Validated、参数上 @Valid 或约束注解。

Bean 参数 ​

AuthController 类上 @Validated,登录入参是 VO:

java
@RestController
@RequestMapping("/system/auth")
@Validated
public class AuthController {

    @PostMapping("/login")
    public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
        return success(authService.login(reqVO));
    }
}

约束写在字段上。Lombok 很少手写 getter,注解不要只挂访问器:

java
public class AuthLoginReqVO extends CaptchaVerificationReqVO {

    @NotEmpty(message = "登录账号不能为空")
    @Length(min = 4, max = 30, message = "账号长度为 4-30 位")
    @Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "账号格式为数字以及字母")
    private String username;

    @NotEmpty(message = "密码不能为空")
    @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
    private String password;

    @InEnum(SocialTypeEnum.class)
    private Integer socialType;
}

路径:ruoyi-office/yudao-module-system/yudao-module-system-server/.../auth/。

简单类型 ​

普通参数直接在形参上标约束。类上必须有 @Validated,否则不生效:

java
@PutMapping("/agree")
public CommonResult<Boolean> agreeFriendRequest(
        @RequestParam("id") @NotNull(message = "申请编号不能为空") Long id) {
    friendRequestService.agreeFriendRequest(getLoginUserId(), id);
    return success(true);
}

这段在 yudao-module-im-server 的 ImFriendRequestController。

Service 也要校验 ​

Controller 拦得住 HTTP 入口。Service 还会被别的 Service 调用,实现类同样加 @Validated,方法参数加 @Valid 或约束。

名称说明仓库路径
接口方法 @Valid登录 VO 再校验一遍AdminAuthService.login
实现类 @Validated打开方法级校验代理例如 PostServiceImpl
手工校验代码里立刻 validateValidationUtils.validate
java
public interface AdminAuthService {
    AuthLoginRespVO login(@Valid AuthLoginReqVO reqVO);
}

@Service
@Validated
public class PostServiceImpl implements PostService { }

没有 @Validated 代理时,接口上的 @Valid 不会在内部调用里触发。

自定义注解 ​

内置注解不够时,在 yudao-common 的 validation 包补一对「注解 + Validator」。以 @Mobile 为例。

java
@Target({
        ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
        ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = MobileValidator.class)
public @interface Mobile {

    String message() default "手机号格式不正确";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
java
public class MobileValidator implements ConstraintValidator<Mobile, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (StrUtil.isEmpty(value)) {
            return true;
        }
        return ValidationUtils.isMobile(value);
    }
}

正则在 ValidationUtils.PATTERN_MOBILE。短信登录叠必填:

java
public class AuthSmsLoginReqVO {

    @NotEmpty(message = "手机号不能为空")
    @Mobile
    private String mobile;
}

@InEnum 要求枚举实现 ArrayValuable。UserUpdateStatusReqVO.status 同时挂 @InEnum(CommonStatusEnum.class) 和 @InDict(type = DictTypeConstants.COMMON_STATUS)。

手工触发、分组校验用 ValidationUtils.validate(object, groups)。更完整的分组与 i18n 见 Hibernate Validator。

时间传参 ​

yudao-server 打开了 spring.jackson.serialization.write-dates-as-timestamps: true。LocalDateTime 另有自定义毫秒序列化;LocalDate / LocalTime 没有,必须自己加 @JsonFormat。

常量在 cn.iocoder.yudao.framework.common.util.date.DateUtils:

名称说明仓库路径
FORMAT_YEAR_MONTH_DAYyyyy-MM-ddyudao-framework/yudao-common/.../date/DateUtils.java
FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECONDyyyy-MM-dd HH:mm:ss同上

时间传参配对示意图

示意图:Query 用字符串 + @DateTimeFormat;JSON 里 LocalDateTime 走毫秒,LocalDate 走 yyyy-MM-dd。

Query ​

GET 或 form-data 由 Spring MVC 绑字符串,字段加 @DateTimeFormat,不要用 @JsonFormat。

用户分页的创建时间:

java
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;

访问日志的单个区间字段同样:

java
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] beginTime;

印章台账的纯日期区间用 FORMAT_YEAR_MONTH_DAY:

java
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate[] purchaseDate;

前端列表筛选用 RangePicker,默认格式必须和常量一致:

ts
{
  fieldName: 'createTime',
  label: '创建时间',
  component: 'RangePicker',
  componentProps: {
    ...getRangePickerDefaultProps(),
    allowClear: true,
  },
}

getRangePickerDefaultProps() 在 ruoyi-office-vben/apps/web-antd/src/utils/rangePickerProps.ts,valueFormat 是 YYYY-MM-DD HH:mm:ss。纯日期区间改成 YYYY-MM-DD,对齐 SealPageReqVO。

Request Body ​

POST / PUT 的 JSON 用 @RequestBody。LocalDateTime 默认收毫秒,不要加 @JsonFormat。

TenantSaveReqVO.expireTime:

java
@NotNull(message = "过期时间不能为空")
private LocalDateTime expireTime;

租户表单对应:

ts
{
  fieldName: 'expireTime',
  label: '过期时间',
  component: 'DatePicker',
  componentProps: {
    format: 'YYYY-MM-DD',
    valueFormat: 'x',
    placeholder: '请选择过期时间',
  },
}

valueFormat: 'x' 表示提交 epoch millis。需要带时分秒时再加 showTime: true。

LocalDate 在 SaveReqVO / RespVO 上都要 @JsonFormat:

java
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate purchaseDate;

印章页 DatePicker 用 format / valueFormat = 'YYYY-MM-DD'。LocalTime 同理,pattern 为 HH:mm:ss。

Response ​

TenantRespVO.createTime、expireTime 无 @JsonFormat,JSON 里是数字毫秒。前端列表用 formatDate() 展示,不要标成 Date。

个别字段要直接出字符串时,再加 @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)。TimestampLocalDateTimeSerializer 发现字段上有该注解,就按 pattern 写字符串,否则写 toEpochMilli()。

注册入口是 YudaoJacksonAutoConfiguration:

java
.serializerByType(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE)
.deserializerByType(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE)
.serializerByType(LocalDate.class, LocalDateSerializer.INSTANCE)
.deserializerByType(LocalDate.class, LocalDateDeserializer.INSTANCE)

路径:ruoyi-office/yudao-framework/yudao-spring-boot-starter-web/.../jackson/config/YudaoJacksonAutoConfiguration.java。

毫秒没有产品格式味道,展示交给前端。不要为了省事改全局 LocalDateTime 为字符串,会拆掉现有 valueFormat: 'x'。

前后端配对 ​

名称说明仓库路径
LocalDateJSON 必须 @JsonFormat(yyyy-MM-dd),前端 YYYY-MM-DD,TS stringSealSaveReqVO / sealinfo/data.ts
LocalTime必须 @JsonFormat(HH:mm:ss),前端 TimePicker打卡等字段
LocalDateTime 默认不加 @JsonFormat,毫秒;前端 valueFormat: 'x',TS numberTenantSaveReqVO.expireTime
LocalDateTime 字符串加 @JsonFormat,前端 YYYY-MM-DD HH:mm:ss,TS string导出或只读展示
PageReqVO 日期区间@DateTimeFormat,前端 RangePicker 字符串UserPageReqVO、SealPageReqVO

LocalDate 漏了 JsonFormat

write-dates-as-timestamps: true 时,无注解的 LocalDate 会序列化成 [2026, 3, 19]。DatePicker 解析失败,控制台常见 date.locale is not a function。RespVO 和 SaveReqVO 都要补 @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY)。

时间选择器显示 NaN

后端 LocalDateTime 返回毫秒,前端却写了 valueFormat: 'YYYY-MM-DD HH:mm:ss',选择器会出 NaN 或空白。无 @JsonFormat 时必须 valueFormat: 'x'。

DateTimeFormat 和 JsonFormat

@DateTimeFormat 只服务 Query / form 绑定。@JsonFormat 只服务 JSON。分页区间用前者,Body / 响应用后者(或默认毫秒)。不要互换。

配置与操作 ​

校验注解写在 ReqVO,Controller 加 @Valid。分页区间用 @DateTimeFormat,JSON 体用 @JsonFormat 或默认毫秒。没有校验菜单。

开启见 框架层。

相关篇 ​

联系我们

获取报价、演示和二开方案

微信咨询二维码

微信咨询

17156169080

添加时备注「RuoYi Office」

在线体验商业版