当前位置: 首页 > news >正文

java每日精进 5.14【参数校验】

参数校验

1.1概述

本文使用 Hibernate Validator 框架对 RESTful API 接口的参数进行校验,确保数据入库的正确性。

例如,在用户注册时,校验手机号格式、密码强度等。如果校验失败,抛出

ConstraintViolationException 或相关异常,由 GlobalExceptionHandler 捕获,返回标准化的 CommonResult 响应,格式如下:

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

1.2 参数校验注解

Hibernate Validator 提供 20+ 个内置校验注解,文档将其分为常用和不常用两类:

常用注解
注解功能
@NotBlank用于字符串,确保非 null 且 trim() 后长度大于 0
@NotEmpty用于集合、字符串,确保非 null 且非空
@NotNull确保非 null
@Pattern(value)符合指定正则表达式
@Max(value)值小于或等于指定值
@Min(value)值大于或等于指定值
@Range(min, max)值在指定范围内
@Size(max, min)集合、字符串等大小在范围内
@Length(max, min)字符串长度在范围内
@AssertTrue值为 true
@AssertFalse值为 false
@Email符合邮箱格式
@URL符合 URL 格式
不常用注解
注解功能
@Null必须为 null
@DecimalMax(value)数字小于或等于指定值
@DecimalMin(value)数字大于或等于指定值
@Digits(integer, fraction)数字在指定位数范围内
@Positive正数
@PositiveOrZero正数或 0
@Negative负数
@NegativeOrZero负数或 0
@Future未来日期
@FutureOrPresent现在或未来日期
@Past过去日期
@PastOrPresent现在或过去日期
@SafeHtml安全的 HTML 内容

1.3 参数校验使用过程

文档提到,只需三步即可启用参数校验:

第零步:引入依赖
  • 项目默认引入 spring-boot-starter-validation,无需手动添加:

    xml

    Copy

    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>

  • 提供 Hibernate Validator 的核心功能。
第一步:在类上添加 @Validated
  • 在需要校验的类(如 Controller 或 Service)上添加 @Validated 注解,启用校验:
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
  • 作用:通知 Spring 在方法调用时对参数进行校验。
  • 注意:Service 层也需校验,因为 Service 可能被其他 Service 调用,参数可能不正确。
第二步:添加校验注解

分为两种情况:

情况一:Bean 类型参数

  • 在方法参数上添加 @Valid,在 Bean 属性上添加校验注解:
    // Controller 示例
    @Validated
    @RestController
    public class AuthController {// ...
    }// Service 示例(实现类)
    @Service
    @Validated
    public class AdminAuthServiceImpl implements AdminAuthService {// ...
    }
  • 解析
    • @Valid:触发对 AuthLoginReqVO 对象的属性校验。
    • @NotEmpty:确保字段非 null 且非空字符串。
    • @Length:限制字符串长度在 4-16 位。
    • @Pattern:确保用户名只包含字母和数字。
    • 如果校验失败,抛出 MethodArgumentNotValidException,由 GlobalExceptionHandler 处理。

情况二:普通类型参数

  • 直接在方法参数上添加校验注解:
    // Controller 示例
    @Validated
    @RestController
    public class DictDataController {@GetMapping("/get")public CommonResult<DictDataRespVO> getDictData(@RequestParam("id") @NotNull(message = "编号不能为空") Long id) {// ...}
    }// Service 接口示例
    public interface DictDataService {DictDataDO getDictData(@NotNull(message = "编号不能为空") Long id);
    }
  • 解析
    • @NotNull:确保 id 非 null。
    • 校验失败抛出 ConstraintViolationException,由 GlobalExceptionHandler 的 constraintViolationExceptionHandler 捕获:
      @ExceptionHandler(value = ConstraintViolationException.class)
      public CommonResult<?> constraintViolationExceptionHandler(ConstraintViolationException ex) {log.warn("[constraintViolationExceptionHandler]", ex);ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", constraintViolation.getMessage()));
      }
第三步:测试校验效果
  • 启动项目,调用 API(如 /login),故意漏填参数(如 username),检查响应:

    { "code": 400, "data": null, "msg": "请求参数不正确:登录账号不能为空" }

  • 验证:确认 GlobalExceptionHandler 正确捕获异常并返回标准响应。

1.4 自定义校验注解

当内置注解不足以满足需求时,可自定义校验注解。文档以 @Mobile 注解为例:

第一步:定义 @Mobile 注解
@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 {};}
  • 解析
    • @Target:指定注解可用于字段、方法、参数等。
    • @Retention:运行时保留,供校验器读取。
    • @Constraint:绑定校验器 MobileValidator。
    • message:自定义错误提示。
