javascript
SpringBoot - 优雅的实现【业务校验】高级进阶
文章目錄
- Pre
- 需求
- 實現三部曲
- 實體類
- Step1 搞兩個自定義注解
- Step2 搞自定義校驗器
- Step3 搞驗證
- 小結
- 源碼
Pre
SpringBoot - 優雅的實現【參數校驗】高級進階
SpringBoot - 優雅的實現【自定義參數校驗】高級進階
SpringBoot - 優雅的實現【參數分組校驗】高級進階
SpringBoot - 使用Assert校驗讓業務代碼更簡潔
在開發中,為了保證接口的穩定安全,一般需要在接口邏輯中進行校驗,比如 上面幾篇都是 【參數校驗】,一般我們都是使用Bean Validation校驗框架。
| @Null | 限制只能為null |
| @NotNull | 限制必須不為null |
| @AssertFalse | 限制必須為false |
| @AssertTrue | 限制必須為true |
| @DecimalMax(value) | 限制必須為一個不大于指定值的數字 |
| @DecimalMin(value) | 限制必須為一個不小于指定值的數字 |
| @Digits(integer,fraction) | 限制必須為一個小數,且整數部分的位數不能超過integer,小數部分的位數不能超過fraction |
| @Future | 限制必須是一個將來的日期 |
| @Max(value) | 限制必須為一個不大于指定值的數字 |
| @Min(value) | 限制必須為一個不小于指定值的數字 |
| @Past | 驗證注解的元素值(日期類型)比當前時間早 |
| @Pattern(value) | 限制必須符合指定的正則表達式 |
| @Size(max,min) | 限制字符長度必須在min到max之間 |
| @NotEmpty | 驗證注解的元素值不為null且不為空(字符串長度不為0、集合大小不為0) |
| @NotBlank | 驗證注解的元素值不為空(不為null、去除首位空格后長度為0),不同于@NotEmpty,@NotBlank只應用于字符串且在比較時會去除字符串的空格 |
| 驗證注解的元素值是Email,也可以通過正則表達式和flag指定自定義的email格式 |
那【業務規則校驗】大部分情況下為了簡單都是 if else ,那怎么玩的更優雅一些呢?
Tips: 參考 Bean Validation 的標準方式,借助自定義校驗注解進行業務規則校驗
需求
- 新增用戶 , 用戶名+手機號碼+郵箱 唯一
- 修改用戶, 修改后的 【用戶名+手機號碼+郵箱】不能與庫中的用戶信息沖突
實現三部曲
當然了, 簡單的寫就是整個if else return 嘛 查查DB 搞個判斷 。 今天晚點看起來有點不一樣的
實體類
package com.artisan.bean;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length;import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/@Data @AllArgsConstructor @NoArgsConstructor public class Artisan {private String id;@NotEmpty(message = "Code不能為空")private String code;@NotBlank(message = "名字為必填項")private String name;@Length(min = 8, max = 12, message = "password長度必須位于8到12之間")private String password;@Email(message = "請填寫正確的郵箱地址")private String email;private String sex;private String phone;}Step1 搞兩個自定義注解
創建兩個自定義注解,用于業務規則校驗
package com.artisan.annos;import com.artisan.validate.ArtisanValidator;import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*;/**** 自定義 "用戶唯一" 校驗注解 .唯一包含 -----------> 用戶名+手機號碼+郵箱* @author artisan*/ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE}) @Constraint(validatedBy = ArtisanValidator.UniqueArtisanValidator.class) public @interface UniqueArtisan {String message() default "用戶名、手機號碼、郵箱不允許與現存用戶重復";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {}; } package com.artisan.annos;import com.artisan.validate.ArtisanValidator;import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target;import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME;/*** 表示一個用戶的信息是無沖突的* “無沖突”是指該用戶的敏感信息與其他用戶不重合,比如將一個注冊用戶的郵箱、手機,修改成與另外一個已存在的注冊用戶一致的值,這樣不行* @author artisan*/ @Documented @Retention(RUNTIME) @Target({FIELD, METHOD, PARAMETER, TYPE}) @Constraint(validatedBy = ArtisanValidator.NotConflictArtisanValidator.class) public @interface NotConflictArtisan {String message() default "用戶名稱、郵箱、手機號碼與現存用戶產生重復";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {}; }Step2 搞自定義校驗器
package com.artisan.validate;import com.artisan.annos.NotConflictArtisan; import com.artisan.annos.UniqueArtisan; import com.artisan.bean.Artisan; import com.artisan.repository.ArtisanDao; import lombok.extern.slf4j.Slf4j;import javax.annotation.Resource; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.function.Predicate;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/@Slf4j public class ArtisanValidator<T extends Annotation> implements ConstraintValidator<T, Artisan> {protected Predicate<Artisan> predicate = c -> true;@Resourceprotected ArtisanDao artisanDao;@Overridepublic boolean isValid(Artisan artisan, ConstraintValidatorContext constraintValidatorContext) {return artisanDao == null || predicate.test(artisan);}/*** 校驗用戶是否唯一* 即判斷數據庫是否存在當前新用戶的信息,如用戶名,手機,郵箱*/public static class UniqueArtisanValidator extends ArtisanValidator<UniqueArtisan> {@Overridepublic void initialize(UniqueArtisan uniqueArtisan) {predicate = c -> !artisanDao.existsByNameOrEmailOrPhone(c.getName(), c.getEmail(), c.getPhone());}}/*** 校驗是否與其他用戶沖突* 將用戶名、郵件、電話改成與現有完全不重復的,或者只與自己重復的,就不算沖突*/public static class NotConflictArtisanValidator extends ArtisanValidator<NotConflictArtisan> {@Overridepublic void initialize(NotConflictArtisan notConflictUser) {predicate = c -> {log.info("user detail is {}", c);Collection<Artisan> collection = artisanDao.findByNameOrEmailOrPhone(c.getName(), c.getEmail(), c.getPhone());// 將用戶名、郵件、電話改成與現有完全不重復的,或者只與自己重復的,就不算沖突return collection.isEmpty() || (collection.size() == 1 && collection.iterator().next().getId().equals(c.getId()));};}} }自定義驗證注解需要實現 ConstraintValidator 接口。
- 第一個參數是 自定義注解類型
- 第二個參數是 被注解字段的類
因為需要校驗多個參數, 直接傳入用戶對象。
需要提到的一點是 ConstraintValidator 接口的實現類無需添加 @Component 它在啟動的時候就已經被加載到容器中了。
使用Predicate函數式接口對業務規則進行判斷.
Step3 搞驗證
package com.artisan.controller;import com.artisan.annos.NotConflictArtisan; import com.artisan.annos.UniqueArtisan; import com.artisan.bean.Artisan; import com.artisan.repository.ArtisanDao; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*;import javax.validation.Valid;/****/ @RestController @RequestMapping("/buziVa/artisan") @Slf4j @Validated public class ArtisanController {@Autowiredprivate ArtisanDao artisanDao;// POST 方法@PostMappingpublic Artisan createUser(@UniqueArtisan @Valid Artisan user) {Artisan savedUser = artisanDao.save(user);log.info("save user id is {}", savedUser.getId());return savedUser;}// PUT@SneakyThrows@PutMappingpublic Artisan updateUser(@NotConflictArtisan @Valid @RequestBody Artisan artisan) {Artisan editUser = artisanDao.save(artisan);log.info("update artisan is {}", editUser);return editUser;}}只需要在方法上加入自定義注解即可,業務邏輯中不需要添加任何業務規則的代碼。
小結
通過上面幾步操作,業務校驗便和業務邏輯就完全分離開來,在需要校驗時用@Validated注解自動觸發,或者通過代碼手動觸發執行。
這些注解應用于控制器、服務層、持久層等任何層次的代碼之中。
在開發時可以將不帶業務含義的格式校驗注解放到 Bean 的類定義之上,將帶業務邏輯的校驗放到 Bean 的類定義的外面。
區別是放在類定義中的注解能夠自動運行,而放到類外面則需要明確標出@Validated注解時才會運行。
源碼
https://github.com/yangshangwei/boot2
總結
以上是生活随笔為你收集整理的SpringBoot - 优雅的实现【业务校验】高级进阶的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringBoot - 优雅的实现【参
- 下一篇: SpringBoot - 优雅的实现【流