前端頁面進行數據校驗之後,提交的數據後端還需要再進行一次數據校驗
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": "不能爲空"
}
}