第二步:实现 MobileValidator
public class MobileValidator implements ConstraintValidator<Mobile, String> {@Overridepublic void initialize(Mobile annotation) {}@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {// 如果手机号为空,默认不校验,即校验通过if (StrUtil.isEmpty(value)) {return true;}// 校验手机return ValidationUtils.isMobile(value);}}
  • 解析
    • 实现 ConstraintValidator<Mobile, String>,校验字符串类型的手机号。
    • isValid:检查是否符合手机号格式(通过 ValidationUtils.isMobile,推测为正则匹配)。
    • 允许空值通过,符合业务需求。
第三步:使用 @Mobile
@Data
public class AppAuthLoginReqVO {@NotEmpty(message = "手机号不能为空")@Mobile // 应用自定义注解private String mobile;
}
  • 解析
    • @Mobile 校验 mobile 是否符合手机号格式。
    • 校验失败抛出 ConstraintViolationException,由 GlobalExceptionHandler 处理。

1.5 校验异常处理

  • 异常类型
    • Bean 参数校验失败:抛出 MethodArgumentNotValidException 或 BindException。
    • 普通参数校验失败:抛出 ConstraintViolationException。
  • 处理流程
    • GlobalExceptionHandler 捕获这些异常,转换为 CommonResult:
      @ExceptionHandler(MethodArgumentNotValidException.class)
      public CommonResult<?> methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {log.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);String errorMessage = ex.getBindingResult().getFieldError().getDefaultMessage();return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", errorMessage));
      }
    • 返回 400 状态码和用户友好的错误消息。

1.6 WebSocket 场景

  • 校验方式:WebSocket 握手请求(HTTP GET)通过查询参数传递数据,可使用普通参数校验:
@Validated
@RestController
public class WebSocketController {@GetMapping("/ws/connect")public CommonResult<?> connect(@RequestParam("token") @NotEmpty(message = "token 不能为空") String token) {// ...}
}
  • 异常处理:同 RESTful API,由 GlobalExceptionHandler 处理,返回 CommonResult。
  • 注意:WebSocket 消息(非握手)通常不直接使用 Hibernate Validator,需手动校验或在 Service 层处理。

二、时间传参

2.1 概述

项目对时间参数的传递和响应有明确规范,根据请求类型(Query 或 Request Body)使用不同格式,响应通常以 Long 时间戳为主。以下分 Query、Request Body 和 Response Body 三部分说明。

2.2 Query 时间传参

适用于 GET 请求或 POST 的 form-data 请求。

后端代码
  • 使用 @DateTimeFormat 指定时间格式:
@Data
public class JobLogPageReqVO {@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") // 指定格式private LocalDateTime beginTime;
}@Data
public class UserPageReqVO {@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") // 数组形式private LocalDateTime[] createTime;
}
  • 解析
    • @DateTimeFormat:将字符串(如 2025-05-14 09:47:00)解析为 LocalDateTime。
    • pattern:定义格式为 yyyy-MM-dd HH:mm:ss,与前端一致。
    • 数组形式支持时间范围查询(如开始和结束时间)。
前端代码
  • 前端传递格式为 yyyy-MM-dd HH:mm:ss:
    • 示例(views/infra/job/logger/index.vue):
    • // 单个时间传参 beginTime: '2025-05-14 09:47:00'

