嚴謹的接口是必須要有參數校驗的,迴避掉一些不合理的請求。但是校驗邏輯和正常業務邏輯摻雜在一起,固然能完成校驗需求,但是在實現方式上卻不那麼優雅。比如現在有一個user註冊接口:
1.直男癌一般的校驗
@PostMapping("/user")
public Mono<String> insert(@RequestBody User user) {
if(StringUtils.isEmpty(user.getName())) {
return Mono.just("姓名不能爲空");
}
if(StringUtils.isEmpty(user.getEnname())) {
return Mono.just("英文姓名不能爲空");
}
// 處理業務邏輯
return Mono.just("註冊成功");
}
這種最爲無腦直接,也確實能完成校驗需求,但是卻有點憨憨,當一個對象中有很多字段需要校驗的時候,很多if - else就顯得很臃腫。我剛寫代碼的時候就經常這樣幹。。。。
2.藉助spring的@validated註解來校驗
@Data
public class User {
private int id;
@NotNull(message = "姓名不可爲空")
private String name;
@NotNull(message = "英文姓名不可爲空")
private String enname;
private String mobile;
}
@PostMapping("/user")
public Mono<String> insert(@RequestBody @Validated User user) {
// 處理業務邏輯
return Mono.just("註冊成功");
}
藉助了spring的力量之後,原先臃腫的if - else沒有了,代碼瞬間變的簡潔了起來。但是這樣子也是有一些問題的,比如我們現在要在Controller中新增添一個修改user的接口,既然是修改接口,我們可以選擇只修改user的name或者只修改user的enname。那麼就會出現一個問題,新增接口的校驗和修改接口的校驗衝突了。新增接口要求:name和enname都不能爲空,修改接口要求:name和enname可以爲空,這個時候用一個user對象就有點尷尬。
3.@validated 分組校驗
@Data
public class User {
@Min(value = 1, message = "ID不可爲空", groups = {Update.class})
private int id;
@NotNull(message = "姓名不可爲空", groups = {Insert.class})
private String name;
@NotNull(message = "英文姓名不可爲空", groups = {Insert.class})
private String enname;
private String mobile;
}
@PostMapping("/user")
public Mono<String> insert(@RequestBody @Validated(Insert.class) User user) {
// 處理新增邏輯
return Mono.just("註冊成功");
}
@PutMapping("/user")
public Mono<String> update(@RequestBody @Validated(Update.class) User user) {
// 處理修改邏輯
return Mono.just("修改成功");
}
// 隨便自定義2個接口 用於標識新增和修改
public interface Insert{
}
public interface Update{
}
通過@Validated的分組校驗,可以指定在什麼時候才驗證字段的合法性。一切看起來都是這麼的完美,spring爲我們整理了一些內置的驗證註解,部分如下:
@Null 被註釋的元素必須爲null
@NotNull 被註釋的元素不能爲null
@AssertTrue 被註釋的元素必須爲true
@AssertFalse 被註釋的元素必須爲false
@Min(value) 被註釋的元素必須是一個數字,其值必須大於等於指定的最小值
@Max(value) 被註釋的元素必須是一個數字,其值必須小於等於指定的最大值
@DecimalMin(value) 被註釋的元素必須是一個數字,其值必須大於等於指定的最小值
@DecimalMax(value) 被註釋的元素必須是一個數字,其值必須小於等於指定的最大值
@Size(max,min) 被註釋的元素的大小必須在指定的範圍內。
@Digits(integer,fraction) 被註釋的元素必須是一個數字,其值必須在可接受的範圍內
@Past 被註釋的元素必須是一個過去的日期
@Future 被註釋的元素必須是一個將來的日期
@Pattern(value) 被註釋的元素必須符合指定的正則表達式。
@Email 被註釋的元素必須是電子郵件地址
@Length 被註釋的字符串的大小必須在指定的範圍內
@NotEmpty 被註釋的字符串必須非空
@Range 被註釋的元素必須在合適的範圍內
這些自帶的驗證註解包括驗證長度,驗證是否爲true,驗證是否爲郵箱,驗證最大值等等。但是現在我們的user對象又有一個新的需求,添加電話號碼字段,但是spring並沒有一個自帶的註解是可以驗證一個字符串是否是電話號碼的。
4.自定義驗證註解
第一步,新增一個註解類:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR,
ElementType.PARAMETER })
//約束註解應用的目標元素類型(METHOD, FIELD, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER等)
@Retention(RetentionPolicy.RUNTIME) // 約束註解應用的時機
@Documented
@Constraint(validatedBy = MobileValidator.class)
public @interface IsMobile {
boolean required() default true;
String message() default "手機號格式不正確";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
第二步,編寫校驗類,實現ConstraintValidator<A extends Annotation, T>接口。ConstraintValidator<A extends Annotation, T>是個泛型接口,第一個泛型是註解類,第二個泛型是需要驗證的數據類型。接口有2個方法,一個initialize負責初始化,另外一個isValid負責具體的校驗邏輯。
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MobileValidator implements ConstraintValidator<IsMobile, String>{
@Override
public void initialize(IsMobile constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 關於手機號的驗證偷個懶 不爲空且長度不是11位的字符串 就被認爲是非法
if(value != null && value.length() != 11) {
System.out.println("手機號非法");
return false;
}
return true;
}
}
第三步, 使用自定義註解:
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import com.arvato.ann.Insert;
import com.arvato.ann.IsMobile;
import com.arvato.ann.Update;
import lombok.Data;
@Data
public class User {
@Min(value = 1, message = "ID不可爲空", groups = {Update.class})
private int id;
@NotNull(message = "姓名不可爲空", groups = {Insert.class})
private String name;
@NotNull(message = "英文姓名不可爲空", groups = {Insert.class})
private String enname;
@IsMobile(message = "手機號格式非法", groups = {Update.class, Insert.class})
private String mobile;
}
至此,項目中大部分的參數驗證都能通過註解來完成了,但是也不排除有一些業務場景是校驗註解無法完成的,有時候還是需要一些直男癌的if-else的。