文章目录
前言
这部分内容第一部分字段自动填充功能和最后的删除功能需要特别关注,都使用了一些特殊的技术处理,中间三部分开发就比较常规。
blog编辑之前,我们先来看一下我们需要完成分类管理业务界面的功能情况。
可以看出,界面展示的功能中,我们需要完成的功能有新增菜品,套餐,修改,删除菜品,以及菜品展示分页开发等功能。接下来我们就将这几个功能逐一进行开发。
1. 公共字段自动填充
1.1 需求分析
前面我们完成了员工管理功能开发,在新增员工时需要设置创建时间,更新时间,创建人,修改人等字段,然而,这些字段属于公共字段,在很多表中都存在,我们就在想,能不能对这些公共字段在某个地方进行统一处理呢?
这个答案就是使用MybatisPlus提供的公共字段自动填充
1.2 代码实现
-
在实体类的属性上加入@TableField注解,指定自动填充策略
-
按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口。
/**
* 公共字段自动填充
*/
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//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", BaseContent.getCurrentId());
metaObject.setValue("updateUser", BaseContent.getCurrentId());
}
@Override
public void updateFill(MetaObject metaObject) {
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser", BaseContent.getCurrentId());
}
}
这里我们重点解释一下如何获取当前修改人的id。
首先,有小伙伴可能会想,之前我们不是已经将用户的id放入session了吗,我们从session里面获取不就行了吗???
注意,我们在MyMetaObjectHandler 这个类上是无法获得Session的,所以我们得另谋出路。
这里,我们可以使用ThreadLocal来解决此问题,该类是JDK提供的一个类。
在学习使用ThreadLocal之前,我们需要明白,就是客户端发送每次Http请求,都会在对应的服务端分配一个新的线程来处理,在处理过程中涉及下面类中的方法都是属于同一个线程:
- LoginCheckFilter中的doFilter方法
- EmployeeController的save方法
- MyMetaObjectHandler 的updateFill方法
我们可以在上面三段代码加入日志信息在控制台打印一下
可以发现,都是线程41这个线程。
这里我们简要介绍一下ThreadLocal
ok,接下来我们完善一下代码
1.3 代码完善
- 编写BaseContext类,基于ThreadLocal封装的工具类
/**
* 基于ThreadLocal封装工具类,用于保存和获取用户的id
*/
public class BaseContent {
//因为需要存储的id为long型,所以这里指定的泛型为Long
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id){
threadLocal.set(id);
}
public static Long getCurrentId(){
return threadLocal.get();
}
}
- 在LoginCheckFilter中的doFilter方法中调用BadeContext来设置我们的id
// 4. 判断是否登录,登录就放行
//从我们存入的session中获取数据,如果session中有数据,则表明登录成功,放行
HttpSession session = request.getSession();
Object employee = session.getAttribute("employee");
if(employee!=null){
//调用BaseContent的set方法存储id
Long empId = (Long) employee;
BaseContent.setCurrentId(empId);
filterChain.doFilter(request,response);
return;
}
- 在MyMetaObjectHandler 的updateFill方法调用get方法获取id
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//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", BaseContent.getCurrentId());
metaObject.setValue("updateUser", BaseContent.getCurrentId());
}
@Override
public void updateFill(MetaObject metaObject) {
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser", BaseContent.getCurrentId());
}
}
有了公共字段自动填充,我们就可以在接下去开发过程中简化新增和更新功能的开发,加速开发进度。
2. 新增分类
2.1 需求分析
在我们分类管理中有新增菜品和新增套餐两个添加按钮,我们需要通过这两个接口完成相对应的服务端开发。
2.2 数据模型
新增菜品和套餐分类,其实就是将我们在窗口录入的数据查到category这张数据表
要注意,这里我们设置的name是unique,唯一的,因此在插入的时候不能出现重复名称,否则会走我们的全局异常捕获。
2.3 代码开发
- 创建Category实体类
- 编写CategoryDao接口
- 编写CategoryService接口及实现类
- 表现层开发
这里我们只演示表现层CategoryController开发,因为使用MP开发,数据层和业务层目前都比较简单。
我们新建菜品,点击保存,看控制台发出的ajax请求,可以看到,控制台发出了POST请求,并携带了我们输入的参数信息。
再看一下前端新增菜品的方法,可以看到,前端只需要我们响应一个成功的标志,因此我们表现层传递的泛型为String就可以了。
/**
* 添加菜品/套餐
* @param category
* @return
*/
@PostMapping
public R<String> saveFood(@RequestBody Category category){
categoryService.save(category);
return R.success("添加菜品成功");
}
3. 分页功能
3.1 需求分析
当我们系统中分类数据很多时,如果在一个页面全部展示出来会显得比较乱,不便于查看,因此,我们一般会使用分页来进行展示列表数据。我们先来看一下效果。
3.2 代码开发
- 页面发送ajax请求,将分页数据提交到服务端
- 服务端调用service查询数据
- service调用dao操作数据库
- controller将查询到的数据响应给页面进行展示
页面发送的ajax请求中包含了page和pageSize两个参数。并使用Get请求。
这里我们采用表现层调用Service的方法进行开发。
public interface CategoryService extends IService<Category> {
public Page<Category> getPage(int page, int pageSize);
}
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, Category> implements CategoryService {
@Resource
private CategoryDao categoryDao;
@Override
public Page<Category> getPage(int page, int pageSize) {
Page page1 = new Page(page,pageSize);
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//queryWrapper.like(Strings.isNotEmpty(name),Category::getName,name);
queryWrapper.orderByAsc(Category::getSort);
categoryDao.selectPage(page1,queryWrapper);
return page1;
}
}
@GetMapping("/page")
public R<Page> page(int page,int pageSize){
Page<Category> pageInfo = categoryService.getPage(page, pageSize);
return R.success(pageInfo);
}
写了这么多次分页请求,我们应该也能猜到前端页面肯定需要我们后端返回的是一个page对象了。
效果开头看过就不再看了。
4. 修改分类
4.1 需求分析
不想分析,直接上界面,小伙伴们自己分析一下
4.2 代码开发
我们先来看一下页面发送了什么请求
可以看出,页面发送了一个PUT请求,并将页面的数据携带过来,因此,我们不难分析得出,服务端需要使用一个Category对象去接收这个请求,别忘了加@RequestBody
与此同时,我们再分析一下前端界面的代码,就能得出我们需要返回的泛型是什么类型了。
这里我们返回一个Category对象
/**
* 修改功能
* @param category
* @return
*/
@PutMapping
public R<Category> update(@RequestBody Category category){
categoryService.updateById(category);
return R.success(category);
}
ok,修改功能完结,接下来删除功能是重点,好好看,好好学。
5. 删除功能
5.1 需求分析
这里我们得好好分析一下了,因为这个分类表中的某些分类关联了菜品或者套餐,因此需要判断该类是否关联了相关的菜品或者套餐,如果关联,则抛出异常,提示用户该类关联相关菜品或套餐,不能删除。如果没有关联,则可以直接删除。
Category表
Dish菜品表
Setmeal套餐表
我们可以看出,菜品表和套餐表都有Category_id,因此category不能随便删除分类。
5.2 功能开发及完善
要完成这里的删除功能,我们需要准备基础的类和相对应的接口和实现类
- 实体类Dish和Setmeal
- Dao接口DishDao和SetmealDao
- service接口以及对应的实现类
这些类和接口比较简单,这里我们就不做书写。
5.2.1 在CategoryService接口定义删除方法
public interface CategoryService extends IService<Category> {
public Page<Category> getPage(int page, int pageSize);
public void delete(Long id);
}
5.2.2 在CategoryServiceImpl实现类实现删除方法
/**
* 根据id删除分类,删除之前进行判断,如果该分类包含菜品或者套餐,则不能删除
* @param id
*/
@Override
public void delete(Long id) {
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
//添加查询条件,根据菜品表的分类的id与我们传递的id进行比较
dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
int count1 = dishService.count(dishLambdaQueryWrapper);
//查询当前分类是否关联菜品,关联则不能删除,抛出一个业务异常
if(count1>0){
//有关联菜品,不能删除抛出异常
throw new CustomException("当前分类关联菜品不能删除");
}
//查询当前分类是否关联套餐,关联则不能删除,抛出一个业务异常
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
int count2 = setmealService.count(setmealLambdaQueryWrapper);
if(count2>0){
throw new CustomException("当前分类关联套餐不能删除");
}
//没有关联,可以删除该分类
//super和this类似,它指向了当前对象自己的父类型特征(也就是继承过来的那些东西)。
super.removeById(id);
}
5.2.3 定义我们自定义异常类
这里的我们自定义的异常是继承了运行时异常,这里我们定义构造方法接收提示信息。
/**
* 自定义异常,在删除分类中判断是否关联菜品或套餐时使用,如果关联,则抛出该异常
*/
public class CustomException extends RuntimeException {
public CustomException(String message){
super(message);
}
}
5.2.4 在全局异常捕获器中添加我们的自定义异常捕获
/**
* 自定义我们的异常处理器
* @param ex
* @return
*/
@ExceptionHandler(CustomException.class)
public R<String> customExceptionHandler(CustomException ex){
return R.error(ex.getMessage());
}
5.2.5 controller调用service层的delete方法
/**
* 删除功能
* @param id
* @return
*/
@DeleteMapping//("{id}")
public R<String> delete(Long id){
//categoryService.removeById(id);
categoryService.delete(id);
return R.success("删除成功");
}
忙活了这么久,我们看一下运行结果吧。
OK,删除功能开发完毕。
写了两个小时,好累呀,对小伙伴们有帮助记得点赞支持一下啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,博主不容易呀。。。。。。。。。。。。