    • 示例(views/system/user/index.vue):

      // 多个时间传参(范围) createTime: ['2025-05-14 00:00:00', '2025-05-14 23:59:59']

  • 解析:前端通过查询参数(如 ?beginTime=2025-05-14+09:47:00)或 form-data 提交。
校验
  • 可添加校验注解:

    @NotNull(message = "开始时间不能为空") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime beginTime;

  • 格式错误抛出 HttpMessageNotReadableException,由 GlobalExceptionHandler 处理。

2.3 Request Body 时间传参

适用于 POST、PUT 请求的 JSON 格式。

后端代码
  • 使用 @RequestBody 接收 LocalDateTime:
    @Data
    public class TenantCreateReqVO {@NotNull(message = "过期时间不能为空")private LocalDateTime expireTime;
    }@PostMapping("/create")
    public CommonResult<?> createTenant(@RequestBody TenantCreateReqVO reqVO) {// ...
    }
  • 解析
    • 不需显式 @DateTimeFormat,因为 JSON 使用 Long 时间戳。
    • LocalDateTime 通过自定义反序列化器处理。
前端代码
  • 传递 Long 时间戳:
    • 示例(views/system/tenant/TenantForm.vue):

      expireTime: 1744558020000 // 对应 2025-05-14 09:47:00

  • 解析:前端将时间转换为毫秒时间戳,符合后端预期。

2.4 Response Body 时间响应

  • LocalDateTime 字段序列化为 Long 时间戳:
    @Data public class TenantRespVO { private LocalDateTime createTime; }
  • 响应示例

