前言
在日常的開發中,引數校驗是非常重要的一個環節,嚴格引數校驗會減少很多出bug的概率,增加介面的安全性。在此之前寫過一篇SpringBoot統一引數校驗主要介紹了一些簡單的校驗方法。而這篇則是介紹一些進階的校驗方式。比如說:在某個介面編寫的過程中肯定會遇到,當xxType值為A,paramA值必傳。xxType值為B,paramB值必須傳。對於這樣的,通常的做法就是在controller加上各種if判斷。顯然這樣的程式碼是不夠優雅的,而分組校驗及自定義引數校驗,就是來解決這個問題的。
PathVariable引數校驗
Restful的介面,在現在來講應該是比較常見的了,常用的位址列的引數,我們都是這樣校驗的。
/**
* 獲取電話號碼資訊
*/
@GetMapping("/phoneInfo/{phone}")
public ResultVo phoneInfo(@PathVariable("phone") String phone){
// 驗證電話號碼是否有效
String pattern = "^[1][3,4,5,7,8][0-9]{9}$";
boolean isValid = Pattern.matches(pattern, phone);
if(isValid){
// 執行相應邏輯
return ResultVoUtil.success(phone);
} else {
// 返回錯誤資訊
return ResultVoUtil.error("手機號碼無效");
}
}
很顯然上面的程式碼不夠優雅,所以我們可以在引數後面,新增對應的正規表示式phone:正規表示式
來進行驗證。這樣就省去了在controller編寫校驗程式碼了。
/**
* 獲取電話號碼資訊
*/
@GetMapping("/phoneInfo/{phone:^[1][3,4,5,7,8][0-9]{9}$}")
public ResultVo phoneInfo(@PathVariable("phone") String phone){
return ResultVoUtil.success(phone);
}
雖然這樣處理後程式碼更精簡了。但是如果傳入的手機號碼,不符合規則會直接返回404。而不是提示手機號碼錯誤。錯誤資訊如下:
自定義校驗註解
我們以校驗手機號碼為例,雖然validation
提供了@Pattern
這個註解來使用正規表示式進行校驗。如果被使用在多處,一旦正規表示式發生更改,則需要一個一個的進行修改。很顯然為了避免做這樣的無用功,自定義校驗註解
就是你的好幫手。
@Data
public class PhoneForm {
/**
* 電話號碼
*/
@Pattern(regexp = "^[1][3,4,5,7,8][0-9]{9}$" , message = "電話號碼有誤")
private String phone;
}
要實現一個自定義校驗註解,主要是有兩步。一是註解本身,二是校驗邏輯實現類。
PhoneVerify 校驗註解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
String message() default "手機號碼格式有誤";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
PhoneValidator 校驗實現類
public class PhoneValidator implements ConstraintValidator<Phone, Object> {
@Override
public boolean isValid(Object telephone, ConstraintValidatorContext constraintValidatorContext) {
String pattern = "^1[3|4|5|7|8]\\d{9}$";
return Pattern.matches(pattern, telephone.toString());
}
}
CustomForm 表單資料
@Data
public class CustomForm {
/**
* 電話號碼
*/
@Phone
private String phone;
}
測試介面
@PostMapping("/customTest")
public ResultVo customTest(@RequestBody @Validated CustomForm form){
return ResultVoUtil.success(form.getPhone());
}
註解的含義
@Target({ElementType.FIELD})
註解是指定當前自定義註解可以使用在哪些地方,這裡僅僅讓他可以使用屬性上。但還可以使用在更多的地方,比如說方法上、構造器上等等。
- TYPE - 類,介面(包括註解型別)或列舉
- FIELD - 欄位(包括列舉常量)
- METHOD - 方法
- PARAMETER - 引數
- CONSTRUCTOR - 建構函式
- LOCAL_VARIABLE - 區域性變數
- ANNOTATION_TYPE -註解型別
- PACKAGE - 包
- TYPE_PARAMETER - 型別引數
- TYPE_USE - 使用型別
@Retention(RetentionPolicy.RUNTIME)
指定當前註解保留到執行時。保留策略有下面三種:
- SOURCE - 註解只保留在原始檔,當Java檔案編譯成class檔案的時候,註解被遺棄。
- CLASS - 註解被保留到class檔案,但jvm載入class檔案時候被遺棄,這是預設的生命週期。
- RUNTIME - 註解不僅被儲存到class檔案中,jvm載入class檔案之後,仍然存在。
@Constraint(validatedBy = PhoneValidator.class)
指定了當前註解使用哪個校驗類來進行校驗。
分組校驗
UserForm
@Data
public class UserForm {
/**
* id
*/
@Null(message = "新增時id必須為空", groups = {Insert.class})
@NotNull(message = "更新時id不能為空", groups = {Update.class})
private String id;
/**
* 型別
*/
@NotEmpty(message = "姓名不能為空" , groups = {Insert.class})
private String name;
/**
* 年齡
*/
@NotEmpty(message = "年齡不能為空" , groups = {Insert.class})
private String age;
}
Insert分組
public interface Insert {
}
Update分組
public interface Update {
}
測試介面
/**
* 新增使用者
*/
@PostMapping("/addUser")
public ResultVo addUser(@RequestBody @Validated({Insert.class}) UserForm form){
// 選擇對應的分組進行校驗
return ResultVoUtil.success(form);
}
/**
* 更新使用者
*/
@PostMapping("/updateUser")
public ResultVo updateUser(@RequestBody @Validated({Update.class}) UserForm form){
// 選擇對應的分組進行校驗
return ResultVoUtil.success(form);
}
測試結果
新增測試
更新測試
順序校驗@GroupSequence
在@GroupSequence
內可以指定,分組校驗的順序。比如說@GroupSequence({Insert.class, Update.class, UserForm.class})
先執行Insert
校驗,然後執行Update
校驗。如果Insert
分組,校驗失敗了,則不會進行Update
分組的校驗。
@Data
@GroupSequence({Insert.class, Update.class, UserForm.class})
public class UserForm {
/**
* id
*/
@Null(message = "新增時id必須為空", groups = {Insert.class})
@NotNull(message = "更新時id不能為空", groups = {Update.class})
private String id;
/**
* 型別
*/
@NotEmpty(message = "姓名不能為空" , groups = {Insert.class})
private String name;
/**
* 年齡
*/
@NotEmpty(message = "年齡不能為空" , groups = {Insert.class})
private String age;
}
測試介面
/**
* 編輯使用者
*/
@PostMapping("/editUser")
public ResultVo editUser(@RequestBody @Validated UserForm form){
return ResultVoUtil.success(form);
}
測試結果
哈哈哈,測試結果其實是個死迴圈,不管你咋輸入都會報錯,小夥伴可以嘗試一下哦。上面的例子只是個演示,在實際中還是別這樣做了,需要根據具體邏輯進行校驗。
自定義分組校驗
對於之前提到了當xxType值為A,paramA值必傳。xxType值為B,paramB值必須傳這樣的場景。單獨使用分組校驗和分組序列是無法實現的。需要使用@GroupSequenceProvider
才行。
自定義分組表單
@Data
@GroupSequenceProvider(value = CustomSequenceProvider.class)
public class CustomGroupForm {
/**
* 型別
*/
@Pattern(regexp = "[A|B]" , message = "型別不必須為 A|B")
private String type;
/**
* 引數A
*/
@NotEmpty(message = "引數A不能為空" , groups = {WhenTypeIsA.class})
private String paramA;
/**
* 引數B
*/
@NotEmpty(message = "引數B不能為空", groups = {WhenTypeIsB.class})
private String paramB;
/**
* 分組A
*/
public interface WhenTypeIsA {
}
/**
* 分組B
*/
public interface WhenTypeIsB {
}
}
CustomSequenceProvider
public class CustomSequenceProvider implements DefaultGroupSequenceProvider<CustomGroupForm> {
@Override
public List<Class<?>> getValidationGroups(CustomGroupForm form) {
List<Class<?>> defaultGroupSequence = new ArrayList<>();
defaultGroupSequence.add(CustomGroupForm.class);
if (form != null && "A".equals(form.getType())) {
defaultGroupSequence.add(CustomGroupForm.WhenTypeIsA.class);
}
if (form != null && "B".equals(form.getType())) {
defaultGroupSequence.add(CustomGroupForm.WhenTypeIsB.class);
}
return defaultGroupSequence;
}
}
測試介面
/**
* 自定義分組
*/
@PostMapping("/customGroup")
public ResultVo customGroup(@RequestBody @Validated CustomGroupForm form){
return ResultVoUtil.success(form);
}
測試結果
Type型別為A
Type型別為B
小結一下
GroupSequence
註解是一個標準的Bean認證註解。正如之前,它能夠讓你靜態的重新定義一個類的,預設校驗組順序。然而GroupSequenceProvider
它能夠讓你動態的定義一個校驗組的順序。
注意的一個點
SpringBoot 2.3.x 移除了validation
依賴需要手動引入依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
總結
個人的一些小經驗,引數的非空判斷,這個應該是校驗的第一步了,除了非空校驗,我們還需要做到下面這幾點:
- 普通引數 - 需要限定欄位的長度。如果會將資料存入資料庫,長度以資料庫為準,反之根據業務確定。
- 型別引數 - 最好使用正則對可能出現的型別做到嚴格校驗。比如
type
的值是【0|1|2】這樣的。 - 列表(list)引數 - 不僅需要對list內的引數是否合格進行校驗,還需要對list的size進行限制。比如說 100。
- 日期,郵件,金額,URL這類引數都需要使用對於的正則進行校驗。
- 引數真實性 - 這個主要針對於 各種
Id
比如說userId
、merchantId
,對於這樣的引數,都需要進行真實性校驗,判斷系統內是有含有,並且對應的狀態是否正常。
引數校驗越嚴格越好,嚴格的校驗規則不僅能減少介面出錯的概率,同時還能避免出現髒資料,從而來保證系統的安全性和穩定性。
錯誤的提醒資訊需要友好一點哦,防止等下被前端大哥吐槽哦。
上期回顧
結尾
如果覺得對你有幫助,可以多多評論,多多點贊哦,也可以到我的主頁看看,說不定有你喜歡的文章,也可以隨手點個關注哦,謝謝。
我是不一樣的科技宅,每天進步一點點,體驗不一樣的生活。我們下期見!