前言
? ? ? ? 在之前的悅享校園的開發中使用了SSM框架,由于當時并沒有使用參數參數校驗工具,方法的入參判斷使用了大量的if else語句,代碼十分臃腫,因此最近在重構代碼時,將框架改為SpringBoot后,引入了Hibernate Validator校驗工具對參數進行優雅校驗(SSM同樣可用)。本文將通過實例來演示如何使用該框架。
環境配置
JDK 1.8
Spring Boot 2.7.12
導入依賴
<!--SpringBoot 2.3開始,校驗包單獨組件,需要手動引入 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
基礎校驗
在正式開始之前,需要先對以下的內容作以了解,方便后續的學習。
校驗注解
Hibernate Validator,提供了豐富的校驗注解來供我們使用,這里列舉一些比較常用的:
注解 | 解釋 |
@Null | 驗證對象是否為空 |
@NotNull | 驗證對象是否為非空 |
@NotBlank | 用于String對象,驗證字符串不為空且trim()后長度大于0 |
@Length | 驗證對象的長度是否符合 |
@Size | 驗證對象長度是否在給定的范圍之內(用于Array,Collection,Map,String) |
@Min | 入參對象的值不能小于該值(用于Number和String) |
@Max | 入參對象的值不能大于該值(用于Number和String) |
@Digits | 驗證number和string的構成是否合法 |
@Past | 驗證date和calendar對象是否在當前時間之前 |
@Future | 驗證date和calendar對象是否在當前時間之后 |
@Pattern | 驗證是否符合正則表達式規則 |
驗證郵箱 | |
@Positive | 驗證輸入的對象是否非負數 |
@Range | 驗證輸入的對象的值是否在指定范圍內 |
參數綁定
@PathVariable
該注解用于接收具有Restful風格的參數,如/api/v1/1001,最終userId的值為1001。
如下代碼中,使用name屬性可以指定GetMapping中的id名稱與之對應,從而可以自定義參數名稱userId,而不是使用默認名稱id
@GetMapping("/v1/{id}")
public void getMsg(@PathVariable(name = "id") Integer userId){}
@RequestParam
該注解用于接收查詢參數,如/api/v1/product?user="123",則user的值為123。該注解也可用于接收form-data類型的數據。
當在參數前使用@RequireParam時,當請求該方法時,對應的參數必須存在,否則會引發異常,可使用@RequireParam(required = false)指明該參數非必須,該注解在入參為null時可提供默認值。
@GetMapping("/v1/product")
public void getMsg(@RequestParam String user){}
@RequestBody
該注解用于接收JSON格式的數據,如請求為{"name":123,"age":18},需要有對應的實體類作為映射。
@PostMapping("/v1/user")
public void getMsg(@RequestBody User user){}
@Validated
該注解可以作用于類、方法、參數上,用于開啟參數校驗。
@Valid
該注解可以作用于方法、參數、字段、構造器上,同樣可以用于參數校驗。
單參校驗
將@Validated注解用于類上,可以不用在每個方法的參數上重復開啟校驗,使用前面提的基礎校驗即可對參數作約束。?
校驗方法
@RestController
@RequestMapping("/v1/hello")
@Slf4j
@Validated
public class HelloController {/*** GetMapping 請求* @param name 用戶名稱* @param uid 用戶uid* @return*/@GetMapping("/{id}")public ResultDataVO getMsg(@RequestParam String name,@Max(value = 10,message = "最大值不能超過10")@PathVariable(name = "id") int uid) {log.info("訪問hello下的msg方法。");String result = "Hello,"+name+" id "+uid;return ResultDataVO.success(result);}
}
請求參數????????響應結果
?由于我們沒有給String參數賦值,因此默認為空字符串。但由于在uid上使用了@Max注解,因此當入參為100時便會出現異常,而返回的JSON格式為全局的響應處理,在后續的文章會提到。
對象校驗
當我們在使用中,往往會遇到多個參數的情況,由于get請求對參數長度有限制,且參數會拼接在URL中, 因此對于此場景我們一般使用對象的方式來接收參數,而對于我們的參數也將傳入也將用JSON格式。
由于使用JSON格式傳參,將使用@RequestBody注解,除此之外還要使用@Validated或@Valid注解,關于兩者的選擇,只需要記住分組校驗使用@Validated,否則二者均可。
即使在類上添加了@Validated注解,此處仍然需要以上兩個注解之一,否則參數校驗將無效。
校驗方法
@RestController
@RequestMapping("/v1/hello")
@Slf4j
@Validated
public class HelloController {/*** 對象校驗示例* @param user* @return */@PostMapping("/msg")public ResultDataVO(@RequestBody @Valid UserDTO user){return ResultDataVO.success(user.toString());}
}
UserDTO
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDTO {/*** id*/@Min(value = 1,message = "id值不合法")@Positiveprivate Integer id;/*** 昵稱*/@NotBlank(message = "用戶名不能為空")@Length(min = 4,max=10,message = "用戶名必須在4-10個字符之間")private String username;/*** 年齡*/@Range(min = 1,max=120,message = "年齡不在合法范圍內")@NotNull(message = "age不能為空")private Integer age;
}
上述代碼中,參數為UserDTO對象,其中包含三個待校驗的字段,由于未涉及到分組校驗,因此校驗方法中參數前使用@Valid注解(@Validated注解亦可),注意入參的字段名稱需要和UserDTO中的名稱一致。
請求參數
?響應結果
分組校驗
假設有這樣的場景,在用戶登錄和修改密碼中,兩者分別需要如下參數,登錄時需要用戶名、密碼,而修改時需要密碼、新密碼,為了方便管理,只使用了一個DTO類來存放上述的字段,此時由于這些參數需要在不同的場景下校驗,而加上基礎校驗的注解會導致所有參數均被校驗,面對此問題,我們將使用分組校驗來解決。
可以看到如何代碼中,均是使用了@Validate注解,且注解中含有一個分組接口,使用該接口可以幫助我們告知程序該對那些參數進行校驗。
校驗方法
@RestController
@Slf4j
@RequestMapping("/local/auth")
@Validated
public class LocalAuthController {@Autowiredprivate LocalAuthService localAuthService;/*** 用戶登錄接口* @param localAuthDTO 賬號對象* @return*/@PostMapping("/login")LocalAuthVO loginCheck(@RequestBody @Validated(AuthCheckSequence.class) LocalAuthDTO localAuthDTO){return localAuthService.userLoginCheck(localAuthDTO);}/*** 用戶修改密碼接口* @param localAuthDTO 賬號對象* @return*/@PutMapping("/password")ResultDataVO userChangePassword(@RequestBody@Validated(AuthChangeSequence.class)LocalAuthDTO localAuthDTO){return localAuthService.userModifyPassword(localAuthDTO);}
}
分組接口
AuthChangeSequence?
/*** 分組校驗,用戶登錄*/public interface AuthCheckSequence {}
AuthCheckSequence?
/*** 分組校驗,密碼修改*/
public interface AuthChangeSequence {}
LocalAuthDTO?
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LocalAuthDTO {/*** 用戶名*/@NotBlank(message = "用戶名不能為空",groups = AuthCheckSequence.class)@Pattern(regexp = "^[a-zA-Z0-9]{3,8}$", message = "用戶名必須由3-8個字母或數字組成",groups = AuthCheckSequence.UsernameCheck.class)private String username;/*** 密碼*/@NotBlank(message = "密碼不能為空",groups = AuthCheckSequence.class)@Size(min = 6,max = 18, message = "密碼長度必須在6到18位之間",groups = AuthCheckSequence.PasswordCheck.class)private String password;/*** 新密碼,修改密碼時使用*/@NotBlank(message = "新密碼不能為空",groups = AuthChangeSequence.class)@Size(min = 6,max = 18, message = "新密碼長度必須在6到18位之間",groups = AuthChangeSequence.ChangePassword.class)private String newPassword;
}
請求參數
響應結果
從上圖所示請求結果可以看出,使用@Validated注解+分組校驗接口后,將只對該參數中標明了對應分組接口的字段進行校驗,并不會對全部參數進行校驗。????????
順序校驗
由于用戶人數增多,我們將在對用戶進行分組,將在LocalAuthDTO中引入新的字段,而我們所期望的是能夠按照用戶類型,用戶名,密碼的順序來校驗,而不是像之前對象校驗最終顯示的結果那樣,隨機進行校驗并返回結果,因此需要使用在分組校驗的基礎上做一些改進,使其可以按照我們所指定的順序執行校驗,以前面登錄為例:
校驗方法
@RestController
@Slf4j
@RequestMapping("/local/auth")
@Validated
public class LocalAuthController {@Autowiredprivate LocalAuthService localAuthService;/*** 用戶登錄接口* @param localAuthDTO 賬號對象* @return*/@PostMapping("/login")LocalAuthVO loginCheck(@RequestBody @Validated(AuthCheckSequence.class) LocalAuthDTO localAuthDTO){return localAuthService.userLoginCheck(localAuthDTO);}
}
?AuthCheckSequence
通過在該分組接口中新建接口,并使用@GroupSequence注解中參數的順序來實現校驗參數的順序。如下將標識使用AuthCheckSequence分組的參數將按照用戶類型、用戶名???????、密碼的順序進行校驗。
@GroupSequence({AuthCheckSequence.UserTypeCheck.class, AuthCheckSequence.UsernameCheck.class, AuthCheckSequence.PasswordCheck.class})
public interface AuthCheckSequence {/*** 用戶類型*/interface UserTypeCheck {};/*** 用戶名校驗*/interface UsernameCheck {};/*** 密碼校驗*/interface PasswordCheck {};
}
LocalAuthDTO
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LocalAuthDTO {/*** 用戶類型 1.用戶 2.商家 3.管理員,目前僅支持用戶和商家類型注冊*/@NotNull(message = "用戶類型不能為空",groups = AuthCheckSequence.UserTypeCheck.class)@Range(min = 1,max = 3,message = "用戶類型不合法",groups = AuthCheckSequence.UserTypeCheck.class)private Integer userType;/*** 用戶名*/@NotBlank(message = "用戶名不能為空",groups = AuthCheckSequence.UsernameCheck.class)@Pattern(regexp = "^[a-zA-Z0-9]{3,8}$", message = "用戶名必須由3-8個字母或數字組成",groups = AuthCheckSequence.UsernameCheck.class)private String username;/*** 密碼*/@NotBlank(message = "密碼不能為空",groups = AuthCheckSequence.PasswordCheck.class)@Size(min = 6,max = 18, message = "密碼長度必須在6到18位之間",groups = AuthCheckSequence.PasswordCheck.class)private String password;
}
請求參數
?響應結果
?