SpringCloud里參數(shù)校驗/參數(shù)驗證
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
1、前言
在控制器類的方法里自己寫校驗邏輯代碼當然也可以,只是代碼比較丑陋,有點“l(fā)ow”。業(yè)界有更好的處理方法,分別闡述如下。
2、PathVariable校驗
@GetMapping("/path/{group:[a-zA-Z0-9_]+}/{userid}")
@ResponseBody
public?String?path(@PathVariable("group")?String?group,?@PathVariable("userid")?Integer?userid)?{
????return?group?+?":"?+?userid;
}
用法是:路徑變量:正則表達式。當請求URI不滿足正則表達式時,客戶端將收到404錯誤碼。不方便的地方是,不能通過捕獲異常的方式,向前端返回統(tǒng)一的、自定義格式的響應參數(shù)。
3、方法參數(shù)校驗
@GetMapping("/validate1")
@ResponseBody
public?String?validate1(
????????@Size(min?=?1,max?=?10,message?=?"姓名長度必須為1到10")@RequestParam("name")?String?name,
????????@Min(value?=?10,message?=?"年齡最小為10")@Max(value?=?100,message?=?"年齡最大為100")?@RequestParam("age")?Integer?age)?{
????return?"validate1";
}
如果前端傳遞的參數(shù)不滿足規(guī)則,則拋出異常。注解Size、Min、Max來自validation-api.jar,更多注解參見相關標準小節(jié)。
4、表單對象/VO對象校驗
當參數(shù)是VO時,可以在VO類的屬性上添加校驗注解。
public?class?User?{
????@Size(min?=?1,max?=?10,message?=?"姓名長度必須為1到10")
????private?String?name;
?
????@NotEmpty
????private?String?firstName;
?
????@Min(value?=?10,message?=?"年齡最小為10")@Max(value?=?100,message?=?"年齡最大為100")
????private?Integer?age;
?
????@Future
????@JSONField(format="yyyy-MM-dd?HH:mm:ss")
????private?Date?birth;
????。。。
}
其中,F(xiàn)uture注解要求必須是相對當前時間來講“未來的”某個時間。
@PostMapping("/validate2")
@ResponseBody
public?User?validate2(@Valid?@RequestBody?User?user){
????return?user;
}
5、自定義校驗規(guī)則
5.1 自定義注解校驗
需要自定義一個注解類和一個校驗類。
import?javax.validation.Constraint;
import?javax.validation.Payload;
import?java.lang.annotation.*;
?
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER,ElementType.FIELD})
@Constraint(validatedBy?=?FlagValidatorClass.class)
public?@interface?FlagValidator?{
????//?flag的有效值,多個使用,隔開
????String?values();
?
????//?flag無效時的提示內(nèi)容
????String?message()?default?"flag必須是預定義的那幾個值,不能隨便寫";
?
????Class>[]?groups()?default?{};
?
????Class?extends?Payload>[]?payload()?default?{};
}
import?javax.validation.ConstraintValidator;
import?javax.validation.ConstraintValidatorContext;
?
public?class?FlagValidatorClass?implements?ConstraintValidator?{
????/**
?????*?FlagValidator注解規(guī)定的那些有效值
?????*/
????private?String?values;
?
????@Override
????public?void?initialize(FlagValidator?flagValidator)?{
????????this.values?=?flagValidator.values();
????}
?
????/**
?????*?用戶輸入的值,必須是FlagValidator注解規(guī)定的那些值其中之一。
?????*?否則,校驗不通過。
?????*?@param?value?用戶輸入的值,如從前端傳入的某個值
?????*/
????@Override
????public?boolean?isValid(Object?value,?ConstraintValidatorContext?constraintValidatorContext)?{
????????//?切割獲取值
????????String[]?value_array?=?values.split(",");
????????Boolean?isFlag?=?false;
?
????????for?(int?i?=?0;?i?????????????//?存在一致就跳出循環(huán)
????????????if?(value_array[i]?.equals(value)){
????????????????isFlag?=?true;?break;
????????????}
????????}
?
????????return?isFlag;
????}
}
使用我們自定義的注解:
public?class?User?{
????//?前端傳入的flag值必須是1或2或3,否則校驗失敗
????@FlagValidator(values?=?"1,2,3")
????private?String?flag?;
????。。。
}
5.2 分組校驗
import?org.hibernate.validator.constraints.Length;
import?javax.validation.constraints.Min;
import?javax.validation.constraints.NotNull;
?
public?class?Resume?{
????public?interface?Default?{
????}
?
????public?interface?Update?{
????}
?
????@NotNull(message?=?"id不能為空",?groups?=?Update.class)
????private?Long?id;
?
????@NotNull(message?=?"名字不能為空",?groups?=?Default.class)
????@Length(min?=?4,?max?=?10,?message?=?"name?長度必須在?{min}?-?{max}?之間",?groups?=?Default.class)
????private?String?name;
?
????@NotNull(message?=?"年齡不能為空",?groups?=?Default.class)
????@Min(value?=?18,?message?=?"年齡不能小于18歲",?groups?=?Default.class)
????private?Integer?age;
????。。。
}
????/**
?????*?使用Defaul分組進行驗證
?????*?@param?resume
?????*?@return
?????*/
????@PostMapping("/validate5")
????public?String?addUser(@Validated(value?=?Resume.Default.class)?@RequestBody?Resume?resume)?{
????????return?"validate5";
????}
?
????/**
?????*?使用Default、Update分組進行驗證
?????*?@param?resume
?????*?@return
?????*/
????@PutMapping("/validate6")
????public?String?updateUser(@Validated(value?=?{Resume.Update.class,?Resume.Default.class})?@RequestBody?Resume?resume)?{
????????return?"validate6";
????}
建立了兩個分組,名稱分別為Default、Update。POST方法提交時使用Defaut分組的校驗規(guī)則,PUT方法提交時同時使用兩個分組規(guī)則。
6、異常攔截器
通過設置全局異常處理器,統(tǒng)一向前端返回校驗失敗信息。
import?com.scj.springbootdemo.WebResult;
import?org.slf4j.Logger;
import?org.slf4j.LoggerFactory;
import?org.springframework.util.CollectionUtils;
import?org.springframework.validation.ObjectError;
import?org.springframework.web.bind.MethodArgumentNotValidException;
import?org.springframework.web.bind.annotation.ControllerAdvice;
import?org.springframework.web.bind.annotation.ExceptionHandler;
import?org.springframework.web.bind.annotation.ResponseBody;
?
import?javax.validation.ConstraintViolation;
import?javax.validation.ConstraintViolationException;
import?java.util.List;
import?java.util.Set;
?
/**
?*?全局異常處理器
?*/
@ControllerAdvice
public?class?GlobalExceptionHandler?{
?
????private?Logger?logger?=?LoggerFactory.getLogger(GlobalExceptionHandler.class);
?
????/**
?????*?用來處理bean?validation異常
?????*?@param?ex
?????*?@return
?????*/
????@ExceptionHandler(ConstraintViolationException.class)
????@ResponseBody
????public??WebResult?resolveConstraintViolationException(ConstraintViolationException?ex){
????????WebResult?errorWebResult?=?new?WebResult(WebResult.FAILED);
????????Set>?constraintViolations?=?ex.getConstraintViolations();
????????if(!CollectionUtils.isEmpty(constraintViolations)){
????????????StringBuilder?msgBuilder?=?new?StringBuilder();
????????????for(ConstraintViolation?constraintViolation?:constraintViolations){
????????????????msgBuilder.append(constraintViolation.getMessage()).append(",");
????????????}
????????????String?errorMessage?=?msgBuilder.toString();
????????????if(errorMessage.length()>1){
????????????????errorMessage?=?errorMessage.substring(0,errorMessage.length()-1);
????????????}
????????????errorWebResult.setInfo(errorMessage);
????????????return?errorWebResult;
????????}
????????errorWebResult.setInfo(ex.getMessage());
????????return?errorWebResult;
????}
?
????@ExceptionHandler(MethodArgumentNotValidException.class)
????@ResponseBody
????public?WebResult?resolveMethodArgumentNotValidException(MethodArgumentNotValidException?ex){
????????WebResult?errorWebResult?=?new?WebResult(WebResult.FAILED);
????????List??objectErrors?=?ex.getBindingResult().getAllErrors();
????????if(!CollectionUtils.isEmpty(objectErrors))?{
????????????StringBuilder?msgBuilder?=?new?StringBuilder();
????????????for?(ObjectError?objectError?:?objectErrors)?{
????????????????msgBuilder.append(objectError.getDefaultMessage()).append(",");
????????????}
????????????String?errorMessage?=?msgBuilder.toString();
????????????if?(errorMessage.length()?>?1)?{
????????????????errorMessage?=?errorMessage.substring(0,?errorMessage.length()?-?1);
????????????}
????????????errorWebResult.setInfo(errorMessage);
????????????return?errorWebResult;
????????}
????????errorWebResult.setInfo(ex.getMessage());
????????return?errorWebResult;
????}
}
?
7、相關標準
JSR 303 是Bean驗證的規(guī)范 ,Hibernate Validator 是該規(guī)范的參考實現(xiàn),它除了實現(xiàn)規(guī)范要求的注解外,還額外實現(xiàn)了一些注解。
validation-api-1.1.0.jar 包括如下約束注解:
| 約束注解 | 說明 |
|---|---|
| @AssertFalse | 被注釋的元素必須為 false |
| @AssertTrue | 被注釋的元素必須為 true |
| @DecimalMax(value) | 被注釋的元素必須是一個數(shù)字,其值必須小于等于指定的最大值 |
| @DecimalMin(value) | 被注釋的元素必須是一個數(shù)字,其值必須大于等于指定的最小值 |
| @Digits (integer, fraction) | 被注釋的元素必須是一個數(shù)字,其值必須在可接受的范圍內(nèi) |
| @Null | 被注釋的元素必須為 null |
| @NotNull | 被注釋的元素必須不為 null |
| @Min(value) | 被注釋的元素必須是一個數(shù)字,其值必須大于等于指定的最小值 |
| @Max(value) | 被注釋的元素必須是一個數(shù)字,其值必須小于等于指定的最大值 |
| @Size(max, min) | 被注釋的元素的大小必須在指定的范圍內(nèi) |
| @Past | 被注釋的元素必須是一個過去的日期 |
| @Future | 被注釋的元素必須是一個將來的日期 |
| @Pattern(value) | 被注釋的元素必須符合指定的正則表達式 |
hibernate-validator-5.3.6.jar 包括如下約束注解:
| 約束注解 | 說明 |
|---|---|
| 被注釋的元素必須是電子郵箱地址 | |
| @Length | 被注釋的字符串的大小必須在指定的范圍內(nèi) |
| @NotBlank | 被注釋的字符串的必須非空 |
| @NotEmpty | 被注釋的字符串、集合、Map、數(shù)組必須非空 |
| @Range | 被注釋的元素必須在合適的范圍內(nèi) |
| @SafeHtml | 被注釋的元素必須是安全Html |
| @URL | 被注釋的元素必須是有效URL |
| 略 |
8、參數(shù)校驗原理
這篇文章?寫得比較深入,我沒有太理解。
9、本文源碼
公司不讓上傳源碼到GitHub,可以參加這篇文章。
10、同時校驗2個或更多個字段/參數(shù)
常見的場景之一是,查詢某信息時要輸入開始時間和結束時間。顯然,結束時間要≥開始時間。可以在查詢VO類上使用自定義注解,下面的例子來自這里。劃重點:@ValidAddress使用在類上。
@ValidAddress
public?class?Address?{
?
????@NotNull
????@Size(max?=?50)
????private?String?street1;
?
????@Size(max?=?50)
????private?String?street2;
?
????@NotNull
????@Size(max?=?10)
????private?String?zipCode;
?
????@NotNull
????@Size(max?=?20)
????private?String?city;
?
????@Valid
????@NotNull
????private?Country?country;
?
????//?Getters?and?setters
}
public?class?Country?{
?
????@NotNull
????@Size(min?=?2,?max?=?2)
????private?String?iso2;
?
????//?Getters?and?setters
}@Documented
@Target(TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy?=?{?MultiCountryAddressValidator.class?})
public?@interface?ValidAddress?{
?
????String?message()?default?"{com.example.validation.ValidAddress.message}";
?
????Class>[]?groups()?default?{};
?
????Class?extends?Payload>[]?payload()?default?{};
}
public?class?MultiCountryAddressValidator?
???????implements?ConstraintValidator?{
?
????public?void?initialize(ValidAddress?constraintAnnotation)?{
?
????}
?
????@Override
????public?boolean?isValid(Address?address,?
???????????????????????????ConstraintValidatorContext?constraintValidatorContext)?{
?
????????Country?country?=?address.getCountry();
????????if?(country?==?null?||?country.getIso2()?==?null?||?address.getZipCode()?==?null)?{
????????????return?true;
????????}
?
????????switch?(country.getIso2())?{
????????????case?"FR":
????????????????return?//?Check?if?address.getZipCode()?is?valid?for?France
????????????case?"GR":
????????????????return?//?Check?if?address.getZipCode()?is?valid?for?Greece
????????????default:
????????????????return?true;
????????}
????}
}
原文鏈接:
https://blog.csdn.net/jinjiankang/article/details/89711493


??? ?
感謝點贊支持下哈?
