學習Spring Boot:(十)使用hibernate validation完成數據後端校驗

前言

後臺數據的校驗也是開發中比較注重的一點,用來校驗數據的正確性,以避免一些非法的數據破壞系統,或者進入數據庫,形成數據污染,因爲數據檢驗可能應用到不少層面,因此係統對數據校驗要求比較嚴格且追求可變性及效率。css

瞭解

瞭解一點概念性的東東。
* JSR 303 是 Java 爲 Bean 數據合法性校驗提供的標準框架,它已經包含在 JavaEE 6.0 中 。
* Hibernate Validator 是 JSR 303 的一個參考實現,因此它多實現了幾個校驗規則。
* Spring 4.0 擁有本身獨立的數據校驗框架,同時支持 JSR303 標準的校驗框架。
* 在已經標註了 JSR303 註解的表單/命令對象前標註一個@Valid,Spring MVC 框架在將請求參數綁定到該入參對象後,就會調用校驗框架根據註解聲明的校驗規則實施校驗
* Spring MVC 是經過對處理方法簽名的規約來保存校驗結果的:前一個表單/命令對象的校驗結果保存到隨後的入參中,這個保存校驗結果的入參必須是 BindingResult 或Errors 類型,這兩個類都位於org.springframework.validation 包中。
* 需校驗的 Bean 對象和其綁定結果對象或錯誤對象時成對出現的,它們之間不容許聲明其餘的入參
* Errors 接口提供了獲取錯誤信息的方法,如 getErrorCount() 或getFieldErrors(String field)
* BindingResult 擴展了 Errors 接口。前端

支持的註解

JSR 提供的校驗註解:java

@Null   被的註解元素必須爲 null    
@NotNull    被註解的元素必須不爲 null    
@AssertTrue     被註解的元素必須爲 true    
@AssertFalse    被註解的元素必須爲 false    
@Min(value)     被註解的元素必須是一個數字,其值必須大於等於指定的最小值    
@Max(value)     被註解的元素必須是一個數字,其值必須小於等於指定的最大值    
@DecimalMin(value)  被註解的元素必須是一個數字,其值必須大於等於指定的最小值    
@DecimalMax(value)  被註解的元素必須是一個數字,其值必須小於等於指定的最大值    
@Size(max=, min=)   被註解的元素的大小必須在指定的範圍內   集合或數組 集合或數組的大小是否在指定範圍內 
@Digits (integer, fraction)     被註解的元素必須是一個數字,驗證是不是符合指定格式的數字,interger指定整數精度,fraction指定小數精度。   
@Past   被註解的元素必須是一個過去的日期    
@Future     被註解的元素必須是一個未來的日期    
@Pattern(regex=,flag=)  被註解的元素必須符合指定的正則表達式

Hibernate Validator 提供的校驗註解:git

@NotBlank(message =)   驗證字符串非null,且長度必須大於0    
@Email  被註釋的元素必須是電子郵箱地址    
@Length(min=,max=)  被註解的值大小必須在指定的範圍內    
@NotEmpty   被註解的字符串的必須非空    
@Range(min=,max=,message=)  驗證該值必須在合適的範圍內

能夠在須要驗證的屬性上,使用多個驗證方式,它們同時生效。
spring boot web 已經有 hibernate-validation 的依賴,因此不須要再手動添加依賴。web

使用

首先我在個人實體類上寫了幾個校驗註解。正則表達式

public class SysUserEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    //主鍵
    private Long id;
    //用戶名
    @NotBlank(message = "用戶名不能爲空", groups = {AddGroup.class, UpdateGroup.class})
    private String username;
    //密碼
    @NotBlank(message = "密碼不能爲空", groups = {AddGroup.class})
    private String password;
    //手機號
    @Pattern(regexp = "^1([345789])\\d{9}$",message = "手機號碼格式錯誤")
    @NotBlank(message = "手機號碼不能爲空")
    private String mobile;
    //郵箱
    @Email(message = "郵箱格式不正確")
    private String email;
    //建立者
    private Long createUserId;
    //建立時間
    private Date createDate;
// ignore set and get

使用@Validated進行校驗

首先了解下:
關於@Valid和@Validated的區別聯繫
* @Valid: javax.validation, 是javax,也是就是jsr303中定義的規範註解
* @Validated: org.springframework.validation.annotation, 是spring本身封裝的註解。參數校驗失敗拋出 org.springframework.validation.BindException 異常。spring

@Validated@Valid 的一個變種,擴展了 @Valid 的功能,支持 group分組校驗 的寫法,因此爲了校驗統一,儘可能使用 @Validated數據庫

在controller自定義一個接口json

@PostMapping("/valid")
    public ResponseEntity<String> valid(@Validated @RequestBody SysUserEntity user, BindingResult result) {
        if (result.hasErrors()) {
            return ResponseEntity.status(BAD_REQUEST).body("校驗失敗");
        }
        return ResponseEntity.status(OK).body("校驗成功");
    }

須要注意的有幾點:
* 須要校驗對象的時候,須要加上 spring 的校驗註解 @Validated ,表示咱們須要 spring 對它進行校驗,而校驗的信息會存放到其後的BindingResult中。
* BindingResult 必須和檢驗對象緊鄰,中間不能穿插任何參數,若是有多個校驗對象 @Validated @RequestBody SysUserEntity user, BindingResult result, @Validated @RequestBody SysUserEntity user1, BindingResult result1後端

我在前端用 Swagger 進行測試下。
我發送一個 body,將 手機號輸錯:

