JSR303 數據校驗

前端頁面進行數據校驗之後,提交的數據後端還需要再進行一次數據校驗

1. JSR303 數據校驗

1. 給bean添加校驗註解 javax.validation.constraints 

package com.zx.zxmall.product.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;

/**
 * 品牌
 * 
 * @author zx
 * @email [email protected]
 * @date 2020-06-10 23:31:58
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 * @NotBlank 必須有一個非空格字符
	 */
	@NotBlank
	private String name;
	/**
	 * 品牌logo地址
	 */
	private String logo;
	/**
	 * 介紹
	 */
	private String descript;
	/**
	 * 顯示狀態[0-不顯示;1-顯示]
	 */
	private Integer showStatus;
	/**
	 * 檢索首字母
	 */
	private String firstLetter;
	/**
	 * 排序
	 */
	private Integer sort;

}


2. 開啓校驗功能@Valid

    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Valid @RequestBody BrandEntity brand){
        brandService.save(brand);
        return R.ok();
    }

3. 使用postman發送數據

{"name":""}

http://localhost:88/api/product/brand/save

{
    "timestamp": "2020-06-20T23:40:29.294+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "NotBlank.brandEntity.name",
                "NotBlank.name",
                "NotBlank.java.lang.String",
                "NotBlank"
            ],
            "arguments": [
                {
                    "codes": [
                        "brandEntity.name",
                        "name"
                    ],
                    "arguments": null,
                    "defaultMessage": "name",
                    "code": "name"
                }
            ],
            "defaultMessage": "不能爲空",
            "objectName": "brandEntity",
            "field": "name",
            "rejectedValue": "",
            "bindingFailure": false,
            "code": "NotBlank"
        }
    ],
    "message": "Validation failed for object='brandEntity'. Error count: 1",
    "path": "/product/brand/save"
}

默認的提示信息是按照ValidationMessages_zh_CN.properties中的配置信息進行提示

javax.validation.constraints.AssertFalse.message     = 只能爲false
javax.validation.constraints.AssertTrue.message      = 只能爲true
javax.validation.constraints.DecimalMax.message      = 必須小於或等於{value}
javax.validation.constraints.DecimalMin.message      = 必須大於或等於{value}
javax.validation.constraints.Digits.message          = 數字的值超出了允許範圍(只允許在{integer}位整數和{fraction}位小數範圍內)
javax.validation.constraints.Email.message           = 不是一個合法的電子郵件地址
javax.validation.constraints.Future.message          = 需要是一個將來的時間
javax.validation.constraints.FutureOrPresent.message = 需要是一個將來或現在的時間
javax.validation.constraints.Max.message             = 最大不能超過{value}
javax.validation.constraints.Min.message             = 最小不能小於{value}
javax.validation.constraints.Negative.message        = 必須是負數
javax.validation.constraints.NegativeOrZero.message  = 必須是負數或零
javax.validation.constraints.NotBlank.message        = 不能爲空
javax.validation.constraints.NotEmpty.message        = 不能爲空
javax.validation.constraints.NotNull.message         = 不能爲null
javax.validation.constraints.Null.message            = 必須爲null

可以定義自己的message信息進行提示

package com.zx.zxmall.product.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;

/**
 * 品牌
 * 
 * @author zx
 * @email [email protected]
 * @date 2020-06-10 23:31:58
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 * @NotBlank 必須有一個非空格字符
	 */
	@NotBlank(message = "品牌名必須提交")
	private String name;
	/**
	 * 品牌logo地址
	 */
	@NotEmpty
	@URL(message = "logo必須是一個合法的url地址")
	private String logo;
	/**
	 * 介紹
	 */
	private String descript;
	/**
	 * 顯示狀態[0-不顯示;1-顯示]
	 */
	private Integer showStatus;
	/**
	 * 檢索首字母
	 */
	//自定義規則 @Pattern
	@NotEmpty
	@Pattern(regexp = "/^[a-zA-Z]$/",message = "檢索首字母必須是一個字母")
	private String firstLetter;
	/**
	 * 排序
	 */
	@NotNull
	@Min(value = 0,message = "排序必須大於等於0")
	private Integer sort;

}

 給校驗的bean後緊跟一個BindingResult,就可以獲取到校驗的結果

    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
        if(result.hasErrors()){
            Map<String,String> map=new HashMap<>();
            //1.獲取校驗的錯誤結果
            result.getFieldErrors().forEach((item)->{
                //FieldError獲取到錯誤提示
                String message=item.getDefaultMessage();
                //獲取錯誤的屬性的名字
                String field=item.getField();
                map.put(field,message);
            });
            return R.error(400,"提交的數據不合法").put("data",map);
        }else{
            brandService.save(brand);
        }
        return R.ok();
    }

