JAVA项目实战瑞吉外卖—day3
公共字段自动填充功能
Mybatis Plus公共字段自动填充,也就是在插入或者更新的时候位指定字段赋予指定的值,使用它的好处就是对这些字段进行处理,避免重复代码
这种代码设置为自动填充即可
1 2 3 4 5 6 7 8 9 10
| employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
Long empId = (Long) request.getSession().getAttribute("employee");
employee.setCreateUser(empId);
employee.setUpdateUser(empId);
|
实现步骤:
- 在实体类的属性上加入@TableField注解,指定自动填充的策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT) private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser;
|
- 按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,且此类需要实现MetaObjectHandler接口
- 但是实现该接口无法获得HttpSession,从而无法得到用户的id属性,则填充字段的createUser/updateUser暂时设置为死属性
1 2
| metaObject.setValue("createUser", 1L); metaObject.setValue("updateUser", 1L);
|
功能完善之ThreadLocal线程知识补充
使用ThreadLocal来根据线程获得之前存储在Session中的用户id,在执行公共字段填充的时候将用户id值进行填充
在学习ThreadLocal之前,我们需要先确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新 的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:
- 1、LoginCheckFilter的doFilter方法
- 2、EmployeeController的update方法
- 3、MyMetaObjectHandler的updateFill方法
可以在上面的三个方法中分别加入下面代码(获取当前线程id):
1 2
| long id = Thread.currentThread().getId(); log.info("线程id:{}",id);
|
执行编辑员工功能进行验证,通过观察控制台输出可以发现, 一次请求对应的线程id是相同的
什么是ThreadLocal?
- ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该 变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不 能访问。
- ThreadLocal常用方法:
- public void set(T value) 设置当前线程的线程局部变量的值
- public T get( 返回当前线程所对应的线程局部变量的值
- 以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线 变量的值(用户id),然后在MyMetaobjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前 对应的线程局部变量的值(用户id)
实现步骤:获得同线程存储的数据
- 1、编写BaseContext工具类,基于ThreadLocal封装的工具类
- 2、在Login CheckFilter的doFilter方法中调用BaseContex来设置当前登录用户的id
- 3、在MyMetaobjectHandler的方法中调用BaseContex获取登灵用户的id
在拦截器中,同线程下记录用户已登录的id属性值
1 2 3
| Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);
|
在设置公共字段填充的时候,调用登录用户的id属性值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
@Component @Slf4j public class MyMetaObjectHandler implements MetaObjectHandler {
@Override public void insertFill(MetaObject metaObject) {
log.info("公共字段填充[[save]。。。。"); log.info(metaObject.toString());
long id = Thread.currentThread().getId(); log.info("线程id:{}",id);
metaObject.setValue("createTime", LocalDateTime.now()); metaObject.setValue("updateTime", LocalDateTime.now()); metaObject.setValue("createUser", BaseContext.getCurrentId()); metaObject.setValue("updateUser", BaseContext.getCurrentId());
}
@Override public void updateFill(MetaObject metaObject) { log.info("公共字段填充[[update]。。。。"); log.info(metaObject.toString());
long id = Thread.currentThread().getId(); log.info("线程id:{}",id);
metaObject.setValue("updateTime", LocalDateTime.now()); metaObject.setValue("updateUser", BaseContext.getCurrentId()); } }
|
分类管理业务
新增分类
查看前端请求路径,配置对应请求接口即可
1 2 3 4 5 6
| @PostMapping public R<String> save(@RequestBody Category category){ log.info("category:{}",category); categoryService.save(category); return R.success("新增分类成功!"); }
|
分类信息分页查询
注意
需要将实体的isDeleted字段注释或者在category表中添加一个isDeleted字段不然会错误sql
在开发代码之前,需要梳理一下整个程序的执行过程:
- 页面发送ajax请求,将分页查询繁数(page. pagesize)提交到服务端
- 服务端Controller接收页面提交的数据并调用Service查询数据
- Service调用Mapper操作数据库,查询分页数据
- controller将查询到的分页数据响应给页面
- 页面接收到分页数据井通过ElementUI的Table組件展示到页面上
这根员信息分类一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @GetMapping("/page") public R<Page> page(int page,int pageSize){
Page<Category> pageInfo = new Page<>(page,pageSize);
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<Category>();
queryWrapper.orderByAsc(Category::getSort);
categoryService.page(pageInfo,queryWrapper);
return R.success(pageInfo); }
|
删除分类
这里老师没有使用restful风格传参,是使用参数传递的方式进行删除
restful风格删除
将前端接口的参数注释,拼接一个restful Url进行发送请求
1 2 3 4 5 6 7 8
| const deleCategory = (id) => { return $axios({ url: '/category/'+id, method: 'delete', }) }
|
后端在路径参数上获得值需要添加注解@PathVariables
1 2 3 4 5 6
| @DeleteMapping("{id}") public R<String> delete(@PathVariable Long id){ log.info("删除分类id为{}",id); categoryService.removeById(id); return R.success("删除成功"); }
|
传统方式
直接为传参形式即可
1 2 3 4 5 6
| @DeleteMapping public R<String> delete(Long id){ log.info("删除分类id为{}",id); categoryService.removeById(id); return R.success("删除成功"); }
|
功能完善
删除分类是判断当前分类是否关联了菜品或者套装,如果有即不能删除
读取菜品和套餐
判断菜品和套餐的字段categoryId有没有分类的字段
配置自定义异常处理类
将数据库表关联的删除信息都抛出这个异常
1 2 3 4 5 6
| public class CustomException extends RuntimeException{
public CustomException(String message){ super(message); } }
|
配置全局异常处理类
集中处理自定义异常
返回异常信息给前端页面显示
1 2 3 4 5 6 7 8 9
|
@ExceptionHandler(CustomException.class) public R<String> exceptionHandler(@NotNull CustomException ex){ log.error(ex.getMessage()); return R.error(ex.getMessage()); }
|
接口实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
@Override public void remove(Long id) { LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>(); dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>(); setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
long count = dishService.count(dishLambdaQueryWrapper);
long count1 = setmealService.count(setmealLambdaQueryWrapper); if (count>0){ throw new CustomException("当前分类关联了菜品,不能删除"); }
if (count1>0){ throw new CustomException("当前分类关联了套餐,不能删除"); }
super.removeById(id); }
|
修改分类
根据前端请求返回对应接口为PutMapping
在点击修改按钮的时候,实现的数据回显没有查询接口即可实现是因为前端在读取列表数据的时候,进行数据保存,点击修改的时候实际是给模型数据赋值,才能完成数据回显
1 2 3 4 5 6 7
| @PutMapping public R<String> update(@RequestBody Category category){ log.info("修改分类信息:{}",category);
categoryService.updateById(category); return R.success("修改分类信息成功!"); }
|