在平常的開發中,參數校驗是很是重要的一個環節,嚴格參數校驗會減小不少出bug的機率,增長接口的安全性。在此以前寫過一篇SpringBoot統一參數校驗主要介紹了一些簡單的校驗方法。而這篇則是介紹一些進階的校驗方式。好比說:在某個接口編寫的過程當中確定會遇到,當xxType值爲A,paramA值必傳。xxType值爲B,paramB值必須傳。對於這樣的,一般的作法就是在controller加上各類if判斷。顯然這樣的代碼是不夠優雅的,而分組校驗及自定義參數校驗,就是來解決這個問題的。前端
Restful的接口,在如今來說應該是比較常見的了,經常使用的地址欄的參數,咱們都是這樣校驗的。java
/** * 獲取電話號碼信息 */ @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。而不是提示手機號碼錯誤。錯誤信息以下:spring
咱們以校驗手機號碼爲例,雖然validation
提供了@Pattern
這個註解來使用正則表達式進行校驗。若是被使用在多處,一旦正則表達式發生更改,則須要一個一個的進行修改。很顯然爲了不作這樣的無用功,自定義校驗註解
就是你的好幫手。數據庫
@Data public class PhoneForm { /** * 電話號碼 */ @Pattern(regexp = "^[1][3,4,5,7,8][0-9]{9}$" , message = "電話號碼有誤") private String phone; }
要實現一個自定義校驗註解,主要是有兩步。一是註解自己,二是校驗邏輯實現類。segmentfault
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = PhoneValidator.class) public @interface Phone { String message() default "手機號碼格式有誤"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
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()); } }
@Data public class CustomForm { /** * 電話號碼 */ @Phone private String phone; }
@PostMapping("/customTest") public ResultVo customTest(@RequestBody @Validated CustomForm form){ return ResultVoUtil.success(form.getPhone()); }
註解是指定當前自定義註解可使用在哪些地方,這裏僅僅讓他可使用屬性上。但還可使用在更多的地方,好比說方法上、構造器上等等。安全
指定當前註解保留到運行時。保留策略有下面三種:app
指定了當前註解使用哪一個校驗類來進行校驗。jvm
@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; }
public interface Insert { }
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
分組的校驗。ide
@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 { } }
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); }
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】這樣的。Id
好比說 userId
、merchantId
,對於這樣的參數,都須要進行真實性校驗,判斷系統內是有含有,而且對應的狀態是否正常。參數校驗越嚴格越好,嚴格的校驗規則不只能減小接口出錯的機率,同時還能避免出現髒數據,從而來保證系統的安全性和穩定性。
錯誤的提醒信息須要友好一點哦,防止等下被前端大哥吐槽哦。
若是以爲對你有幫助,能夠多多評論,多多點贊哦,也能夠到個人主頁看看,說不定有你喜歡的文章,也能夠隨手點個關注哦,謝謝。
我是不同的科技宅,天天進步一點點,體驗不同的生活。咱們下期見!