springboot接口请求参数校验

参数校验

参数校验可以防止无效或错误的数据进入系统。通过校验前端输入的参数,可以确保数据的完整性,避免因为缺少必要的信息而导致程序错误或异常。例如,对于密码字段,可以通过校验规则要求用户输入至少8个字符、包含字母和数字等,以增加密码的强度,提高系统的安全性。通过及时地反馈错误信息,用户可以更快地发现和纠正输入错误,提升用户体验。特别是在前后端接口联调时,前端传参错误很快能得到异常提示,就大大提升了联调效率。

传统的校验方法

@RestController
@RequestMapping("/api/users")
public class UserController {@PostMapping("/register")public ResponseEntity<String> register(@RequestBody Map<String, Object> request) {String username = (String) request.get("username");if (username == null || username.length() < 3 || username.length() > 20) {return ResponseEntity.badRequest().body("用户名不能为空,且长度必须在3到20之间");}String password = (String) request.get("password");if (password == null || password.length() < 8) {return ResponseEntity.badRequest().body("密码不能为空,且长度至少为8个字符");}Integer age = (Integer) request.get("age");if (age == null || age <= 0 || age > 120) {return ResponseEntity.badRequest().body("年龄必须是正整数,且不能超过120");}return ResponseEntity.ok("注册成功!");}
}

这种手写参数校验的方式,在简单场景下勉强能用,但如果业务变复杂,问题会越来越多。在 Spring Boot 中,可以使用Hibernate Validator来实现参数校验。它的核心思路是:把校验逻辑从业务代码里抽离出来,用注解的方式声明校验规则

Springboot 校验原理

Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。Spring Validation是对hibernate validation的二次封装,用于支持SpringMVC参数自动校验,如果SpringBoot版本小于2.3.x,spring-boot-starter-web会自动引入hibernate-validator依赖。如果SpringBoot版本大于2.3.x,则需要手动引入依赖:

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

校验注解

校验空值

  • @Null:验证对象是否为 null
  • @NotNull:验证对象是否不为 null,但可以为空比如空字符串或者空集合
  • @NotEmpty:验证对象不为 null,可以为空字符串,但是长度(数组、集合、字符串等)大于 0
  • @NotBlank:验证字符串不为 null,且去除两端空白字符后长度大于 0

校验大小

  • @Size(min=, max=):验证对象(数组、集合、字符串等)长度是否在给定的范围之内
  • @Min(value):验证数值(整数或浮点数)是否大于等于指定的最小值
  • @Max(value):验证数值是否小于等于指定的最大值
  • @DecimalMin(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @DecimalMax(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @Digits (integer, fraction):被注释的元素必须是一个数字,其值必须在可接受的范围内

校验布尔值

  • @AssertTrue:验证 Boolean 对象是否为 true
  • @AssertFalse:验证 Boolean 对象是否为 false

校验日期和时间

  • @Past:验证 Date 和 Calendar 对象是否在当前时间之前
  • @Future:验证 Date 和 Calendar 对象是否在当前时间之后
  • @PastOrPresent:验证日期是否是过去或现在的时间
  • @FutureOrPresent:验证日期是否是现在或将来的时间

正则表达式

  • @Pattern(regexp=, flags=):验证 String 对象是否符合正则表达式的规则

其他

  • @Length(min=, max=):验证字符串的大小是否在指定的范围内
  • @Range(min=, max=):验证数值是否在合适的范围内
  • @UniqueElements:校验集合中的值是否唯一,依赖于 equals 方法
  • @ScriptAssert:利用脚本进行校
  • @Email:被注释的元素必须是电子邮箱地址
  • @SafeHtml:被注释的元素必须 Html
  • URL: 被注释的元素必须是有效的 URL
  • @Negative:负数不包括0
  • @NegativeOrZero:负数或0
  • **@**CreditCardNumber:字符串是有效的信用卡数字,不校验信用卡本身的有效性
  • @Valid 和 @Validated

这两个注解是校验的入口,作用相似但用法上存在差异。

// 用于类/接口/枚举,方法以及参数
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})  
@Retention(RetentionPolicy.RUNTIME)
@Documented  
public @interface Validated {  // 校验时启动的分组  Class<?>[] value() default {};  
}// 用于方法,字段,构造函数,参数,以及泛型类型  
@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })  
@Retention(RUNTIME)  
@Documented
public @interface Valid {  // 未提供其他属性  
}
  1. 作用范围不同:@Validated 无法作用在于字段, @Valid 无法作用于类
  2. 注解中的属性不同:@Validated 中提供了指定校验分组的属性,而 @Valid 没有这个功能,因为 @Valid 不能进行分组校验
  3. @Valid 注解支持嵌套校验,@Validated 不支持嵌套校验
  4. @Validated(Spring’s JSR-303 规范,是标准 JSR-303 的一个变种),javax提供了@Valid(标准JSR-303规范),配合 BindingResult 可以直接提供参数验证结果。

