需求
1. 表单校验需求
1.1 前端校验
参考element-ui官网: https://element.eleme.cn/#/zh-CN/component/form

前端表格展示
<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="品牌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>
<!--激活是1 ,不激活是0-->
<el-form-item label="显示状态" prop="showStatus">
<el-switch
v-model="dataForm.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
></el-switch>
</el-form-item>
<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>
</el-form>
校验规则
//和前端输入内容进行双向绑定
dataForm: {
brandId: 0,
name: "",
logo: "",
descript: "",
showStatus: 1,
firstLetter: "",
sort: 0
},
dataRule: {
//name字段校验规则,失去焦点触发。
name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }],
//logo字段校验规则
logo: [{ required: true, message: "品牌logo地址不能为空", trigger: "blur" }],
//描述字段校验规则
descript: [{ required: true, message: "介绍不能为空", trigger: "blur" }],
//展示状态校验规则
showStatus: [{
required: true,
message: "显示状态[0-不显示;1-显示]不能为空",
trigger: "blur"
}],
//首字母校验规则,自定义校验规则,传入一个校验器。
//rule:校验规则, value:传入的值, callback:回调函数。
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.2 后端校验
只有前端校验是不够的的,如果通过postman等工具直接向后端发请求,那前端校验规则就不起作用了。
思路
- 在pojo上添加校验注解。
- controller收到请求,将请求体封装为pojo,且根据注解对其进行校验。
- 校验不通过,将异常抛出。
- 新建一个统一处理异常的类。
- 不同情况,校验规则不同,
分组校验。- 新增时,id自增,不允许传递。
- 修改时,id必须传递。
- 显示状态只能传入0或者1,自定义规则。
1.新增分组校验接口
- 定义空接口即可,只是一个标识。
public interface AddGroup {
}
public interface UpdateGroup {
}
public interface UpdateStateGroup {
}
2.pojo添加校验注解
品牌id新增和修改校验规则不同,指定不同接口。品牌名不能为空。logo不能为空,且必须是合法的url。品牌介绍不能为空。显示状态需要自定义校验器。检索首字母不能为空,且必须符合规则,使用正则表达式即可。排序不能为空,且必须符合规则,使用正则表达式即可。
//品牌id
@TableId
@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;
//品牌logo地址
@NotEmpty(groups = {AddGroup.class,UpdateGroup.class})
@URL(message = "logo必须是一个合法的URL地址",groups ={AddGroup.class,UpdateGroup.class} )
private String logo;
//品牌介绍
@NotEmpty(groups = {AddGroup.class,UpdateGroup.class})
private String descript;
//显示状态[0-不显示;1-显示]
@NotNull(groups = {AddGroup.class,UpdateStateGroup.class})
@ListValue(values = {0,1},groups = {AddGroup.class,UpdateStateGroup.class})
private Integer showStatus;
//检索首字母,自定义注解,传入一个正则表达式
@Pattern(regexp = "^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups ={AddGroup.class,UpdateGroup.class})
@NotEmpty(groups ={AddGroup.class,UpdateGroup.class} )
private String firstLetter;
//排序
@Min(value =0,message = "排序必须是一个大于等于0的整数",groups ={AddGroup.class,UpdateGroup.class})
@NotNull(groups ={AddGroup.class,UpdateGroup.class} )
private Integer sort;
3.Controller接收请求,并进行注解校验
- 根据不同请求指定不同校验分组。
//新增指定校验组为AddGroup
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
//修改指定校验组为UpdateGroup
@RequestMapping("/update")
public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
brandService.updateDetail(brand);
return R.ok();
}
4.统一处理校验出错时返回结果
- 指定包名,该异常处理器只对该包下的异常进行捕获处理。
- 指定异常类型,根据不同的异常进行不同的处理。
@Slf4j
@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);
});
log.error("错误信息"+map);
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());
}
}
2. 父子组件传参
某一页面引用了子组件,需要在子组件点击时获取子组件节点信息,并将该信息传递给父组件。
- 子组件能够感知节点点击,并获取到该节点的信息。
- 节点被点击后的回调函数。https://element.eleme.cn/#/zh-CN/component/tree
- 通过$emit传递事件。

1.引入子组件,并绑定子组件定义的点击事件son-node-click,和父组件的方法fathernodeclick进行双向绑定。
- 可以将子组件的事件参数,传递给父组件绑定的方法中。
// 引入子组件,占页面纵向6份
<el-col :span="6">
<category @son-node-click="fathernodeclick"></category>
</el-col>
// 表格部分,占18份
<el-col :span="18">
...
</el-col>
子组件
- 将回调事件node-click绑定子组件方法nodeclick
<el-tree :data="menus" :props="defaultProps" node-key="catId" :default-expanded-keys="expanded" ref="menuTree" @node-click="nodeclick">
</el-tree>
- 通过
$emit将子组件事件和参数发散出去
nodeclick(data, node, component) {
console.log("树节点被点击了", data, node, component);
//向父组件发送事件(携带参数)和父组件的一个方法进行绑定。
this.$emit("son-node-click", data, node, component);
},
- 父组件通过事件名称进行接收。
- 如果是第三级分类。就将点击的节点id传递给后端,重新查询数据更新表格内容。
<category @son-node-click="fathernodeclick"></category>
//感知树节点被点击
fathernodeclick(data, node, component) {
if (node.level == 3) {
this.catId = data.catId;
this.getDataList(); //重新查询
}
},
3.事务
修改一个表内容,与之相关的其他表格内的数据也应同时修改。
- 为了性能,数据库通常会做一些冗余设计。
- 如果该表数据修改成功,其他表也应该成功。该表修改失败,其他表也修改失败。
品牌表

品牌与分类关联表

1.修改品牌表内容时,应同步修改品牌和分类关联表,且使用事务。
- 使用
@Transactional注解标注的方法都是事务。
@Autowired
private CategoryBrandRelationService categoryBrandRelationService;
@Override
@Transactional
public void updateDetail(BrandEntity brand) {
//先修改品牌表自身数据
this.updateById(brand);
//调用关联表方法,修改关联表内容
//如果修改品牌内容时,品牌名不为空。则同步修改关联表内容。
if(!StringUtils.isEmpty(brand.getName())) {
categoryBrandRelationService.updataBrand(brand.getBrandId(), brand.getName());
}
}
2.关联表更新内容
- 根据品牌id更新所有品牌id的品牌名。
@Override
public void updataBrand(Long brandId, String name) {
CategoryBrandRelationEntity entity = new CategoryBrandRelationEntity();
entity.setBrandId(brandId);
entity.setBrandName(name);
this.update(entity,new UpdateWrapper<CategoryBrandRelationEntity>().eq("brand_id",brandId));
}
3.开启事务注解
- 使用@EnableTransactionManagement注解开启事务功能。
@Configuration
@EnableTransactionManagement //开启事务功能,标注事务注解就能生效
@MapperScan("com.atguigu.gulimall.product.dao")
public class MybatisConfig {
//引入分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(1500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}

浙公网安备 33010602011771号