參數校驗
參數校驗可以防止無效或錯誤的數據進入系統。通過校驗前端輸入的參數,可以確保數據的完整性,避免因為缺少必要的信息而導致程序錯誤或異常。例如,對于密碼字段,可以通過校驗規則要求用戶輸入至少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 { // 未提供其他屬性
}
- 作用范圍不同:@Validated 無法作用在于字段, @Valid 無法作用于類
- 注解中的屬性不同:@Validated 中提供了指定校驗分組的屬性,而 @Valid 沒有這個功能,因為 @Valid 不能進行分組校驗
- @Valid 注解支持嵌套校驗,@Validated 不支持嵌套校驗
- @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 接口校驗
- 可在@DubboService注解中,設置validation參數為true開啟生產者的字段驗證
@DubboService(version = "1.0.0", validation="true")
public class DubboApiImpl implements DubboApi {}
- 該方式返回的信息對使用者不友好,可通過 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());}
}