3. 分組校驗(yàn)
一個(gè)VO對(duì)象在新增的時(shí)候某些字段為必填,在更新的時(shí)候又非必填。如上面的ValidVO中 id`` 和 appId 屬性在新增操作時(shí)都是 非必填 ,而在編輯操作時(shí)都為 必填 ,name在新增操作時(shí)為 必填 ,面對(duì)這種場(chǎng)景你會(huì)怎么處理呢? 在實(shí)際開發(fā)中我見到很多同學(xué)都是建立兩個(gè)VO對(duì)象,ValidCreateVO,ValidEditVO來處理這種場(chǎng)景,這樣確實(shí)也能實(shí)現(xiàn)效果,但是會(huì)造成類膨脹。
其實(shí)
Validator校驗(yàn)框架已經(jīng)考慮到了這種場(chǎng)景并且提供了解決方案,就是 分組校驗(yàn) ,只不過很多同學(xué)不知道而已。
要使用分組校驗(yàn),只需要三個(gè)步驟。
3.1. 第一步,定義分組接口
public interface ValidGroup extends Default {
interface Crud extends ValidGroup{
interface Create extends Crud{
}
interface Update extends Crud{
}
interface Query extends Crud{
}
interface Delete extends Crud{
}
}
}
這里我們定義一個(gè)分組接口ValidGroup讓其繼承javax.validation.groups.Default,再在分組接口中定義出多個(gè)不同的操作類型,Create,Update,Query,Delete。
3.2. 第二步,在模型中給參數(shù)分配分組
@Data
public class ValidVO {
@Null(groups = ValidGroup.Crud.Create.class)
@NotNull(groups = ValidGroup.Crud.Update.class, message = "應(yīng)用ID不能為空")
private String id;
@Length(min = 6,max = 12,message = "appId長度必須位于6到12之間")
@Null(groups = ValidGroup.Crud.Create.class)
@NotNull(groups = ValidGroup.Crud.Update.class, message = "應(yīng)用ID不能為空")
private String appId;
@NotBlank(message = "名字為必填項(xiàng)")
@NotBlank(groups = ValidGroup.Crud.Create.class,message = "名字為必填項(xiàng)")
private String name;
@Email(message = "請(qǐng)?zhí)顚懻_的郵箱地址")
private String email;
@EnumString(value = {"F","M"}, message="性別只允許為F或M")
private String sex;
@NotEmpty(message = "級(jí)別不能為空")
private String level;
}
給參數(shù)指定分組,對(duì)于未指定分組的則使用的是默認(rèn)分組。
3.3. 第三步,給需要參數(shù)校驗(yàn)的方法指定分組
@PostMapping(value = "/valid/add")
public String add(@Validated(value = ValidGroup.Crud.Create.class) ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test3 valid success";
}
@PostMapping(value = "/valid/update")
public String update(@Validated(value = ValidGroup.Crud.Update.class) ValidVO validVO){
log.info("validEntity is {}", validVO);
return "test4 valid success";
}
這里我們通過value屬性給add()和update()方法分別指定Create和Update分組
3.4. 測(cè)試
POST http://localhost:8080/valid/add
Content-Type: application/x-www-form-urlencoded
name=javadaily&level=12&email=476938977@qq.com&sex=F
- Create操作
在Create時(shí)我們 沒有傳遞id和appId參數(shù) ,校驗(yàn)通過。
{
"status": 100,
"message": "操作成功",
"data": "test3 valid success",
"timestamp": 1652186105359
}
- update操作
使用同樣的參數(shù)調(diào)用update方法時(shí)則提示參數(shù)校驗(yàn)錯(cuò)誤
{
"status": 400,
"message": "ID不能為空; 應(yīng)用ID不能為空",
"data": null,
"timestamp": 1652186962377
}
復(fù)制代碼
- 默認(rèn)校驗(yàn)生效操作
由于email屬于默認(rèn)分組,而我們的分組接口ValidGroup已經(jīng)繼承了Default分組,所以也是可以對(duì)email字段作參數(shù)校驗(yàn)的。故意寫錯(cuò)email格式
POST http://localhost:8080/valid/add
Content-Type: application/x-www-form-urlencoded
/valid/update?name=javadaily&level=12&email=476938977&sex=F
{
"status": 400,
"message": "請(qǐng)?zhí)顚懻_的郵箱地址; ID不能為空; 應(yīng)用ID不能為空",
"data": null,
"timestamp": 1652187273865
}
4. 業(yè)務(wù)規(guī)則校驗(yàn)
業(yè)務(wù)規(guī)則校驗(yàn)指接口需要滿足某些特定的業(yè)務(wù)規(guī)則,舉個(gè)例子:業(yè)務(wù)系統(tǒng)的用戶需要保證其唯一性,用戶屬性不能與其他用戶產(chǎn)生沖突,不允許與數(shù)據(jù)庫中任何已有用戶的用戶名稱、手機(jī)號(hào)碼、郵箱產(chǎn)生重復(fù)。 這就要求在 創(chuàng)建用戶時(shí)需要校驗(yàn)用戶名稱、手機(jī)號(hào)碼、郵箱是否被注冊(cè) ; 編輯用戶時(shí)不能將信息修改成已有用戶的屬性 。最優(yōu)雅的實(shí)現(xiàn)方法應(yīng)該是參考 **Bean Validation** 的標(biāo)準(zhǔn)方式,借助自定義校驗(yàn)注解完成業(yè)務(wù)規(guī)則校驗(yàn)。
4.1. 自定義注解
首先我們需要?jiǎng)?chuàng)建兩個(gè)自定義注解,用于業(yè)務(wù)規(guī)則校驗(yàn):
UniqueUser:表示一個(gè)用戶是唯一的,唯一性包含:用戶名,手機(jī)號(hào)碼、郵箱
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidation.UniqueUserValidator.class)
public @interface UniqueUser {
String message() default "用戶名、手機(jī)號(hào)碼、郵箱不允許與現(xiàn)存用戶重復(fù)";
Class?[] groups() default {};
Class? extends Payload[] payload() default {};
}
NotConflictUser:表示一個(gè)用戶的信息是無沖突的,無沖突是指該用戶的敏感信息與其他用戶不重合
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidation.NotConflictUserValidator.class)
public @interface NotConflictUser {
String message() default "用戶名稱、郵箱、手機(jī)號(hào)碼與現(xiàn)存用戶產(chǎn)生重復(fù)";
Class?[] groups() default {};
Class? extends Payload[] payload() default {};
}
4.2. 實(shí)現(xiàn)業(yè)務(wù)校驗(yàn)規(guī)則
想讓自定義驗(yàn)證注解生效,需要實(shí)現(xiàn) ConstraintValidator 接口。接口的第一個(gè)參數(shù)是 自定義注解類型 ,第二個(gè)參數(shù)是 被注解字段的類 ,因?yàn)樾枰r?yàn)多個(gè)參數(shù),我們直接傳入用戶對(duì)象。 需要提到的一點(diǎn)是 ConstraintValidator 接口的實(shí)現(xiàn)類無需添加 @Component 它在啟動(dòng)的時(shí)候就已經(jīng)被加載到容器中了。
@Slf4j
public class UserValidation<T extends Annotation> implements ConstraintValidator<T, User> {
protected Predicate
這里使用Predicate函數(shù)式接口對(duì)業(yè)務(wù)規(guī)則進(jìn)行判斷。
4.3. 測(cè)試代碼
@RestController
@RequestMapping("/senior/user")
@Slf4j
@Validated
public class UserController {
@Autowired
private UserRepository userRepository;
@PostMapping
public User createUser(@UniqueUser @Valid User user){
User savedUser = userRepository.save(user);
log.info("save user id is {}",savedUser.getId());
return savedUser;
}
@SneakyThrows
@PutMapping
public User updateUser(@NotConflictUser @Valid @RequestBody User user){
User editUser = userRepository.save(user);
log.info("update user is {}",editUser);
return editUser;
}
}
使用很簡單,只需要在方法上加入自定義注解即可,業(yè)務(wù)邏輯中不需要添加任何業(yè)務(wù)規(guī)則的代碼。
POST http://localhost:8080/valid/add
Content-Type: application/json
/senior/user
{
"userName" : "100001"
}
{
"status": 400,
"message": "用戶名、手機(jī)號(hào)碼、郵箱不允許與現(xiàn)存用戶重復(fù)",
"data": null,
"timestamp": 1652196524725
}
5. 總結(jié)
通過上面幾步操作,業(yè)務(wù)校驗(yàn)便和業(yè)務(wù)邏輯就完全分離開來,在需要校驗(yàn)時(shí)用@Validated注解自動(dòng)觸發(fā),或者通過代碼手動(dòng)觸發(fā)執(zhí)行,可根據(jù)你們項(xiàng)目的要求,將這些注解應(yīng)用于控制器、服務(wù)層、持久層等任何層次的代碼之中。 這種方式比任何業(yè)務(wù)規(guī)則校驗(yàn)的方法都優(yōu)雅,推薦大家在項(xiàng)目中使用。在開發(fā)時(shí)可以將不帶業(yè)務(wù)含義的格式校驗(yàn)注解放到 Bean 的類定義之上,將帶業(yè)務(wù)邏輯的校驗(yàn)放到 Bean 的類定義的外面。這兩者的區(qū)別是放在類定義中的注解能夠自動(dòng)運(yùn)行,而放到類外面則需要像前面代碼那樣,明確標(biāo)出注解時(shí)才會(huì)運(yùn)行。
-
接口
+關(guān)注
關(guān)注
33文章
9520瀏覽量
157027 -
代碼
+關(guān)注
關(guān)注
30文章
4968瀏覽量
73965 -
Validator驗(yàn)
+關(guān)注
關(guān)注
0文章
3瀏覽量
5904 -
SpringBoot
+關(guān)注
關(guān)注
0文章
177瀏覽量
688
發(fā)布評(píng)論請(qǐng)先 登錄
SpringBoot 如何實(shí)現(xiàn)熱部署
請(qǐng)問TIDA-00554的光譜模組在安裝和調(diào)試階段光機(jī)是如何進(jìn)行校驗(yàn)的呢?
變頻器的參數(shù)如何進(jìn)行設(shè)置呢
SpringBoot應(yīng)用啟動(dòng)運(yùn)行run方法
Springboot是如何獲取自定義異常并進(jìn)行返回的
如何進(jìn)行OPCDCOM配置
增強(qiáng)FIFO模式下的奇偶校驗(yàn)
如何用責(zé)任鏈默認(rèn)優(yōu)雅地進(jìn)行參數(shù)校驗(yàn)
什么是 SpringBoot?
SpringBoot的核心注解1
SpringBoot的核心注解2
SpringBoot Web應(yīng)用如何進(jìn)行參數(shù)校驗(yàn)?(下)
評(píng)論