SpringBoot通過自定義注解實現(xiàn)參數(shù)校驗
1. 為什么要進行參數(shù)校驗
在后端進行工作時,需要接收前端傳來的數(shù)據(jù)去數(shù)據(jù)庫查詢,但是如果有些數(shù)據(jù)過于離譜,我們就可以直接把它pass掉,不讓這種垃圾數(shù)據(jù)接觸數(shù)據(jù)庫,減小數(shù)據(jù)庫的壓力。
有時候會有不安分的人通過一些垃圾數(shù)據(jù)攻擊咱們的程序,讓咱們的服務(wù)器或數(shù)據(jù)庫崩潰,這種攻擊雖然低級但不得不防,就像QQ進行登錄請求時,它們向后端發(fā)送 賬號=123,密碼=123 的數(shù)據(jù),一秒鐘還發(fā)1w次,這很明顯就是找事的好吧,什么人類的手速能達到1秒1萬次?
解決方法是:一方面我們可以通過Redis記錄ip/賬號的方式拒絕一部分請求,例如1s中同一個ip/賬號最多請求100次。另一方面就是進行數(shù)據(jù)校驗pass一部分數(shù)據(jù),這100里又多少次是垃圾數(shù)據(jù)。這樣就可以盡量減小服務(wù)器數(shù)據(jù)庫的壓力。
2. 如何實現(xiàn)參數(shù)校驗
實現(xiàn)參數(shù)校驗說實話方式還挺多,個人使用過直接在Controller代碼里面寫、AOP+自定義注解、ConstraintValidator
。本篇博客講的是ConstraintValidator
實現(xiàn)。
直接在Controller代碼里面寫,說實話,寫起來是簡單,但是臃腫,耦合性高,最主要是,不夠優(yōu)雅。
AOP實現(xiàn)有難度,代碼繁瑣,顯得邏輯雜亂。
所以我建議使用ConstraintValidator
。
在這里先提供一個工具類進行參數(shù)校驗,提供了對于手機號、郵箱、驗證碼、密碼、身份證號的驗證方法,可以直接copy來用。等下進行參數(shù)校驗時我使用的就是這個類里的校驗方法。
/** * @description : 驗證手機號、身份證號、密碼、驗證碼、郵箱的工具類 * @author : 小何 */ public class VerifyUtils { /** * 手機號正則 */ public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$"; /** * 郵箱正則 */ public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$"; /** * 密碼正則。4~32位的字母、數(shù)字、下劃線 */ public static final String PASSWORD_REGEX = "^\\w{4,32}$"; /** * 驗證碼正則, 6位數(shù)字或字母 */ public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$"; /** * 身份證號正則 */ public static final String ID_CARD_NUMBER_REGEX_18 = "^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$"; public static final String ID_CARD_NUMBER_REGEX_15 = "^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{2}$"; /** * 手機號是否合法 * @param phone 要校驗的手機號 * @return true:符合,false:不符合 */ public static boolean isPhoneLegal(String phone){ return match(phone, PHONE_REGEX); } /** * 是否是無效郵箱格式 * @param email 要校驗的郵箱 * @return true:符合,false:不符合 */ public static boolean isEmailLegal(String email){ return match(email, EMAIL_REGEX); } /** * 是否是無效驗證碼格式 * @param code 要校驗的驗證碼 * @return true:符合,false:不符合 */ public static boolean isCodeLegal(String code){ return match(code, VERIFY_CODE_REGEX); } // 校驗是否不符合正則格式 private static boolean match(String str, String regex){ if (str == null || "".equals(str)) { return false; } return str.matches(regex); } /** * 驗證身份證號是否合法 * @param idCard 身份證號 * @return true: 合法; false:不合法 */ public static boolean isIdCardLegal(String idCard) { if (idCard.length() == 18) { return match(idCard, ID_CARD_NUMBER_REGEX_18); } else { return match(idCard, ID_CARD_NUMBER_REGEX_15); } } }
使用案例:
public static void main(String[] args) { String phone = "15039469595"; boolean phoneLegal = VerifyUtils.isPhoneLegal(phone); System.out.println(phoneLegal); }
3. 注解實現(xiàn)參數(shù)校驗
首先導(dǎo)入依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
導(dǎo)入依賴后可以嘗試使用一下它自帶的參數(shù)校驗注解:@NotNull 非空校驗
先來說一下這注解實現(xiàn)參數(shù)校驗的使用步驟。
在平時寫的demo中,本人比較喜歡對接口另外定義vo來接收數(shù)據(jù),例如前端傳的數(shù)據(jù)是user對象里的username和password,我們的user里有很多字段,如果單純使用user就太浪費了,而且如果直接在實體類上進行自定義注解會對實體類造成代碼污染。所以個人認為定義vo類是很有必要的。
以下是我的登錄接口:
@PostMapping("/login") public String login(@RequestBody @Validated LoginVo user) { return "user:" + user.toString(); }
以下是我登錄接口的vo類:
@Data public class LoginVo { // 郵箱 @NotNull(message = "郵箱不能為空") private String email; // 密碼 private String password; }
大家可能注意到我多寫了兩個注解:@Validated、@NotNull(message = “郵箱不能為空”)
對,使用注解進行參數(shù)校驗就分為兩步:
- 在需要進行校驗的字段上加對應(yīng)校驗方式,如@NotNull
- 在需要進行校驗的接口參數(shù)前加@Validated,告訴Spring,這個類你給我看一下,里面有的字段加了校驗注解,符合要求就放行,不符合要求就報錯。
如圖所示:
使用postman發(fā)起請求,故意使得郵箱為空:
會發(fā)現(xiàn)報錯:
Resolved [org.springframework.web.bind.MethodArgumentNotValidException:
Validation failed for argument [0] in public java.lang.String com.example.demo.controller.UserController.login(com.example.demo.domain.vo.LoginVo):
[Field error in object 'loginVo' on field 'email': rejected value [null]; codes [NotNull.loginVo.email,NotNull.email,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [loginVo.email,email]; arguments [];
default message [email]];
default message [郵箱不能為空]] ]
出現(xiàn)這個異常:MethodArgumentNotValidException
,我們就可以在全局異常處理器中捕獲它,返回一個較為規(guī)范的信息。
4. 自定義注解實現(xiàn)參數(shù)校驗
學(xué)習(xí)了如何使用注解進行參數(shù)校驗,我們就可以進行接下來的工作:自定義注解。
由于需求的復(fù)雜,我們現(xiàn)在需要完成注冊接口,注冊時需要身份證號、電話號碼、郵箱、密碼,這些字段的注解校驗Spring并沒有幫我們實現(xiàn),此時就需要DIY注解滿足需求。
如何實現(xiàn)自定義注解?我們先模仿,先來看看@NotNull注解里面有什么:
@Target、@Retention、@Repeatable、@Documented這些常用的注解就不再解釋,
@Constraint:表示此注解是一個參數(shù)校驗的注解,validateBy指定校驗規(guī)則實現(xiàn)類,這里需要填實現(xiàn)類.class。
各個字段的含義:
- message :數(shù)據(jù)不符合校驗規(guī)則后的報錯信息??梢允亲址部梢允俏募?,如果校驗字段較多,建議實現(xiàn)文件形式。
- groups :指定注解使用場景,例如新增、刪除
- payload :往往對Bean使用
以上這三個字段都是必須的,每一個使用ConstraintValidator完成參數(shù)校驗都要有這三個字段。
后面的那個List是NotNull專屬的,所以不必關(guān)心。
那么我們大可以模仿@NotNull來實現(xiàn)自定義注解。
第一步:實現(xiàn)校驗類:
需要實現(xiàn)一個接口:ConstraintValidator<?, ?>
# ConstraintValidator<?, ?>
第一個參數(shù)是自定義注解
第二個參數(shù)是需要進行校驗的數(shù)據(jù)的數(shù)據(jù)類型
例如想對手機號校驗,第一個參數(shù)是Phone,第二個參數(shù)是String
這個接口提供了一個方法:
boolean isValid(T value, ConstraintValidatorContext context);
第一個參數(shù)就是前端傳來的數(shù)據(jù)。我們可以對這個數(shù)據(jù)進行判斷,返回一個布爾值
public class VerifyPhone implements ConstraintValidator<Phone, String> { @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { // 判斷手機號是否合法 return VerifyUtils.isPhoneLegal(s); } }
第二步:實現(xiàn)注解,這個注解的名稱需要與ConstraintValidator的第一個參數(shù)保持一致。
特別注意的是,@Constraint注解里面的validatedBy的值是第一步的Class實例。
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint( validatedBy = {VerifyPhone.class} ) public @interface Phone { boolean isRequired() default false; String message() default "手機號格式錯誤"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
第三步:在字段上加上相應(yīng)注解。
@Data public class RegisterVo { private String name; // 身份證號 private String id; // 電話號碼 @Phone private String phone; // 郵箱 private String email; // 密碼 private String password; }
第四步:在參數(shù)前加上@Validated
。
@PutMapping("/register") public String register(@RequestBody @Validated RegisterVo user) { return "user: " + user.toString(); }
這樣一來,就優(yōu)雅的實現(xiàn)了參數(shù)校驗。別以為我們搞這么多類很麻煩,除非你想每一個controller里都這樣寫:
@PutMapping("/register") public String register(@RequestBody @Validated RegisterVo user) { if (VerifyUtils.isPhoneLegal("xxx")) { return "手機號格式錯誤"; } if (VerifyUtils.isCodeLegal("xxx")) { return "驗證碼格式錯誤"; } if (VerifyUtils.isIdCardLegal("xxx")) { return "身份證格式錯誤"; } if (VerifyUtils.isEmailLegal("xxx")) { return "郵箱格式錯誤"; } return "user: " + user.toString(); }
真的很low很麻煩好嗎。
可能步驟有點繁瑣,不過也就4步,畫張圖加強一下記憶:
以上就是SpringBoot通過自定義注解實現(xiàn)參數(shù)校驗的詳細內(nèi)容,更多關(guān)于SpringBoot參數(shù)校驗的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java.nio.file.WatchService?實時監(jiān)控文件變化的示例代碼
在?Java?語言中,從?JDK7?開始,新增了java.nio.file.WatchService類,用來實時監(jiān)控文件的變化,這篇文章主要介紹了java.nio.file.WatchService?實時監(jiān)控文件變化,需要的朋友可以參考下2022-05-05springboot vue組件開發(fā)實現(xiàn)接口斷言功能
這篇文章主要為大家介紹了springboot+vue組件開發(fā)實現(xiàn)接口斷言功能,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-05-05java從mysql導(dǎo)出數(shù)據(jù)的具體實例
這篇文章主要介紹了java從mysql導(dǎo)出數(shù)據(jù)的具體實例,有需要的朋友可以參考一下2013-12-12