如何为生成式ID实现静态校验?阻止POST/PUT传入ID返回400错误
解决方案:阻止DTO的ID字段在POST/PUT请求体中被传入
针对你提到的场景,推荐两种基于注解的实现方案,无需在控制器中编写重复判断逻辑,适合大规模代码库:
方案一:自定义校验注解(灵活通用)
通过自定义校验注解,专门约束“请求体中不能传入指定字段”的规则,全局生效且易于维护。
1. 定义自定义校验注解
import jakarta.validation.Constraint; import jakarta.validation.Payload; import java.lang.annotation.*; @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = NotPresentInRequestBodyValidator.class) public @interface NotPresentInRequestBody { String message() default "该字段不允许在请求体中传入"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
2. 实现校验器逻辑
import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; public class NotPresentInRequestBodyValidator implements ConstraintValidator<NotPresentInRequestBody, Object> { @Override public boolean isValid(Object value, ConstraintValidatorContext context) { // 字段值不为null,说明请求体中传入了该字段,校验不通过 return value == null; } }
3. 修改DTO并启用校验
将id字段改为包装类型Long(避免基本类型默认值干扰判断),添加自定义注解:
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class SampleApiDto { @NotPresentInRequestBody private Long id; private String description; private String columnName; private String code; }
在控制器方法上添加@Valid注解,触发请求体校验:
@RestController @RequestMapping("/samples") public class SampleController { @PostMapping public ResponseEntity<Void> createSample(@Valid @RequestBody SampleApiDto dto) { // 处理创建逻辑,此时dto.id为null,后续可自行赋值服务端生成的ID return ResponseEntity.status(HttpStatus.CREATED).build(); } @PutMapping("/{id}") public ResponseEntity<Void> updateSample(@PathVariable Long id, @Valid @RequestBody SampleApiDto dto) { // 处理更新逻辑,使用路径变量的id即可,dto.id为null return ResponseEntity.ok().build(); } }
当请求体中包含id字段时,会自动触发校验失败,返回400 Bad Request,且后续服务端可正常给id赋值并访问。
方案二:分组校验(基于现有注解)
利用Bean Validation的分组功能,仅在POST/PUT请求的校验场景中要求id为null,不影响后续字段访问。
1. 定义校验分组
public interface RequestValidationGroup {}
2. 修改DTO配置分组校验
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class SampleApiDto { @Null(groups = RequestValidationGroup.class, message = "id不允许在请求体中传入") private Long id; private String description; private String columnName; private String code; }
3. 控制器中指定校验分组
@RestController @RequestMapping("/samples") public class SampleController { @PostMapping public ResponseEntity<Void> createSample(@Validated(RequestValidationGroup.class) @RequestBody SampleApiDto dto) { // 处理创建逻辑 return ResponseEntity.status(HttpStatus.CREATED).build(); } @PutMapping("/{id}") public ResponseEntity<Void> updateSample(@PathVariable Long id, @Validated(RequestValidationGroup.class) @RequestBody SampleApiDto dto) { // 处理更新逻辑 return ResponseEntity.ok().build(); } }
关键说明
- 推荐将
long改为Long:基本类型long默认值为0,无法区分“未传入字段”和“传入0”的情况,包装类型Long默认值为null,能准确判断请求体是否传入了id。 - 两种方案都不会影响后续对
id字段的访问:服务端可自行赋值,之后通过dto.getId()正常获取值。
内容的提问来源于stack exchange,提问作者zakpruitt
相关产品推荐
相关产品推荐

