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
英文地址,請注明出處:http://en.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…