{
  "createDate": "",
  "createUserId": 0,
  "email": "k@wuwii.com",
  "id": 0,
  "mobile": "12354354",
  "password": "123",
  "username": "12312" }

後端調試下 BindingResult 的結果,發現結果:
image
只要注意下 errors 屬性,它是校驗全部不符合規則的,是一個數組。

分組校驗

有時候 ,咱們在新增和更新的時候校驗效果是不同的。例如上面,我在User新增的時候須要判斷密碼是否是爲空,可是更新的時候我不作校驗。這個時候就也要用到分組校驗了。

@NotBlank(message = "密碼不能爲空", groups = {AddGroup.class})
private String password;

將Contoller中的校驗修改下。

(@Validated({AddGroup.class}) @RequestBody SysUserEntity user, BindingResult result)

上面的意思是隻有分組是AddGroup的校驗才生效,其他的校驗忽略。

通過我測試,把分組狀況分下:
1. 在controller校驗沒加分組的時候,只對實體類的沒有分組的註解有效。
2. 在controller校驗加分組的時候,只對實體類的當前分組的註解有效,沒有註解的也無效。
3. 當校驗有兩個分組的時候@Validated({AddGroup.class, UpdateGroup.class}),知足當前兩個分組其中任意一個均可以校驗,兩個註解同時一塊兒出現,也沒問題,並且檢驗不經過的信息不會重複。

自定義校驗

有時候系統提供給咱們的校驗註解,並不夠用,咱們能夠自定義校驗,來知足咱們的業務需求。

例如:如今咱們有一個需求,須要檢測一條信息的敏感詞彙,如sb ……文明人,舉個栗子 ……

自定義校驗註解
// 註解能夠用在哪些地方
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
// 指定校驗規則實現類
@Constraint(validatedBy = {NotHaveSBValidator.class})
public @interface NotHaveSB {
    //默認錯誤消息
    String message() default "不能包含字符sb";
    //分組
    Class<?>[] groups() default {};
    //負載
    Class<? extends Payload>[] payload() default {};
    //指定多個時使用
    @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        NotHaveSB[] value();
    }

}
規則校驗實現類
// 能夠指定檢驗類型,這裏選擇的是 String
public class NotHaveSBValidator implements ConstraintValidator<NotHaveSB, String> {
    @Override
    public void initialize(NotHaveSB notHaveSB) {

    }

    /** * * @param s 待檢驗對象 * @param constraintValidatorContext 檢驗上下文,能夠設置檢驗的錯誤信息 * @return false 表明檢驗失敗 */
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        return !StringUtils.isNotBlank(s) || !s.toLowerCase().contains("sb");
    }
}

全部的驗證者都須要實現ConstraintValidator接口,它的接口也很形象,包含一個初始化事件方法,和一個判斷是否合法的方法。

測試一下喂

如今個人用戶類上,也沒什麼多餘的字段拿出來測試,暫時把 password 字段拿來測試吧。

//@NotBlank(message = "密碼不能爲空", groups = AddGroup.class)
    @NotHaveSB
    private String password;

image

手動校驗

這個是我最終想要的處理方式。
因爲如今都是先後端分離開發的,校驗失敗的時候,拋出自定義的異常,而後統一處理這些異常,最後將相關的錯誤提示信息返回給前端處理。

新建一個驗證工具類

public class ValidatorUtils {
    private static Validator validator;

    static {
        validator = Validation.buildDefaultValidatorFactory().getValidator();
    }

    /** * 手動校驗對象 * * @param object 待校驗對象 * @param groups 待校驗的組 * @throws KCException 校驗不經過,則拋出 KCException 異常 */
    public static void validateEntity(Object object, Class<?>... groups)
            throws KCException {
        Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
        if (!constraintViolations.isEmpty()) {
            String msg = constraintViolations.parallelStream()
                    .map(ConstraintViolation::getMessage)
                    .collect(Collectors.joining(","));
            throw new KCException(msg);
        }
    }
}

它主要作的事情就是驗證咱們的待驗證對象,驗證不一樣經過的時候,拋出自定義異常,在後臺統一處理異常就能夠了。

在業務中直接調用就能夠了,有分組添加分組就行

@PostMapping("/valid1")
    public ResponseEntity<String> customValid(@RequestBody SysUserEntity user) {
        ValidatorUtils.validateEntity(user);
        return ResponseEntity.status(OK).body("校驗成功");
    }

最後測試一下,查看返回結果是否符合預期:

image

手動校驗的補充

決定仍是採用註解的形式進行編碼,原本想用處理方法參數的裝配進行檢驗,寫好了發現和 @responseBody 不能同時使用,而後發現仍是可使用 @Validated 直接校驗,拋出異常, 進行捕捉異常統一處理。

@PostMapping()
    @ApiOperation("新增")
    public ResponseEntity insert(@Validated SysUserAddForm user)

在全局異常處理裏面加上 處理綁定參數異常 org.springframework.validation.BindException

/** * 參數檢驗違反約束(數據校驗) * @param e BindException * @return error message */
    @org.springframework.web.bind.annotation.ExceptionHandler(BindException.class)
    public ResponseEntity<String> handleConstraintViolationException(BindException e) {
        LOGGER.debug(e.getMessage(), e);
        return ResponseEntity.status(BAD_REQUEST).body(
                e.getBindingResult()
                        .getAllErrors()
                        .stream()
                        .map(DefaultMessageSourceResolvable::getDefaultMessage)
                        .collect(Collectors.joining(",")));
    }
相關文章
相關標籤/搜索