前端
基于vue和elementUI。
- 在
el-form
标签加属性:rules="dataRule"
<el-form :model="dataForm" :rules="dataRule"></el-form>
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校验。
- 引入依赖
<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>
- 在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;
}
- 在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();
}
}