使用postman提交數據   

http://localhost:88/api/product/brand/save

提交數據 
{"name":""}
 
{
    "msg": "提交的數據不合法",
    "code": 400,
    "data": {
        "name": "品牌名必須提交",
        "logo": "不能爲空",
        "sort": "不能爲null",
        "firstLetter": "不能爲空"
    }
}

{"name":"abc","logo":"abc"}
{
    "msg": "提交的數據不合法",
    "code": 400,
    "data": {
        "logo": "logo必須是一個合法的url地址",
        "sort": "不能爲null",
        "firstLetter": "不能爲空"
    }
}

注:

@NotEmpty的標註範圍

@NotNull 可以標註任意類型

/**
 * The annotated element must not be {@code null} nor empty.
 * <p>
 * Supported types are:
 * <ul>
 * <li>{@code CharSequence} (length of character sequence is evaluated)</li>
 * <li>{@code Collection} (collection size is evaluated)</li>
 * <li>{@code Map} (map size is evaluated)</li>
 * <li>Array (array length is evaluated)</li>
 * </ul>
 *
 * @author Emmanuel Bernard
 * @author Hardy Ferentschik
 *
 * @since 2.0
 */
@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
public @interface NotEmpty{}


------
/**
 * The annotated element must not be {@code null}.
 * Accepts any type.
 *
 * @author Emmanuel Bernard
 */
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {}

2. 統一異常處理

1. 編寫異常處理類 使用@ControllerAdvice

使用@ExceptionHandler標註方法可以處理的異常

package com.zx.zxmall.product.exception;

import com.zx.common.exception.BizCodeEnum;
import com.zx.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

/**
 * 集中處理所有異常
 */
@Slf4j
//@ResponseBody
//@ControllerAdvice(basePackages = "com.zx.zxmall.product.controller")
@RestControllerAdvice(basePackages = "com.zx.zxmall.product.controller")
public class ZxMallExceptionControllerAdvice {

//   @ExceptionHandler(value = Exception.class)
//   public R handleValidException(Exception e){
//        log.error("數據校驗出現異常{},異常類型{}",e.getMessage(),e.getClass());
//        return R.error();
//        //"msg": "未知異常,請聯繫管理員",
//        //    "code": 500
//   }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e) {
        log.error("數據校驗出現異常{},異常類型{}", e.getMessage(), e.getClass());
        BindingResult bindingResult=e.getBindingResult();
        Map<String,String> errorMap=new HashMap<>();
        bindingResult.getFieldErrors().forEach((fieldError)->{
            errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
        });
        return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(),BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data",errorMap);
        //return R.error(400,"數據校驗出現問題").put("data",errorMap);
    }

    @ExceptionHandler(value = Throwable.class)
    public R handleException(Throwable throwable){
        return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(),BizCodeEnum.UNKNOW_EXCEPTION.getMsg());
    }
}
package com.zx.common.exception;

/**
 * 系統錯誤碼
 *  * 1,錯誤碼定義規則爲5爲數字
 *  * 2,前兩位表示業務場景,最後三位表示錯誤碼。
 *  *   例如:100001,10:通用    001:系統未知異常
 *  * 3,維護錯誤碼後需要維護錯誤描述,將他們定義爲枚舉形式
 *  * 錯誤碼列表
 *  * 10:通用
 *  *  001:參數格式校驗
 *  * 11:商品
 *  * 12:訂單
 *  * 13:購物車
 *  * 14:物流
 */
public enum BizCodeEnum {

    UNKNOW_EXCEPTION(10000,"系統未知異常"),
    VALID_EXCEPTION(10001,"參數格式校驗失敗");

    private int code;
    private String msg;

    BizCodeEnum(int code,String msg){
        this.code=code;
        this.msg=msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}
{"name":"abc","logo":"abc"}

{
    "msg": "參數格式校驗失敗",
    "code": 10001,
    "data": {
        "logo": "logo必須是一個合法的url地址",
        "sort": "不能爲null",
        "firstLetter": "不能爲空"
    }
}

3. 分組校驗

1. 給校驗註解標註什麼情況需要進行校驗
 

