一、公共字段自动填充
1.1 问题分析
前面已经完成了后台系统的员工管理功能的开发,在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时需要设置修改时间、修改人等字段。这些字段属于公共字段,也就是也就是在系统中很多表中都会有这些字段,如下:
而针对于这些字段,目前的赋值方式为:
A. 在新增数据时, 将createTime、updateTime 设置为当前时间, createUser、updateUser设置为当前登录用户ID。
B. 在更新数据时, 将updateTime 设置为当前时间, updateUser设置为当前登录用户ID。
如果都按照这种操作方式来处理这些公共字段, 需要在每一个业务方法中进行操作, 编码相对冗余、繁琐,所以可以使用Mybatis Plus提供的公共字段自动填充功能来简化开发。
1.2 思路分析
Mybatis Plus公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。在上述的问题分析中,我们提到有四个公共字段,需要在新增/更新中进行赋值操作, 具体情况如下:
字段名 | 赋值时机 | 说明 |
---|---|---|
createTime | 插入(INSERT) | 当前时间 |
updateTime | 插入(INSERT) , 更新(UPDATE) | 当前时间 |
createUser | 插入(INSERT) | 当前登录用户ID |
updateUser | 插入(INSERT) , 更新(UPDATE) | 当前登录用户ID |
实现步骤:
1、在实体类的属性上加入@TableField注解,指定自动填充的策略。
2、按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口。
1.给实体类添加注解
实体类的属性上加入**@TableField注解**,指定自动填充的策略。
2.编写元数据对象处理器
按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口。
1 | /** |
然后将原来的添加和更新操作中的手动赋值操作注释掉
添加操作
更新操作
3.功能测试
添加功能测试:
添加操作公共字段添加没有问题
更新功能测试:
将飞飞性别改成女
公共字段自动也填充成功
1.3 功能完善
前面完成了公共字段自动填充功能的代码开发,但是还有一个问题没有解决,就是在自动填充createUser和updateUser时设置的用户id是固定值,因为在MyMetaObjectHandler类中是不能直接获得HttpSession对象的,所以需要通过其他方式来获取登录用户id。
1.业务执行流程
客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:
1). LoginCheckFilter的doFilter方法
2). EmployeeController的update方法
3). MyMetaObjectHandler的updateFill方法
在上述类的方法中加入如下代码(获取当前线程ID,并输出):
1 | long id = Thread.currentThread().getId(); |
执行编辑员工功能进行验证,通过观察控制台输出可以发现,一次请求对应的线程id是相同的:
所以可以使用ThreadLocal来进行保存员工的id并填充
2.ThreadLocal介绍
ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问当前线程对应的值。
ThreadLocal常用方法:
A. public void set(T value) : 设置当前线程的线程局部变量的值
B. public T get() : 返回当前线程所对应的线程局部变量的值
C. public void remove() : 删除当前线程所对应的线程局部变量的值
所以我们可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。
如果在后续的操作中, 我们需要在Controller / Service中要使用当前登录用户的ID, 可以直接从ThreadLocal直接获取。
3. 操作步骤
实现步骤:
编写BaseContext工具类,基于ThreadLocal封装的工具类
在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id
4.代码实现:
1、在common包下创建BaseContext工具类
1 | /** |
2、LoginCheckFilter中存放当前登录用户到ThreadLocal
在doFilter方法中, 判定用户是否登录, 如果用户登录, 在放行之前, 获取HttpSession中的登录用户信息, 调用BaseContext的setCurrentId方法将当前登录用户ID存入ThreadLocal。
1 | //将用户id从Session中取出来,存入线程的ThreadLocal变量中 |
3、MyMetaObjectHandler中从ThreadLocal中获取
将之前在代码中固定的当前登录用户1, 修改为动态调用BaseContext中的getCurrentId方法获取当前登录用户ID
5. 功能测试
测试增加/更新员工信息功能, 直接查询数据库数据变更,看看在新增/修改数据时,这些公共字段数据是否能够完成自动填充, 并且看看填充的create_user 及 update_user字段值是不是本地登录用户的ID。
二、新增分类
2.1 需求分析
后台系统中可以管理分类信息,分类包括两种类型,分别是 菜品分类 和 套餐分类 。当我们在后台系统中添加菜品时需要选择一个菜品分类,当我们在后台系统中添加一个套餐时需要选择一个套餐分类,在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐。
在分类管理中,我们新增分类时, 可以选择新增菜品分类(川菜、湘菜、粤菜…), 也可以选择新增套餐分类(营养早餐、超值午餐…)。 在添加套餐的时候, 输入的排序字段, 控制的是移动端套餐列表的展示顺序。
2.2 前端页面分析
在开发代码之前,需要梳理一下整个程序的执行过程:
1). 在页面(backend/page/category/list.html)的新增分类表单中填写数据,点击 “确定” 发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端
2). 服务端Controller接收页面提交的数据并调用Service将数据进行保存
3). Service调用Mapper操作数据库,保存数据
可以看到新增菜品分类和新增套餐分类请求的服务端地址和提交的json数据结构相同,只是type不同,所以服务端只需要提供一个方法统一处理即可:
具体请求信息整理如下:
请求 | 说明 |
---|---|
请求方式 | POST |
请求路径 | /category |
请求参数 | json格式 - {“name”:”川菜”,”type”:”1”,”sort”:2} |
2.3 代码实现
代码实现的具体步骤:
实体类Category
Mapper接口CategoryMapper
业务层接口CategoryService
业务层实现类CategoryServiceImpl
控制层CategoryController
1.实体类Category
在entity包下新建实体类Category
1 |
|
2.Mapper接口
在mapper包下新建Mapper接口CategoryMapper
1 | /** |
3.业务层接口CategoryService
在service包下新建业务层接口CategoryService
1 | /** |
4.业务层实现类CategoryServiceImpl
在service包下的impl包下新建业务层接口CategoryService
1 | /** |
5. 控制层CategoryController
在controller包下新建CategoryController类
1 | /** |
2.4 功能测试
新增分类的代码编写完毕之后, 我们需要重新启动项目,进入管理系统访问分类管理, 然后进行新增分类测试,需要将所有情况都覆盖全,例如:
1). 输入的分类名称不存在
鲁菜添加成功
2). 输入已存在的分类名称
三、分类信息分页查询
3.1 需求分析
系统中的分类很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
3.2 前端页面分析
在开发代码之前,需要梳理一下整个程序的执行过程:
1). 页面发送ajax请求,将分页查询参数(page、pageSize)提交到服务端
2). 服务端Controller接收页面提交的数据并调用Service查询数据
3). Service调用Mapper操作数据库,查询分页数据
4). Controller将查询到的分页数据响应给页面
5). 页面接收到分页数据并通过ElementUI的Table组件展示到页面上
页面加载时,就会触发Vue声明周期的钩子方法,然后执行分页查询,发送异步请求到服务端,前端代码如下:
页面中使用的是ElementUI提供的分页组件进行分页条的展示:
我们通过浏览器,也可以抓取到分页查询的请求信息, 如下:
具体的请求信息整理如下:
请求 | 说明 |
---|---|
请求方式 | GET |
请求路径 | /category/page |
请求参数 | ?page=1&pageSize=10 |
3.3 代码实现
在CategoryController中增加分页查询的方法,在方法中传递分页条件进行查询,并且需要对查询到的结果,安排设置的套餐顺序字段sort进行排序。
1 | /** |
3.4 功能测试
可以正常查询到数据并分页
四、删除分类
4.1 需求分析
在分类管理列表页面,可以对某个分类进行删除操作。需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。
4.2 前端页面分析
在前端页面中,点击 “删除” 按钮,就会触发定义的方法,然后往服务端发送异步请求,并传递参数id,执行删除分类操作。
删除操作的具体执行流程如下:
1). 点击删除,页面发送ajax请求,将参数(id)提交到服务端
2). 服务端Controller接收页面提交的数据并调用Service删除数据
3). Service调用Mapper操作数据库
从上述的分析中,可以得到请求的信息如下:
请求 | 说明 |
---|---|
请求方式 | DELETE |
请求路径 | /category |
请求参数 | ?id=1395291114922618881 |
4.3 代码实现
在CategoryController中增加根据ID删除的方法,在方法中接收页面传递参数id,然后执行删除操作。
1 | /** |
这里前端传来的是ids,可以选择把前端的ids改为id,也可以在后端将id改为ids
3.5 功能完善
在上述的测试中,可以看到分类数据是可以正常删除的。但是并没有检查删除的分类是否关联了菜品或者套餐,所以我们需要进行功能完善。
完善后的逻辑为:
- 根据当前分类的ID,查询该分类下是否存在菜品,如果存在,则提示错误信息
- 根据当前分类的ID,查询该分类下是否存在套餐,如果存在,则提示错误信息
- 执行正常的删除分类操作
1. 准备工作
1. 准备菜品(Dish)及套餐(Setmeal)实体类(课程资料中直接拷贝)放在entity包下
1 | package com.itheima.reggie.entity; |
1 | package com.itheima.reggie.entity; |
2.Mapper接口DishMapper和SetmealMapper
1 |
|
1 |
|
3.Service接口DishService和SetmealService
1 | public interface DishService extends IService<Dish> { |
1 | public interface SetmealService extends IService<Setmeal> { |
4.Service实现类DishServicelmpl和SetmealServicelmpl
1 |
|
1 |
|
2.代码实现
1.在CategoryService中扩展remove方法
1 | public interface CategoryService extends IService<Category> { |
2.在CategoryServiceImpl中实现remove方法
1 |
|
在上述的业务逻辑中,当分类下关联的有菜品或者套餐时,我们在业务代码中抛出了自定义异常,并且在异常中封装了错误提示信息,那这个错误提示信息如何提示给页面呢?
异常抛出之后,会被异常处理器捕获,我们只需要在异常处理器中捕获这一类的异常,然后给页面返回对应的提示信息即可。
3.定义异常类CustomException
1 | public class CustomException extends RuntimeException{ |
4.在全局异常处理器GlobalExceptionHandler添加异常处理
1 | //进行异常处理方法 |
5.改造CategoryController的delete方法
1 | /** |
五、修改分类
5.1 需求分析
在分类管理列表页面点击修改按钮,弹出修改窗口,在修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改操作
5.2 代码实现
1 | //修改分类 |
5.3 功能测试
修改湘菜信息
页面展示正常
SQL语句成功执行
数据库中数据修改成功