    { "code": 0, "data": { "createTime": 1744558020000 }, "msg": "success" }

2.5 自定义 JSON 时间格式

作用范围(前端 POST/PUT 请求发送 JSON 数据时,包含 LocalDateTime 字段,后端返回包含 LocalDateTime 的 Java 对象时),上文Request Body 时间传参 和 Response Body 时间响应 的处理主要依赖于 自定义 JSON 时间格式,而 Query 时间传参 则依赖 @DateTimeFormat 注解来解析字符串格式的时间。

为什么使用 Long 时间戳?
  • 原因
    • Long 时间戳是标准格式,无格式歧义(如 yyyy-MM-dd vs yyyy/MM/dd)。
    • 前端可通过 format 方法灵活展示任意格式,规范性强。
  • 实现
    • 使用自定义序列化器和反序列化器:
      /*** 基于时间戳的 LocalDateTime 序列化器* 用于将 Java 8 的 LocalDateTime 对象序列化为 Unix 时间戳(毫秒级)*/
      public class TimestampLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {// 单例实例,避免重复创建public static final TimestampLocalDateTimeSerializer INSTANCE = new TimestampLocalDateTimeSerializer();/*** 将 LocalDateTime 对象序列化为时间戳(毫秒)* * @param value       待序列化的 LocalDateTime 对象* @param gen         JSON 生成器,用于输出 JSON 内容* @param serializers 序列化器提供程序,可用于获取上下文信息* @throws IOException 当 JSON 生成过程中发生 I/O 错误时抛出*/@Overridepublic void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {// 1. 将 LocalDateTime 转换为系统默认时区的 ZonedDateTime// LocalDateTime 本身不带时区信息,需要通过 ZoneId.systemDefault() 获取系统默认时区// 例如:2023-01-01T12:00:00 + 系统时区(如 Asia/Shanghai) = 2023-01-01T12:00:00+08:00[Asia/Shanghai]ZonedDateTime zonedDateTime = value.atZone(ZoneId.systemDefault());// 2. 将 ZonedDateTime 转换为 Instant(时间线上的一个点,UTC 时间)// 例如:2023-01-01T12:00:00+08:00[Asia/Shanghai] -> 2023-01-01T04:00:00ZInstant instant = zonedDateTime.toInstant();// 3. 将 Instant 转换为 Unix 时间戳(自 1970-01-01T00:00:00Z 以来的毫秒数)// 例如:2023-01-01T04:00:00Z -> 1672545600000long timestamp = instant.toEpochMilli();// 4. 将时间戳写入 JSON 输出流gen.writeNumber(timestamp);}
      }
      /*** 基于时间戳的 LocalDateTime 反序列化器*/
      public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();@Overridepublic LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {// 将 Long 时间戳,转换为 LocalDateTime 对象return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());}}
    • 配置在 YudaoJacksonAutoConfiguration 中:
      @Bean
      public ObjectMapper objectMapper() {ObjectMapper mapper = new ObjectMapper();SimpleModule module = new SimpleModule();module.addSerializer(LocalDateTime.class, new TimestampLocalDateTimeSerializer());module.addDeserializer(LocalDateTime.class, new TimestampLocalDateTimeDeserializer());mapper.registerModule(module);return mapper;
      }
全局配置时间格式
  • 配置 LocalDateTimeSerializer 和 LocalDateTimeDeserializer:
    @Bean
    public ObjectMapper objectMapper() {ObjectMapper mapper = new ObjectMapper();SimpleModule module = new SimpleModule();module.addSerializer(LocalDateTime.class, new TimestampLocalDateTimeSerializer());module.addDeserializer(LocalDateTime.class, new TimestampLocalDateTimeDeserializer());mapper.registerModule(module);return mapper;
    }
  • 效果:所有 LocalDateTime 字段以 yyyy-MM-dd HH:mm:ss 格式序列化。
局部配置时间格式
  • 使用 @JsonFormat:
    @Data
    public class UserRespVO {@JsonSerialize(using = LocalDateTimeSerializer.class)@JsonDeserialize(using = LocalDateTimeDeserializer.class)@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime createTime;
    }
  • 效果:仅 createTime 字段使用指定格式。
http://www.xdnf.cn/news/446095.html

相关文章:

  • qml中定时器的用法
  • 操作系统期末复习笔记
  • WHAT - 前端开发滚动场景API罗列
  • Web UI测试效率低?来试Parasoft Selenic的智能修复与分析!
  • 从 “学会学习” 到高效适应:元学习技术深度解析与应用实践
  • 常见 RPC 协议类别对比
  • 《Effective Python》第2章 字符串和切片操作——深入理解 Python 中 __repr__ 与 __str__
  • 行业趋势与技术创新:驾驭工业元宇宙与绿色智能制造
  • 【氮化镓】AlGaN合金中成分相关的辐射响应
  • 最短路和拓扑排序知识点
  • 各省网上零售额数据(2015-2022年)-社科数据
  • C++之fmt库介绍和使用(1)
  • TCP/IP-——C++编程详解
  • 【windows server脚本每天从网络盘复制到本地】
  • C 语言学习笔记(8)
  • 【3Ds Max】.ive格式文件的导出与加载
  • Oracle数据库中,WITH..AS 子句用法解析
  • 解读红黑树:揭晓高效数据结构的核心引擎
  • 精益数据分析(58/126):移情阶段的深度实践与客户访谈方法论
  • 全面解析 Server-Sent Events(SSE)协议:从大模型流式输出到实时通信场景
  • Spring MVC数据绑定和响应 你了解多少?
  • 如何下载和安装 Ghost Spectre Windows 11 24H2 PRO
  • 102. 二叉树的层序遍历递归法:深度优先搜索的巧妙应用
  • 软件设计师考试《综合知识》计算机编码考点分析
  • [Linux] vim及gcc工具
  • Spring中的循环引用
  • 一发入魂:极简解决 SwiftUI 复杂视图未能正确刷新的问题(上)
  • LabVIEW中样条插值实现及应用
  • Qwen集成clickhouse实现RAG
  • C# 调试技巧——日志记录,NuGet内断点