商品服务-品牌管理表单校验
1. 前端校验
- 前端在给后端提交表单时要进行数据校验功能。
- https://element.eleme.cn/#/zh-CN/component/form
前端校验功能防君子,后端检验功能防小人(防止绕过前端界面直接发请求)。

Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可。
- 自定义校验规则
dataRule
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="140px">
</el-form>
1.1 品牌名、logo地址、介绍
这三项都不为空即可。
- 绑定校验规则的
name、logo、descript属性,同时和data中的dataForm.name、dataForm.logo、dataForm.descript双向绑定。
<el-form-item label="品牌名" prop="name">
<el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
</el-form-item>
<el-form-item label="品牌logo地址" prop="logo">
<single-upload v-model="dataForm.logo"></single-upload>
</el-form-item>
<el-form-item label="介绍" prop="descript">
<el-input v-model="dataForm.descript" placeholder="介绍"></el-input>
</el-form-item>
校验规则
required: true表示该项必填,message的值表示如果不符合规则提示信息。trigger: "blur"失去焦点事件。
dataRule: {
name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }],
logo: [{ required: true, message: "品牌logo地址不能为空", trigger: "blur" },],
descript: [{ required: true, message: "介绍不能为空", trigger: "blur" },],
showStatus: [{ required: true, message: "显示状态不能为空", trigger: "blur" },],
},
1.2 检索首字母、排序
检索首字母必须是a-z或A-Z,使用正则表达式即可。排序必须是大于等于0的整数
<el-form-item label="检索首字母" prop="firstLetter">
<el-input v-model="dataForm.firstLetter" placeholder="检索首字母"></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>
</el-form-item>
校验规则
dataRule: {
firstLetter: [
{ validator:(rule, value, callback)=>{
if(value ==""){
callback(new Error('首字母必须填写'));
}else if(!/^[a-zA-Z]$/.test(value)){
callback(new Error('首字母必须a-z或者A-Z之间'));
}else{
callback();
}
}, trigger: "blur" },
],
sort: [
{ validator:(rule, value, callback)=>{
if(value==""){
callback(new Error('排序字段必须填写'));
}else if(!Number.isInteger(value) || value<0){
callback(new Error('排序字段必须是一个大于等于0的整数'));
}else{
callback();
}
}, trigger: "blur" },
],
},
1.3 给表单默认值
dataForm: {
brandId: 0,
name: "",
logo: "",
descript: "",
showStatus: 1,
firstLetter: "",
sort: 0,
},
2. 后端校验
只有前端检验还不行,有可能别人知道你的接口,直接通过postman向后台发送请求。
校验注解都在这个包:javax.validation.constraints
- 后台使用JSR303规范
- 给需要提交给数据库的bean添加注解,并自定义提示信息。
- 在controller层标注提交的bean需要进行校验
@Valid。 - 给需要校验的bean加一个BindingResult,就可以获取到校验的结果。
以品牌名为例。
public class BrandEntity implements Serializable {
//该属性至少有一个非空字符(只有一个空格不允许)
@NotBlank(message = "品牌名不能为空")
private String name;
}
@RestController
@RequestMapping("product/brand")
public class BrandController {
@Autowired
private BrandService brandService;
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
}
测试
传入name为空请求,返回400错误。

2.1 自定义接口返回内容
数据提交不符合规范后的返回内容不是我们想要的,可以自定义返回内容。
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
if (result.hasErrors()){
//新建一个集合,用来存放错误数据
HashMap<String, String> map = new HashMap<>();
result.getFieldErrors().forEach((item)->{
//错误属性字段
String field= item.getField();
//错误提示信息
String message = item.getDefaultMessage();
map.put(field,message);
});
return R.error(400,"提交数据不合法").put("data",map);
}else{
brandService.save(brand);
}
return R.ok();
}
测试

给其他字段添加注解校验
@NotBlank(message = "品牌名不能为空")
private String name;
@NotEmpty
@URL(message = "logo必须是一个合法的URL地址")
private String logo;
//自定义注解,传入一个正则表达式
@Pattern(regexp = "/^[a-zA-Z]$/",message = "检索首字母必须是一个字母")
@NotEmpty
private String firstLetter;
@Min(value =0,message = "排序必须是一个大于等于0的整数")
@NotNull
private Integer sort;
测试

