分类管理业务
1.编辑员工信息
1.1 需求分析
1.2 代码开发
点击新增员工按钮,F12调试器调试,带add.html的请求url如下:
员工信息查询请求url如下:
整个修改员工信息过程分两步:
一是用员工id查询员工信息,并在编辑页面进行回显;
二是编辑完成后,点击保存按钮,执行update操作。
根据上面的请求url,编写第一步的代码,也就是用id去查询员工信息:
@GetMapping("/{id}")
public R<Employee> getEmpById(@PathVariable Long id){
log.info("根据id查询员工信息");
Employee empbyid = employeeService.getById(id);
if(empbyid!=null){
return R.success(empbyid);
}
return R.error("没有查询到对应员工信息");
}
测试:发现员工信息回显成功。
看前端的js我们会发现,前端调用ajax请求queryEmployeeById后,本质就是把res.data传给ruleForm对象。而sex由于后端传来的是“0”或“1”的标志位,前端要进行特殊处理。我们也可以查看前端的ajax请求queryEmployeeById的代码:
那么下一步需要编写update方法吗?其实已经不需要了,因为我们前面在编写用户启用/禁用功能时,已经写好了update方法,点击保存之后本质上还是调用了那个方法,所以其实现在功能已经完成了。
测试发现编辑员工功能正常运行。
2.公共字段维护
2.1 需求分析
2.2 代码实现
给Employee实体类的几个需要自动填充的属性加上**@TableField注解**。
我们在common包下编写一个类MyMetaObjectHandler,实现MetaObjectHandler接口。
注意要加上**@Component注解**
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时自动填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充[insert]...");
log.info(metaObject.toString());
}
/**
* 更新时自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[update]...");
log.info(metaObject.toString());
}
}
这个类目前没有做任何处理,只是输出日志。
我们将之前增加员工controller的公共字段赋值的语句删去,然后debug后输出:
发现四个公共字段值为null。
重启系统测试后,发现报错,原因是,数据库中这四个字段设置为了非空。
我们在insertFill和updateFill中对字段进行填充,由于在本类中获取不到session,因此我们暂时将createUser和UpdateUser写死。后续完善。
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时自动填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充[insert]...");
log.info(metaObject.toString());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("createUser",new Long(1));
metaObject.setValue("updateUser",new Long(1));
}
/**
* 更新时自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[update]...");
log.info(metaObject.toString());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",new Long(1));
}
}
公共字段维护的好处是,无论是员工,还是菜品,还是订单,只要包含了这几个公共字段,我们都可以自动进行填充。
测试,运行:添加李四员工正常。
但是目前仍然存在问题:createUser和updateUser设置的用户id是固定的值,我们需要改成动态获取当前登录用户的id。
解决方案是使用ThreadLocal给当前线程绑定一个登录用户id值。为什么这样可以呢?原因如下:
我们在登录过滤器、编辑员工controller方法、更新时自动填充方法分别日志打印当前线程id,然后在前端操作编辑员工,输出结果如下:
由此说明,客户端进行一次操作,也就是给服务端发送一个http请求,服务端会分配一个线程来处理,处理过程中涉及到的方法都属于同一个线程!
我们在common包下,新建基于ThreadLocal的封装工具类BaseContext,作用是保存和获取当前线程的用户id
/**
* 基于ThreadLocal的封装工具类,作用是保存和获取当前线程的用户id
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal=new ThreadLocal<>();
public static void setCurrentId(Long id){
threadLocal.set(id);
}
public static Long getCurrentId(){
return threadLocal.get();
}
}
然后在登录拦截器中使用BaseContext设置用户id
然后在公共字段自动填充方法里面获取id。
测试运行,能成功更新createUser和updateUser字段。
3.新增分类
3.1需求分析
数据模型如下:其中type字段为1则为菜品分类,为2则为套餐分类。
注意:name字段数据库设置为了唯一
3.2 代码开发
首先是Category实体类
@Data
public class Category {
private Long id;
private Integer type;
private String name;
private Integer sort;
@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;
private static final long serialVersionUID = 1L;
}
接下来是CategoryMapper(接口)
@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
}
然后是CategoryService(接口)
public interface CategoryService extends IService<Category> {
}
接下来是CategoryServiceImpl
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
}
最后是CategoryController
@RestController
@Slf4j
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
}
架子搭起来了,没有具体功能。
编写新增分类方法:
@PostMapping()
public R<String> addNewCategory(@RequestBody Category category){
if(category!=null){
categoryService.save(category);
return R.success("添加成功");
}else {
return R.error("输入为空,请重新输入");
}
}
测试,成功添加一条分类。
如果此时我们继续添加同名的分类,由于数据库表中添加了分类名称的唯一性约束,所以会进入我们之前写过的全局异常处理类GlobalExceptionHandler,会向我们提示记录已经存在。这也是全局异常处理的好处。
4.分类信息分页查询
4.1 需求分析
4.2 代码开发
为什么每次进入页面就能直接看到数据了呢?看前端代码就知道,钩子函数调用init,再调用getCategoryPage
@GetMapping("/page")
public R<Page> pageQuery(int page,int pageSize){
//分页构造器
Page<Category> pageInfo=new Page<>(page,pageSize);
//条件构造器
LambdaQueryWrapper<Category> queryWrapper=new LambdaQueryWrapper<>();
//添加排序条件,按照sort字段升序排序
queryWrapper.orderByAsc(Category::getSort);
//执行查询
categoryService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
测试:正常运行
为什么我们传给前端的type是数字,而前端能显示中文呢?因为前端做了转换。
5.删除分类
5.1 需求分析
5.2 代码开发
编写controller代码
@DeleteMapping
public R<String> delCategory(Long ids){
log.info("删除的分类id:{}",ids);
categoryService.removeById(ids);
return R.success("分类信息删除成功");
}
测试,删除成功
但是我们这里有问题:当分类已经关联了菜品或者套餐时,此分类不容许删除
接下来建套餐、菜品的实体类、mapper、service
Dish实体类:
@Data
public class Dish {
private Long id;
private String name;
private Long categoryId;
private BigDecimal price;
private String code;
private String image;
private String description;
private Integer status;
private Integer sort;
@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;
private Integer isDeleted;
private static final long serialVersionUID = 1L;
}
Setmeal实体类:
@Data
public class Setmeal {
private Long id;
private Long categoryId;
private String name;
private BigDecimal price;
private Integer status;
private String code;
private String description;
private String image;
@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;
private Integer isDeleted;
private static final long serialVersionUID = 1L;
}
这两个类的mapper、service根据之前的套路建即可。
接下来开始具体实现删除分类业务,我们先在CategoryService中写个remove方法:
public interface CategoryService extends IService<Category> {
/**
* 删除分类
* @param id
*/
public void remove(Long id);
}
然后在CategoryServiceImpl中实现,编写具体逻辑:
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
@Autowired
private DishService dishService;
@Autowired
private SetmealService setmealService;
/**
* 删除分类前先判断
* @param id
*/
@Override
public void remove(Long id) {
//查询当前分类是否关联菜品,如果有,抛出业务异常
LambdaQueryWrapper<Dish> dishQueryWrapper=new LambdaQueryWrapper<>();
//添加查询条件:分类id
dishQueryWrapper.eq(Dish::getCategoryId,id);
int count=dishService.count(dishQueryWrapper);
if(count>0){
//已经关联菜品,抛出业务异常
throw new CustomException("当前分类关联了套餐,不能删除");
}
//查询当前分类是否关联套餐,如果有,抛出业务异常
LambdaQueryWrapper<Setmeal> setmealQueryWrapper=new LambdaQueryWrapper<>();
//添加查询条件:分类id
setmealQueryWrapper.eq(Setmeal::getCategoryId,id);
int count1=setmealService.count(setmealQueryWrapper);
if(count1>0){
//已经关联套餐,抛出业务异常
throw new CustomException("当前分类关联了套餐,不能删除");
}
//正常删除分类
super.removeById(id);
}
}
当我们的当前分类已经关联菜品或者套餐时,则抛出业务异常。我们需要在common新建一个自定义异常类CustomException,继承RuntimeException(运行时异常)
public class CustomException extends RuntimeException{
public CustomException(String message) {
super(message);
}
}
然后我们在全局异常处理类GlobalExceptionHandler中增加方法,通过这样的方式,可以给用户返回具体异常信息。
/**
* 自定义异常处理
* @param ce
* @return
*/
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ce){
log.info(ce.getMessage());
return R.error(ce.getMessage());
}
测试运行,发现关联了套餐的分类点击删除时弹出提示信息,说明功能能正常运行。
6.修改分类
6.1 需求分析
回显操作不用我们自己完成,前端已经完成了。
6.2 代码开发
编写controller
/**
* 根据id修改分类
* @return
*/
@PutMapping
public R<String> updateCategory(@RequestBody Category category){
categoryService.updateById(category);
return R.success("分类修改成功");
}
测试,修改功能正常