校验

@RequestBody 参数校验

当方法入参为 @RequestBody 注解的 JavaBean,可在入参前使用 @Validated 或 @Valid 注解开启校验

@PostMapping("/addStudent")
public void addStudent(@RequestBody  @Valid  Student student) {//所有验证通过才会执行下面的代码System.out.println("添加学生成功");
}

@ResponseBody标注方法校验原理

在SpringMVC中,RequestResponseBodyMethodProcessor是用于解析@RequestBody标注的参数以及处理@ResponseBody标注方法的返回值的。显然,执行参数校验的逻辑肯定就在解析参数的方法resolveArgument()中:

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {@Overridepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {parameter = parameter.nestedIfOptional();//将请求数据封装到DTO对象中Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());String name = Conventions.getVariableNameForParameter(parameter);if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);if (arg != null) {// 执行数据校验validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());}}if (mavContainer != null) {mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());}}return adaptArgumentIfNecessary(arg, parameter);}
}

可以看到,resolveArgument()调用了validateIfApplicable()进行参数校验。

protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {// 获取参数注解,比如@RequestBody、@Valid、@ValidatedAnnotation[] annotations = parameter.getParameterAnnotations();for (Annotation ann : annotations) {// 先尝试获取@Validated注解Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);//如果直接标注了@Validated,那么直接开启校验。//如果没有,那么判断参数前是否有Valid起头的注解。if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});//执行校验binder.validate(validationHints);break;}}
}

看到这里,大家应该能明白为什么这种场景下@Validated、@Valid两个注解可以混用。接下来继续看WebDataBinder.validate()实现。

@Override
public void validate(Object target, Errors errors, Object... validationHints) {if (this.targetValidator != null) {processConstraintViolations(//此处调用Hibernate Validator执行真正的校验this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);}
}

最终发现底层最终还是调用了Hibernate Validator进行真正的校验处理。

注意:如果是通过post ResponseBody会抛出MethodArgumentNotValidException异常,如果是get 请求,普通的student参数,则会抛出BindException 异常

简单参数校验

当方法入参为 @PathVariable、 @RequestParam 注解的简单参数时,需要在 Controller 加上 @Validated 注解开启校验。

@RequestMapping("/student")
@RestController
// 必须加上该注解
@Validated
public class StudentController {// 路径变量@GetMapping("{id}")public Reponse<Student> detail(@PathVariable("id") @Min(1L) Long StudentId) {// 参数StudentId校验通过,执行后续业务逻辑return Reponse.ok();}// 请求参数@GetMapping("getByName")public Result getByName(@RequestParam("Name") @Length(min = 1, max = 20) String  Name) {// 参数Name校验通过,执行后续业务逻辑return Result.ok();}
}

简单参数校验原理

上面提到的将参数一个个平铺到方法参数中,然后在每个参数前面声明约束注解的校验方式,就是方法级别的参数校验。实际上,这种方式可用于任何Spring Bean的方法上,比如Controller/Service等。其底层实现原理就是AOP,具体来说是通过MethodValidationPostProcessor动态注册AOP切面,然后使用MethodValidationInterceptor对切点方法织入增强。

public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor implements InitializingBean {@Overridepublic void afterPropertiesSet() {//为所有`@Validated`标注的Bean创建切面Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);//创建Advisor进行增强this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));}//创建Advice,本质就是一个方法拦截器protected Advice createMethodValidationAdvice(@Nullable Validator validator) {return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());}
}

接着看一下MethodValidationInterceptor

public class MethodValidationInterceptor implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {//无需增强的方法,直接跳过if (isFactoryBeanMetadataMethod(invocation.getMethod())) {return invocation.proceed();}//获取分组信息Class<?>[] groups = determineValidationGroups(invocation);ExecutableValidator execVal = this.validator.forExecutables();Method methodToValidate = invocation.getMethod();Set<ConstraintViolation<Object>> result;try {//方法入参校验,最终还是委托给Hibernate Validator来校验result = execVal.validateParameters(invocation.getThis(), methodToValidate, invocation.getArguments(), groups);}catch (IllegalArgumentException ex) {}//有异常直接抛出if (!result.isEmpty()) {throw new ConstraintViolationException(result);}//真正的方法调用Object returnValue = invocation.proceed();//对返回值做校验,最终还是委托给Hibernate Validator来校验result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);//有异常直接抛出if (!result.isEmpty()) {throw new ConstraintViolationException(result);}return returnValue;}
}

实际上,不管是requestBody参数校验还是方法级别的校验,最终都是调用Hibernate Validator执行校验,Spring Validation只是做了一层封装。

复杂参数校验

有时候,需要对多个字段进行复杂的逻辑校验,例如需要两个字段相互比较或执行自定义的校验逻辑。在这种情况下,可以使用自定义的校验器(Validator)来实现。

public class UserDto{@NotNull(message = "起始日期不能为空")private LocalDate startDate;@NotNull(message = "结束日期不能为空")private LocalDate endDate;@AssertTrue(message = "结束日期必须晚于起始日期")private boolean isEndDateAfterStartDate(){if (startDate == null || endDate == null) {returntrue;}return endDate.isAfter(startDate);}
}

在上述示例中,使用了 @AssertTrue注解来标记自定义的校验方法 isEndDateAfterStartDate()。该方法检查 endDate是否晚于 startDate,如果校验失败,将返回指定的错误提示信息。

分组校验

在实际项目中,可能多个方法需要使用同一个DTO类来接收参数,而不同方法的校验规则很可能是不一样的。这个时候,简单地在DTO类的字段上加约束注解无法解决这个问题。因此,spring-validation支持了分组校验的功能,专门用来解决这类问题。还是上面的例子,比如保存User的时候,UserId是可空的,但是更新User的时候,UserId的值必须>=10000000000000000L;其它字段的校验规则在两种情况下一样。这个时候使用分组校验的代码示例如下:

约束注解上声明适用的分组信息groups

@Data
public class UserDTO {@Min(value = 10000000000000000L, groups = Update.class)private Long userId;@NotNull(groups = {Save.class, Update.class})@Length(min = 2, max = 10, groups = {Save.class, Update.class})private String userName;@NotNull(groups = {Save.class, Update.class})@Length(min = 6, max = 20, groups = {Save.class, Update.class})private String account;@NotNull(groups = {Save.class, Update.class})@Length(min = 6, max = 20, groups = {Save.class, Update.class})private String password;/*** 保存的时候校验分组*/public interface Save {}/*** 更新的时候校验分组*/public interface Update {}
}

@Validated注解上指定校验分组

@PostMapping("/save")
public Result saveUser(@RequestBody @Validated(UserDTO.Save.class) UserDTO userDTO) {// 校验通过,才会执行业务逻辑处理return Result.ok();
}@PostMapping("/update")
public Result updateUser(@RequestBody @Validated(UserDTO.Update.class) UserDTO userDTO) {// 校验通过,才会执行业务逻辑处理return Result.ok();
}

嵌套(递归)校验

前面的示例中,DTO类里面的字段都是基本数据类型和String类型。但是实际场景中,有可能某个字段也是一个对象,这种情况先,可以使用嵌套校验。比如,上面保存User信息的时候同时还带有Job信息。需要注意的是,此时如果是 post json 请求,无法完成对嵌套类字段校验,必须在 DTO类的对应字段必须标记@Valid注解。但如果是 GET 请求,是正常校验 job 下的所有属性的

@Data
public class UserDTO {@Min(value = 10000000000000000L, groups = Update.class)private Long userId;@NotNull(groups = {Save.class, Update.class})@Length(min = 2, max = 10, groups = {Save.class, Update.class})private String userName;@NotNull(groups = {Save.class, Update.class})@Length(min = 6, max = 20, groups = {Save.class, Update.class})private String account;@NotNull(groups = {Save.class, Update.class})@Length(min = 6, max = 20, groups = {Save.class, Update.class})private String password;@NotNull(groups = {Save.class, Update.class})@Validprivate Job job;@Datapublic static class Job {@Min(value = 1, groups = Update.class)private Long jobId;@NotNull(groups = {Save.class, Update.class})@Length(min = 2, max = 10, groups = {Save.class, Update.class})private String jobName;@NotNull(groups = {Save.class, Update.class})@Length(min = 2, max = 10, groups = {Save.class, Update.class})private String position;}/*** 保存的时候校验分组*/public interface Save {}/*** 更新的时候校验分组*/public interface Update {}
}

集合校验

就像下面的写法,方法的参数为集合时,如何检验元素的约束呢?

/*** 集合类型参数元素.** @param student the student* @return the rest*/
@PostMapping("/batchadd")
public Rest<?> batchAddStudent(@Valid @RequestBody List<Student> student) {return RestBody.okData(student);
}

同样是在类上添加@Validated注解。注意一定要添加到方法所在的类上才行。这时候会抛出ConstraintViolationException异常

自定义校验

业务需求总是比框架提供的这些简单校验要复杂的多,可以自定义校验来满足需求。自定义Spring validation非常简单,如下两个案例

1、 假设自定义加密id(由数字或者a-f的字母组成,32-256长度)校验,主要分为两步:

自定义约束注解

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EncryptIdValidator.class})
public @interface EncryptId {// 默认错误消息String message() default "加密id格式错误";// 分组Class<?>[] groups() default {};// 负载Class<? extends Payload>[] payload() default {};
}

实现ConstraintValidator接口自定义校验器

public class EncryptIdValidator implements ConstraintValidator<EncryptId, String> {private static final Pattern PATTERN = Pattern.compile("^[a-f\\d]{32,256}$");@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {// 不为null才进行校验if (value != null) {Matcher matcher = PATTERN.matcher(value);return matcher.find();}return true;}
}

这样就可以使用@EncryptId进行参数校验了!

2、 校验集合中的指定属性是否存在重复

实现校验注解,功能如注释所示

@Target({ElementType.FIELD, ElementType.PARAMETER})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
// 指定校验器
@Constraint(validatedBy = UniqueValidator.class)  
public @interface Unique {  // 用于自定义验证信息String message() default "字段存在重复";  // 指定集合中的待校验字段String[] field();  // 指定分组Class<?>[] groups() default {};  
}

实现对应的校验器,主要校验逻辑在 isValid 方法:获取集合中指定字段,并组装为 set,比较 set 和集合的长度,以判断集合中指定字段是否存在重复。

// 实现ConstraintValidator<T, R>接口,T为注解的类型,R为注解的字段类型
public class UniqueValidator implements ConstraintValidator<Unique, Collection<?>> {  private Unique unique;  @Override  public void initialize(Unique constraintAnnotation) {  this.unique = constraintAnnotation;  }  @Override  public boolean isValid(Collection collection, ConstraintValidatorContext constraintValidatorContext) {// 集合为空直接校验通过if (collection == null || collection.size() == 0) {  return Boolean.TRUE;  }  // 从集合中获取filed中指定的待校验字段,看是否存在重复return Arrays.stream(unique.field())  .filter(fieldName -> fieldName != null && !"".equals(fieldName.trim()))  .allMatch(fieldName -> {// 收集集合collection中字段为fieldName的值,存入set并计算set的元素个数countint count = (int) collection.stream()  .filter(Objects::nonNull)  .map(item -> {  Class<?> clazz = item.getClass();  Field field;  try {  field = clazz.getField(fieldName);  field.setAccessible(true);  return field.get(item);  } catch (Exception e) {  return null;  }  })  .collect(Collectors.collectingAndThen(Collectors.toSet(), Set::size)); // set中元素个数count与集合长度比较,若不相等则说明collection中字段存在重复,校验不通过if (count != collection.size()) {  return false;  }  return true;  });  }  
}

枚举校验

在后台定义了枚举值来进行状态的流转,也是需要校验的,比如定义了颜色枚举:

public enum Colors {RED, YELLOW, BLUE}

希望入参不能超出Colors的范围[“RED”, “YELLOW”, “BLUE”],这就需要实现ConstraintValidator<A extends Annotation, T>接口来定义一个颜色约束了,
其中泛型A为自定义的约束注解,泛型T为入参的类型,这里使用字符串,实现如下:

public class ColorConstraintValidator implements ConstraintValidator<Color, String> {private static final Set<String> COLOR_CONSTRAINTS = new HashSet<>();@Overridepublic void initialize(Color constraintAnnotation) {Colors[] value = constraintAnnotation.value();List<String> list = Arrays.stream(value).map(Enum::name).collect(Collectors.toList());COLOR_CONSTRAINTS.addAll(list);}@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {return COLOR_CONSTRAINTS.contains(value);}
}

然后声明对应的约束注解Color,需要在元注解@Constraint中指明使用上面定义好的处理类ColorConstraintValidator进行校验。

@Constraint(validatedBy = ColorConstraintValidator.class)
@Documented
@Target({ElementType.METHOD, ElementType.FIELD,ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Color {// 错误提示信息String message() default "颜色不符合规格";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};// 约束的类型Colors[] value();
}

然后来试一下,先对参数进行约束:

@Data
public class Param {@Color({Colors.BLUE,Colors.YELLOW})private String color;
}

接口跟上面几个一样,调用下面的接口将抛出BindException异常

编程式校验

上面的示例都是基于注解来实现自动校验的,在某些情况下,可能希望以编程方式调用验证。

1、配置validator

@Configuration  
public class ValidatorConfiguration {  @Bean  public Validator validator() {  ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)  .configure()  // 设置是否开启快速失败模式  //.failFast(true)  .buildValidatorFactory();  return validatorFactory.getValidator();  }  
}

2、获取 validator 并校验

@Autowired
private javax.validation.Validator validator;// 编程式校验
@PostMapping("/saveWithCodingValidate")
public Result saveWithCodingValidate(@RequestBody UserDTO userDTO) {Set<ConstraintViolation<UserDTO>> validate = globalValidator.validate(userDTO, UserDTO.Save.class);// 如果校验通过,validate为空;否则,validate包含未校验通过项if (validate.isEmpty()) {// 校验通过,才会执行业务逻辑处理} else {for (ConstraintViolation<UserDTO> userDTOConstraintViolation : validate) {// 校验失败,做其它逻辑System.out.println(userDTOConstraintViolation);}}return Result.ok();
}

快速失败(Fail Fast)

Spring Validation默认会校验完所有字段,然后才抛出异常。可以通过一些简单的配置,开启Fali Fast模式,一旦校验失败就立即返回。

@Bean
public Validator validator() {ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure()// 快速失败模式.failFast(true).buildValidatorFactory();return validatorFactory.getValidator();
}

Dubbo 接口校验

  1. 可在@DubboService注解中,设置validation参数为true开启生产者的字段验证
@DubboService(version = "1.0.0", validation="true")
public class DubboApiImpl implements DubboApi {}
  1. 该方式返回的信息对使用者不友好,可通过 Dubbo 的 filter自定义校验逻辑和返回信息。需要注意的是,在 Dubbo 中有自己的 IOC 实现来控制容器,因此需提供 setter 方法,供 Dubbo 调用。
@Activate(  group = {"provider"},  value = {"customValidationFilter"},  order = 10000  
)  
@Slf4j  
public class CustomValidationFilter implements Filter {  private javax.validation.Validator validator;  // duubo会调用setter获取beanpublic void setValidator(javax.validation.Validator validator) {  this.validator = validator;  } public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {  if (this.validator != null && !invocation.getMethodName().startsWith("$")) {  // 补充字段校验,返回信息的组装以及异常处理}  return invoker.invoke(invocation);  }  
}

校验结果接收

BindingResult接收

这种方式需要在Controller层的每个接口方法参数中指定,Validator会将校验的信息自动封装到其中。这也是上面例子中一直用的方式。如下:

@PostMapping("/add")
public String add(@Valid @RequestBody ArticleDTO articleDTO, BindingResult bindingResult){}

这种方式的弊端很明显,每个接口方法参数都要声明,同时每个方法都要处理校验信息,显然不现实,舍弃。这种写法不会将异常处理的结果返回给全局异常处理

此种方式还有一个优化的方案:使用AOP,在Controller接口方法执行之前处理BindingResult的消息提示,不过这种方案仍然不推荐使用。

统一异常处理接收

如果校验失败,会抛出MethodArgumentNotValidException或者ConstraintViolationException异常。在实际项目开发中,通常会用统一异常处理来返回一个更友好的提示。比如系统要求无论发送什么异常,http的状态码必须返回200,由业务码去区分系统的异常情况。

@RestControllerAdvice
public class CommonExceptionHandler {@ExceptionHandler({MethodArgumentNotValidException.class})@ResponseStatus(HttpStatus.OK)@ResponseBodypublic Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {BindingResult bindingResult = ex.getBindingResult();StringBuilder sb = new StringBuilder("校验失败:");for (FieldError fieldError : bindingResult.getFieldErrors()) {sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");}String msg = sb.toString();return Result.fail(BusinessCode.参数校验失败, msg);}@ExceptionHandler({ConstraintViolationException.class})@ResponseStatus(HttpStatus.OK)@ResponseBodypublic Result handleConstraintViolationException(ConstraintViolationException ex) {return Result.fail(BusinessCode.参数校验失败, ex.getMessage());}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/92901.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/92901.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker部署 Neo4j 及集成 APOC 插件:安装与配置完整指南(docker-compose)

Docker部署 Neo4j 及集成 APOC 插件&#xff1a;分步骤指南 摘要 &#xff1a;本文将分两部分详细介绍相关内容。第一部分讲解如何使用 Docker Compose 部署 Neo4j 图数据库&#xff0c;提供完整配置文件及常见问题解决方案&#xff1b;第二部分在前者基础上&#xff0c;介绍 A…

TLSv1.2协议与TCP/UDP协议传输数据内容差异

一、Wireshark中常见的TLSv1.2在用Wireshark抓包时&#xff0c;除了看到课堂上教过的经典的TCP/UDP协议&#xff0c;还有一个协议经常出现——TLSv1.2。并且这个协议的Info解释是Application data&#xff0c;其实看到这个解释&#xff0c;我大概猜出来了TLSv1.2是用来给用户数…

51c自动驾驶~合集14

自己的原文哦~ https://blog.51cto.com/whaosoft/11707335 #Text2LiDAR 文本引导的无条件点云生成新SOTA 论文题目&#xff1a;《Text2LiDAR: Text-guided LiDAR Point Cloud Generation via Equirectangular Transformer》 论文地址&#xff1a;https://arxiv.o…

k8s基本概念

k8s 的基本概念 Kubernetes是一个可以移植、可扩展的开源平台&#xff0c;使用 声明式的配置 并依据配置信息自动地执行容器化应用程序的管理。在所有的容器编排工具中&#xff08;类似的还有 docker swarm / mesos等&#xff09;&#xff0c;Kubernetes的生态系统更大、增长更…

Easysearch 数据迁移之数据比对

上一篇我们通过 INFINI Gateway 进行了索引数据迁移&#xff0c;对索引迁移结果进行了初步且直观的校验--对比索引的文档数是否一致。今天介绍个实实在在的数据比对方法&#xff0c;通过网关对比索引文档的内容在两个集群是否一致。话不多说&#xff0c;就拿上次迁移的两个索引…

Codeforces Round 1042 (Div. 3)

ABCD 略E注意到每个操作最多执行一次&#xff0c;ifa[i]!b[i]&#xff0c;要么a[i]^a[i1]要么a[i]^b[i1]G设消除1~i的数的操作次数为f[i]&#xff0c;可以推出f[i]2*f[i-1]1&#xff0c;那么消除1~i的数的分数乘的数为g[i]&#xff0c;g[i]g[i-1]*g[i-1]*i s虽然很大&#xff0…

AJAX:让你的网页“静悄悄”变聪明,体验丝滑升级

大家好&#xff0c;今天想聊聊一个让网页“活”起来的小秘密——AJAX。你可能遇到过这种情况&#xff1a;点个按钮&#xff0c;页面就刷新&#xff0c;等得心急火燎。但用了AJAX的网站&#xff0c;比如购物车更新或搜索建议&#xff0c;数据嗖嗖就来了&#xff0c;整个页面却纹…

【iOS】Block基础知识和底层探索

文章目录前言Block的声明和创建问题引入Block的底层结构Block的执行流程Block的创建与存储Block的传递与调用Block的捕获机制捕获局部变量捕获全局变量小结Block的类型__block修饰符__block变量的包装结构体block的实例结构体block的执行逻辑Block循环引用造成的原因解决方法小…

1.Ansible 自动化介绍

1-Ansible 自动化介绍 Ansible 自动化介绍 手动执行任务和自动化执行任务 手动执行任务的麻烦事&#xff1a; 很容易漏掉某个步骤&#xff0c;或者不小心执行错步骤&#xff0c;而且很难验证每个步骤是不是真的按预期完成了。管理一大堆服务器时&#xff0c;很容易出现配置…

2025年云手机场景适配的行业观察

2025年的市场中&#xff0c;云手机品牌百花齐放&#xff0c;不同品牌在性能、功能和场景适配性上的差异日益显著。随着云计算技术的快速发展&#xff0c;云手机已从 尝鲜工具 演变为游戏、办公、企业运营等场景的刚需工具。现市面上也有着更多的云手机品牌&#xff0c;结合实测…

Date/Calendar/DateFormat/LocalDate

作用说明Date用于定义时间&#xff0c;提供date对象间的比较方法Calendar(日历类),提供对时间的运算方法DateFormat是接口&#xff0c;它的实现类SimpleDateFormat用来规范时间输出形式LocalDate&#xff0c;在JDK1.8之后引入&#xff0c;方便了对时间的运算方法介绍Date常用方…

在Python 3.8环境中安装Python 3.6兼容包的方法

在Python 3.8环境中安装Python 3.6兼容包的方法 用户的需求是&#xff1a;在Python 3.8环境中重新安装原本为Python 3.6设计的包。这通常涉及兼容性问题&#xff0c;因为Python 3.8可能引入了一些语法或API变更&#xff0c;导致旧包无法直接运行。以下是逐步解决方案&#xff…

三种DuckDB电子表格插件的union all查询性能对比

我选取了最稳定、兼容性最好的三种&#xff1a;官方excel对应函数read_xlsx()、官方spatial对应函数st_read()、rusty_sheet对应函数read_sheet。 1.建立两个包含前50万和后54万的xlsx文件&#xff0c;用于比较。利用官方excel的copy()to进行。 D copy (from v1 order by l_ord…

Python 中使用多进程编程的“三两”问题

文章目录一、简介二、选择合适的启动方式三、手动终止所有的进程小结一、简介 这里简单介绍在Python中使用多进程编程的时候容易遇到的情况和解决办法&#xff0c;有助于排查和规避某类问题&#xff0c;但是具体问题还是需要具体分析&#xff0c;后续会补充更多的内容。 二、…

Ansible部署应用

目录Ansible概述1&#xff1a;什么是Ansible2&#xff1a;Ansible的架构组成3&#xff1a;Ansible与SaltStack的对比安装部署Ansible服务1&#xff1a;系统环境设置2&#xff1a;安装Ansible&#xff08;第一台&#xff09;2&#xff1a;配置主机清单3&#xff1a;修改Ansible配…

疏老师-python训练营-Day44预训练模型

浙大疏锦行 知识点回顾&#xff1a; 预训练的概念常见的分类预训练模型图像预训练模型的发展史预训练的策略预训练代码实战&#xff1a;resnet18 作业&#xff1a; 尝试在cifar10对比如下其他的预训练模型&#xff0c;观察差异&#xff0c;尽可能和他人选择的不同尝试通过ctrl进…

AI入门学习--如何写好prompt?

写好Prompt&#xff08;提示词&#xff09;是驾驭AI模型的核心技能。以下是结合测试工程师需求的 结构化方法论 和 黄金模板一、prompt设计金字塔终极心法&#xff1a; Prompt 对AI的测试需求文档&#xff0c;需像设计测试用例一样&#xff1a;可执行&#xff1a;明确输入输出…

Linux编程 IO(标准io,文件io,目录io)

标准IO C语言标准IO概述标准IO&#xff08;Standard Input/Output&#xff09;是C语言中用于处理文件和数据流的一组函数库&#xff0c;定义在<stdio.h>头文件中。与低级IO&#xff08;如read/write&#xff09;相比&#xff0c;标准IO提供了缓冲机制&#xff0c;提高了数…

C# WPF本地Deepseek部署

模型下载地址 using LLama; using LLama.Common; using System; using System.IO; using System.Threading.Tasks; using System.Windows; using System.Windows.Input;namespace YF_Talk {public partial class MainWindow : Window{private LLamaWeights _model;private LLa…

【Abp.VNext】Abp.Vnext框架模块学习

1、Abp.Vnext-集成 Volo.Abp.Core2、Abp.vNext-Web模块 Volo.Abp.AspNetCore.MVC框架&#xff08;framework文件夹&#xff09; 七、Abp.vNext-应用模块-Identity身份认证 业务模块&#xff08;modules文件夹->identity&#xff09; 1、添加领域模型 Volo.Abp.Identity.Doma…