前后端校验小结

前端

基于vue和elementUI。

  1. el-form标签加属性:rules="dataRule"
<el-form :model="dataForm" :rules="dataRule"></el-form>
  1. data里加校验规则,比如我现在要对name进行校验
<el-form
      :model="dataForm"
      :rules="dataRule"
      ref="dataForm"
      @keyup.enter.native="dataFormSubmit()"
      label-width="140px"
    >
      <el-form-item label="品牌名" prop="name">
        <el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
      </el-form-item>
</el-form>
data() {
    return {
      dataForm: {
        brandId: 0,
        name: "",
        logo: "",
        descript: "",
        showStatus: 1,
        firstLetter: "",
        sort: 0,
      },
      dataRule: {
        name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }]
    };
}

还可以自定义校验规则。比如我想对firstLetter字段进行检验,规则是:只允许包含一个字母

<el-form
      :model="dataForm"
      :rules="dataRule"
      ref="dataForm"
      @keyup.enter.native="dataFormSubmit()"
      label-width="140px"
    >
      <el-form-item label="品牌名" prop="name">
        <el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
      </el-form-item>
      <el-form-item label="检索首字母" prop="firstLetter">
        <el-input
          v-model="dataForm.firstLetter"
          placeholder="检索首字母"
        ></el-input>
      </el-form-item>
</el-form>
data() {
    return {
      dataForm: {
        brandId: 0,
        name: "",
        logo: "",
        descript: "",
        showStatus: 1,
        firstLetter: "",
        sort: 0,
      },
      dataRule: {
        name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }],
        firstLetter: [
          {
            validator: (rule, value, callback) => {
              if (value == "") {
                callback(new Error("检索首字母不能为空"));
              } else if (!/^[a-zA-Z]$/.test(value)) {
                callback(new Error("检索首字母必须为一个字母"));
              } else {
                callback();
              }
            },
            trigger: "blur",
          },
        ],
    };
}

针对数字类型,除了可以使用正则校验外,还可以在双向绑定的时候告诉vue这是一个数字。

<el-form-item label="排序" prop="sort">
	<el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>
</el-form-item>

校验部分

sort: [
    {
        validator: (rule, value, callback) => {
            if (!Number.isInteger(value) || value < 0) {
                callback(new Error("排序必须为大于等于0的整数"));
            } else {
                callback();
            }
        },
        trigger: "blur",
    },
]

后端

基于JSR303校验。

  1. 引入依赖
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.0.Final</version>
</dependency>
  1. 在bean中添加各种注解
/**
 * 品牌
 *
 * @author liuzhulin
 * @email 1392673190@qq.com
 * @date 2021-03-02 11:00:29
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 品牌id
     */
    @TableId
    private Long brandId;
    /**
     * 品牌名
     */
    @NotBlank(message = "品牌名不能为空")
    private String name;
    /**
     * 品牌logo地址
     */
    @NotBlank
    @URL(message = "品牌logo地址必须是合法url地址")
    private String logo;
    /**
     * 介绍
     */
    private String descript;
    /**
     * 显示状态[0-不显示;1-显示]
     */
    @NotNull
    @Min(value = 0, message = "显示状态必须为0或1")
    @Max(value = 1, message = "显示状态必须为0或1")
    private Integer showStatus;
    /**
     * 检索首字母
     */
    @NotBlank
    @Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须为一个字母")
    private String firstLetter;
    /**
     * 排序
     */
    @NotNull
    @Min(value = 0)
    private Integer sort;

}
  1. 在controller中添加@Valid注解就完成了。但是返回给前端的信息不够友好,为了统一前端的返回格式,可以在该参数后紧跟着BindingResult注意:必须紧跟着,该对象封装了校验不通过的字段和消息,可以将它们封装成map返回给前端。这里有个坑:如果不传某个字段,并且不设置不为空,那么就不会对其进行校验。
/**
 * 品牌
 *
 * @author liuzhulin
 * @email 1392673190@qq.com
 * @date 2021-03-02 11:58:02
 */
@RestController
@RequestMapping("product/brand")
public class BrandController {
    @Autowired
    private BrandService brandService;
    /**
     * 保存
     */
    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
        Map<String, String> map = new HashMap<>();
        if (result.hasErrors()) {
            result.getFieldErrors().forEach(p -> {
                String field = p.getField();
                String message = p.getDefaultMessage();
                map.put(field, message);
            });
            return R.error(400, "提交数据不合法").put("data", map);
        }
		brandService.save(brand);
        return R.ok();
    }
}

验证不通过返回的格式是这样的,这样就比较友好了。

{
    "msg": "提交数据不合法",
    "code": 400,
    "data": {
        "name": "品牌名不能为空",
        "logo": "品牌logo地址必须是合法url地址",
        "showStatus": "显示状态必须为0或1",
        "sort": "不能为null",
        "firstLetter": "不能为空"
    }
}

改进:统一异常处理

现在基本功能已经完成了,不过有一个问题:写多了controller就发现,关于校验这部分代码都是一个模板。我们其实可以继续优化,做成统一的异常处理。做法就是controller层出现异常不捕获,让Spring感知到异常后交给自己写的统一异常处理类来处理。

改进代码,让异常处理代码和业务代码解耦

/**
 * 品牌
 *
 * @author liuzhulin
 * @email 1392673190@qq.com
 * @date 2021-03-02 11:58:02
 */
@RestController
@RequestMapping("product/brand")
public class BrandController {
    @Autowired
    private BrandService brandService;
    /**
     * 保存
     */
    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
		brandService.save(brand);
        return R.ok();
    }
}

编写一个exception包,其下新增类

@Slf4j
//@RestControllerAdvice = ResponseBody + ControllerAdvice
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
//在测试过程中发现校验不通过后没有进这个类,最后是设置一个注解解决的。网上也有人用@Priority,但是我试过没有作用
@Order(1)
public class GulimallExceptionControllerAdvice {

    //表示出现MethodArgumentNotValidException后执行该方法
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e) {
        log.error("异常类型:{},异常消息:{}", e.getClass(), e.getMessage());
        //该方法可以获得具体信息
        BindingResult result = e.getBindingResult();
        Map<String, String> map = new HashMap<>();
        result.getFieldErrors().forEach(p -> {
            map.put(p.getField(), p.getDefaultMessage());
        });
        return R.error().put("data", map);
    }
    
    // 如果能精确匹配异常,就走精确的异常,否则就走最大的异常
    @ExceptionHandler(value = Throwable.class)
    public R handleException(Throwable e) {
        return R.error();
    }
}
原文地址:https://www.cnblogs.com/liuzhulin/p/14542910.html