    @NotNull(message = "修改必須指定品牌id",groups = {UpdateGroup.class})
	@Null(message = "新增不能指定id",groups = {AddGroup.class})
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 * @NotBlank 必須有一個非空格字符
	 */
	
	@NotBlank(message = "品牌名必須提交",groups = {AddGroup.class,UpdateGroup.class})
	private String name;
package com.zx.common.exception;

public class UpdateGroup {
}


------
package com.zx.common.exception;

public interface AddGroup {
}

 

2. @Validated({AddGroup.class})

    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
        brandService.save(brand);
        return R.ok();
    }

 

{"name":"abc","logo":"abc","brandId":"3"}

{
    "msg": "參數格式校驗失敗",
    "code": 10001,
    "data": {
        "brandId": "新增不能指定id"
    }
}

//沒有標註分組的默認不生效,讓其生效就必須指定分組
{"name":"abc","logo":"abc"}
{
    "msg": "success",
    "code": 0
}

3. 默認沒有指定分組的校驗註解@NotBlank 在分組校驗情況@Validated({UpdateGroup.class})下不生效, 只會在@Valid生效

package com.zx.zxmall.product.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import java.util.Date;

import com.zx.common.exception.AddGroup;
import com.zx.common.exception.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;

/**
 * 品牌
 * 
 * @author zx
 * @email [email protected]
 * @date 2020-06-10 23:31:58
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@NotNull(message = "修改必須指定品牌id",groups = {UpdateGroup.class})
	@Null(message = "新增不能指定id",groups = {AddGroup.class})
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 * @NotBlank 必須有一個非空格字符
	 */

	@NotBlank(message = "品牌名必須提交",groups = {AddGroup.class,UpdateGroup.class})
	private String name;
	/**
	 * 品牌logo地址
	 * 新增不能爲空
	 */
	@NotEmpty(groups = {AddGroup.class})
	@URL(message = "logo必須是一個合法的url地址",groups = {AddGroup.class,UpdateGroup.class})
	private String logo;
	/**
	 * 介紹
	 */
	private String descript;
	/**
	 * 顯示狀態[0-不顯示;1-顯示]
	 */
	private Integer showStatus;
	/**
	 * 檢索首字母
	 */
	//自定義規則 @Pattern
	@NotEmpty(groups = {AddGroup.class})
	@Pattern(regexp = "/^[a-zA-Z]$/",message = "檢索首字母必須是一個字母",groups = {AddGroup.class,UpdateGroup.class})
	private String firstLetter;
	/**
	 * 排序
	 */
	@NotNull(groups = {AddGroup.class})
	@Min(value = 0,message = "排序必須大於等於0",groups = {AddGroup.class,UpdateGroup.class})
	private Integer sort;

}
    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
        brandService.save(brand);
        return R.ok();
    }
    /**
     * 修改
     */
    @RequestMapping("/update")
   // @RequiresPermissions("product:brand:update")
    public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
		brandService.updateById(brand);
        return R.ok();
    }

4. 自定義校驗

 1.編寫一個自定義的校驗註解

package com.zx.common.valid;

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.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {

    String message() default "{com.zx.common.valid.ListValue.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    int[] vals() default {};
}

添加ValidationMessages.properties 

com.zx.common.valid.ListValue.message=必須提交指定的值

 2.編寫一個自定義的校驗器

package com.zx.common.valid;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

    Set<Integer> set=new HashSet<>();
    //初始化方法
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals=constraintAnnotation.vals();
        for(int val:vals){
            set.add(val);
        }
    }

    //判斷是否校驗成功

    /**
     *
     * @param value 需要校驗的值
     * @param constraintValidatorContext
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
        return set.contains(value);
    }
}

 3.關聯自定義的校驗器和自定義的校驗註解

    @ListValue(vals={0,1},groups = {AddGroup.class})
	private Integer showStatus;
http://localhost:88/api/product/brand/save

{"name":"abc","logo":"abc","showStatus":3}

{
    "msg": "參數格式校驗失敗",
    "code": 10001,
    "data": {
        "logo": "logo必須是一個合法的url地址",
        "showStatus": "必須提交指定的值",
        "sort": "不能爲null",
        "firstLetter": "不能爲空"
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章