後臺數據的校驗也是開發中比較注重的一點,用來校驗數據的正確性,以避免一些非法的數據破壞系統,或者進入數據庫,形成數據污染,因爲數據檢驗可能應用到不少層面,因此係統對數據校驗要求比較嚴格且追求可變性及效率。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
首先了解下:
關於@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 的結果,發現結果:
只要注意下 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;
這個是我最終想要的處理方式。
因爲如今都是先後端分離開發的,校驗失敗的時候,拋出自定義的異常,而後統一處理這些異常,最後將相關的錯誤提示信息返回給前端處理。
新建一個驗證工具類
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("校驗成功");
}
最後測試一下,查看返回結果是否符合預期:
決定仍是採用註解的形式進行編碼,原本想用處理方法參數的裝配進行檢驗,寫好了發現和 @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(",")));
}