3. 统一异常处理
- 自定义一个异常处理类,用来处理controller层捕获的异常。
- controller层遇到异常,进行抛出即可。
//处理哪些controller的异常,返回值都是json格式。
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class MyExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
BindingResult result = e.getBindingResult();
//新建一个集合,用来存放错误数据
HashMap<String, String> map = new HashMap<>();
result.getFieldErrors().forEach((item)-> {
//错误属性字段
String field = item.getField();
//错误提示信息
String message = item.getDefaultMessage();
map.put(field, message);
});
return R.error(400,"提交数据不合法").put("data",map);
}
}
controller
- controller层就不用对异常进行处理了。
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
系统错误码
错误码和错误信息定义类
1.错误码定义规则为5为数字
2.前两位表示业务场景,最后三位表示错误码。例如:10000。10:通用 000:系统未知异常
3.维护错误码后需要维护错误描述,将他们定义为枚举形式
错误码列表;
10:通用
000:系统未知异常
001:参数格式校验
11:商品
12:订单
13:购物车
14:物流
3.1 自定义枚举异常类
枚举异常类定义在commom服务中,方便别的服务引用。
public enum BizCodeEnume {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_EXCEPTION(10001,"参数格式校验失败异常");
private int code;
private String msg;
//枚举构造器
BizCodeEnume(int code,String msg){
this.code=code;
this.msg=msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
3.2 在异常处理类中使用该枚举
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class MyExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
BindingResult result = e.getBindingResult();
HashMap<String, String> map = new HashMap<>();
result.getFieldErrors().forEach((item)-> {
String field = item.getField();
String message = item.getDefaultMessage();
map.put(field, message);
});
return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",map);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable e){
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
}
4. JSR303分组校验功能
需求:新增和修改功能,需要校验的规则不同,比如:
- id是自增的,新增的时候不需要进行验证,但是修改的时候需要验证。
- url字段新增的时候必填,修改的时候如果字段不变可以不用填。
1.定义分组接口
//定义一个空接口即可,只是一个标识。
public interface AddGroup {
}
public interface UpdateGroup {
}
2.在bean属性上添加分组
- 给校验注解标注什么情况需要进行校验。
- 没有标注分组的注解,在分组校验
@Validated({AddGroup.class})情况下不生效。
@Null(message = "新增不能指定id",groups = {AddGroup.class})
@NotNull(message = "修改必须指定id",groups = {UpdateGroup.class})
private Long brandId;
@NotBlank(message = "品牌名不能为空",groups = {AddGroup.class,UpdateGroup.class})
private String name;
@NotEmpty(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的URL地址",groups ={AddGroup.class,UpdateGroup.class} )
private String logo;
- 在controller标注需要校验哪种规则@Validated
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
@RequestMapping("/update")
public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
5. 自定义检验注解
需求:有些情况正则不能满足我们的需求,需要自定义注解来实现数据校验功能。
- 比如,
showStatus属性 。显示状态[0-不显示;1-显示]
<!--数据校验依赖 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
5.1 编写自定义检验注解
- 指定注解校验器
- 自定义错误信息
- 自定义属性
@Documented
@Constraint(validatedBy = { }) //指定注解校验器
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
//@Repeatable(List.class) 可重复注解
public @interface ListValue {
//JSR303规定的自定义校验注解必须的三个属性
String message() default "{com.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
//自定义注解属性
int[] values() default {};
}
ValidationMessages.properties
- 自定义错误信息
- Unicode转换https://www.zxgj.cn/g/unicode
com.atguigu.common.valid.ListValue.message=\u5fc5\u987b\u63d0\u4ea4\u6307\u5b9a\u7684\u503c
5.2 编写自定义校验器
- 判断提交的值是否在注解规定的范围内
public class ListValueConstraintValidator implements ConstraintValidator<ListValue , Integer> {
private Set<Integer> set =new HashSet<>();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] values = constraintAnnotation.values();
for (int value : values) {
set.add(value);
}
}
//判断是否校验成功value:需要检验的值
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
5.3 关联自定义检验注解和自定义校验器
@Constraint(validatedBy = { ListValueConstraintValidator.class}) //指定注解校验器
public @interface ListValue {}
测试
@ListValue(values = {0,1},groups = {AddGroup.class})
private Integer showStatus;

6. 总体测试
新增没问题,修改状态时报错。
Request URL: http://localhost:88/api/product/brand/update


6.1 问题分析
- 修改显示状态发送
update请求,使用的UpdateGroup.class组的验证。 - 品牌名验证规则是:新增和修改时都必须携带。
- 如果改为只是新增时需要携带品牌名会出现:绕过前端直接发修改请求,不用携带品牌名也能进行修改。
@RequestMapping("/update")
public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
@NotBlank(message = "品牌名不能为空",groups = {AddGroup.class,UpdateGroup.class})
private String name;
6.2 解决方案
新增一个修改状态的组
public interface UpdateStateGroup {
}
修改showStatus校验规则
@NotNull(groups = {AddGroup.class,UpdateStateGroup.class})
@ListValue(values = {0,1},groups = {AddGroup.class,UpdateStateGroup.class})
private Integer showStatus;
新增controller单独处理修改状态请求
- 只需要传入id和状态即可。
/**
* 修改显示状态
*/
@RequestMapping("/update/state")
// @RequiresPermissions("product:brand:update")
public R updateState(@Validated({UpdateStateGroup.class}) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
前端更改请求路径
updateBrandStatus(data) {
//结构获取需要的两个值
let {brandId,showStatus}=data;
//后台发送修改信息。
this.$http({
url: this.$http.adornUrl('/product/brand/update/state'),
method: 'post',
data: this.$http.adornData({brandId,showStatus}, false)
}).then(({ data }) => {
this.$message({
type:"success",
message:"状态更新成功"
})
});
},
再次测试,更改状态成功,数据库数据更新成功。



浙公网安备 33010602011771号