企业管理系统定制开发【SpringBoot项目】SpringBoot项目-瑞吉外卖【day03】分类管理

文章目录

🌕博客x主页:🌕!
🌎文章说明:SpringBoot项目-瑞吉外卖【day03】分类管理🌎
✅系列专栏:
🌴本篇内容:企业管理系统定制开发对黑马的瑞吉外卖项目的day03企业管理系统定制开发进行笔记和项目实现🌴
☕️每日一语:企业管理系统定制开发生活不可能像你想象得那么好,企业管理系统定制开发但也不会像你想象得那么糟。☕️
🚩 交流社区:(企业管理系统定制开发优质编程社区)

前言

企业管理系统定制开发本次文章对应所属项目的第3天,我在想,企业管理系统定制开发我项目进度到底是快了还是慢了。企业管理系统定制开发这个问题有点深奥,企业管理系统定制开发如果对于官方给的进度,企业管理系统定制开发那我项目肯定是慢了;企业管理系统定制开发但是项目得消化,企业管理系统定制开发不能做完即可,企业管理系统定制开发图个完成任务的心态是不可取的,企业管理系统定制开发所以还是慢慢来吧。

企业管理系统定制开发公共字段自动填充

问题分析

我们在day02企业管理系统定制开发已经对后台的员工管理功能进行了开发,在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段信息,在编辑员工时需要设置修改时间和修改人等字段信息。这些字段都是属于公共字段,也就是很多表中都有的字段,如下所示:


基本每个表都有以上字段,而且我们在每一个需要用到的修改、新增时都用到了这些公共字段。



这些代码十分冗余,没有技术含量,每次都写一遍是不可接受的。那么我们能不能对于这些公共字段做一个统一的处理,以便简化开发,让代码更加美观呢?可以!
MybatisPlus为我们提供了公共字段自动填充功能。
Mybatis Plus公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。
实现步骤:

1、在实体类的属性上加入@TableField注解,指定自动填充的策略


可以看到,我们能在相应的公共字段上,添加@TableField注解,然后在括号里选择方式,最后选择填充策略。填充策略有默认、插入、插入和更新、更新四种。

@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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

以上是公共字段填充,为什么填充策略不同呢?这里解释一下:

因为createTime只有在新建的时候使用,而updateTime在插入的时候就已经算更新了,在后面的更新中当然也算。所以updateTime的策略是插入和更新时填充,同理可以理解其他几个。

2.按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口
新建一个MyMetaObjectHandler:

代码实现

package com.example.commons;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;import lombok.extern.slf4j.Slf4j;import org.apache.ibatis.reflection.MetaObject;import org.springframework.stereotype.Component;/** * 自定义公共字段自动填充 * @author 不止于梦想 * @date 2022/11/15 20:23 */@Component@Slf4jpublic class MyMetaObjectHandler implements MetaObjectHandler {    /**     * insert策略填充     * @param metaObject     */    @Override    public  void insertFill(MetaObject metaObject) {        log.info(metaObject.toString());        log.info("insert填充策略......");    }    /**     * update策略填充     * @param metaObject     */    @Override    public void updateFill(MetaObject metaObject) {        log.info(metaObject.toString());        log.info("update填充策略......");    }}
  • 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

这里没有进行任何填充,先测试一下代码是否能够走通

功能测试

我们在update策略里输出日志并且打上断点,验证我们程序是否能够执行成功。

这是更新策略,所以我们修改员工信息:

可以看到,代码是可以走通的。并且是在UPDATE之前,这就是我们想看到的

功能完善

这里其实把上面没写的代码一并在这里完成,这里原本是解决ThreadLocal问题的,一并解决了吧。
先把update的里面这几句注释掉,现在要用公共字段填充,这些不写了,拜拜勒:

重启项目发送更新请求:

注意看参数,update时间跟我当前时间不符合,说明了现在没有填充时间。下面依次完成需要的填充:

package com.example.commons;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;import lombok.extern.slf4j.Slf4j;import org.apache.ibatis.reflection.MetaObject;import org.springframework.stereotype.Component;import java.time.LocalDateTime;/** * 自定义公共字段自动填充 * @author 不止于梦想 * @date 2022/11/15 20:23 */@Component@Slf4jpublic class MyMetaObjectHandler implements MetaObjectHandler {    /**     * insert策略填充     * @param metaObject     */    @Override    public  void insertFill(MetaObject metaObject) {        metaObject.setValue("createTime",LocalDateTime.now());        log.info("insert填充策略......");    }    /**     * update策略填充     * @param metaObject     */    @Override    public void updateFill(MetaObject metaObject) {        metaObject.setValue("updateTime", LocalDateTime.now());        log.info("update填充策略......");    }}
  • 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

上面这段代码是不完整的,没有设置本次插入或者更新的人的id,我们能不能用session对象设置呢?不行,因为在方法执行的时候,真正的方法压根没有明着调用我们这个公共填充,而一次request请求你也给不了它。

解决办法,首先我们要知道的是一次request请求其实对应的是一次线程,而我们要用到的线程是JDK为我们提供的ThreadLocal.
这里我们先要确认一件事情,就是每当前台发一次http请求,我们后台对应的服务器是不是分配了一个新的线程来处理:

多余解释画蛇添足,下面是官方给的方法,我们可以试试:

在处理过程中涉及到下面类中的方法都属于相同的一个线程:
1、LoginCheckFilter的doFilter方法
2、EmployeeController的update方法
3、MyMetaObjectHandler的updateFill方法
可以在上面的三个方法中分别加入下面代码 (获取当前线程id):
long id Thread. current Thread() getId() :
Log. info(“线程id:1”,id) :
执行编辑员工功能进行验证,通过观察控制台输出可以发现,一次请求对应的线程id是相同的:



可以知道的是一次请求确实是对应一个线程,还得验证一件事情,就是不同请求不是一次线程。再发一次:

既然每次请求对应一个线程,我们不可以共有一个请求,一个线程我们是可以共享的,而且别的请求线程也影响不到你的线程。

介绍ThreadLocal:

还是看一下官方解释:
什么是ThreadLocal?
ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不
能访问。
ThreadLocal常用方法:

public void set(T value):设置当前线程的线程局部变量的值
public T get() :返回当前线程所对应的线程局部变量的值

我们可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaobjectHandler的updateFil方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。

有了步骤咱就整它,打它啊,打它mad!:

实现步骤:
1、编写BaseContext工具类,基于ThreadLocal封装的工具类

package com.example.commons;/** * @author 不止于梦想 * @date 2022/11/15 21:45 */public class BaseContext {    //设置成静态属性    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();    /**     * 设置线程局部变量     * @param id     */    public static void setCurrentId(Long id){        threadLocal.set(id);    }    /**     * 获取线程局部变量的值     * @return     */    public static Long getCurrentId(){        return threadLocal.get();    }}
  • 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

2、在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id

if (httpServletRequest.getSession().getAttribute("employee")!=null) {            log.info("用户已经登录"+httpServletRequest.getSession().getAttribute("employee"));            //获取当前请求的用户id            long empId = (long) httpServletRequest.getSession().getAttribute("employee");           //设置当前线程的线程局部变量的值            BaseContext.setCurrentId(empId);            filterChain.doFilter(httpServletRequest,httpServletResponse);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

只有已经登录过的用户才能获取到对应的id。
3、在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id

 metaObject.setValue("updateUser",BaseContext.getCurrentId());
  • 1

测试:

最后把所有公共字段去掉

新增分类

需求分析


在我们的分类管理中,有两个新增分类,分别是新增菜品分类和新增套餐分类。


新增菜品分类和新增套餐分类其实基本无差别,只是发给后台时的type属性不同。
在这个功能中,我们需要连接前端,并且在后端区分,然后把操作数据存入数据库。


调用了axios、方法是post方法。

只判断了code,所以新的controller应该是String类型。

模型

在这个功能中,我们的数据模型跟前面的不一样了,不再是employee,而是category

两个分类模式,数据其实存入了一张表之中。

需要注意的是这里的name设置了唯一性约束,如果名字重复是会抛出异常的

代码开发

这里直接导入类category:

package com.example.entity;import com.baomidou.mybatisplus.annotation.FieldFill;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableField;import com.baomidou.mybatisplus.annotation.TableId;import lombok.Data;import lombok.Getter;import lombok.Setter;import java.io.Serializable;import java.time.LocalDateTime;/** * 分类 */@Datapublic class Category implements Serializable {    private static final long serialVersionUID = 1L;    private Long id;    //类型 1 菜品分类 2 套餐分类    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;}
  • 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
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

接下来把service、mapper弄好。
然后·写好controller:

package com.example.controller;import com.example.commons.R;import com.example.entity.Category;import com.example.service.CategoryService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @author 不止于梦想 * @date 2022/11/17 18:21 */@RestController@RequestMapping("/category")@Slf4jpublic class CategoryController {    @Autowired    CategoryService categoryService;    /**     * 新增分类     * @param category     * @return     */    @PostMapping    public R<String> save(@RequestBody Category category){        log.info("新增分类");        boolean save = categoryService.save(category);        if(save) {            return R.success("新增分类成功");        }        return R.error("新增分类失败");    }}
  • 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

功能测试



添加分类成功,接口异常是接下来要处理的信息分页查询。

这里还有一个问题就是,如果我们添加的菜品名字一样,会出异常的,因为我们设计表时就已经把name字段设置为非空了。我们测试一下

但是我们不会受到影响,因为我们在前面已经设置了一个全局处理异常“Duplicate entry”

所以会提示我们已经存在。

分类信息分页查询

需求分析

在上面的新增分类,我们已经提到了系统接口404异常,那么这个异常其实就是当我们点击分类管理时,页面就会发送请求去后台查询数据并且返回展示了:

由上图,当我们点击新增分类时,vue就创建了钩子函数,并调用了getCategoryPage方法。并且传入了页码和页码所在页的大小。其实就是一个分页查询,我们在employee时已经做过,所以这里直接跟进getCategoryPage:

细节如图。

代码开发

@GetMapping("/page")    public R<Page> page(int page,int pageSize){        log.info("分页查询");        //构造分页构造器        Page pageInfo = new Page(page,pageSize);        //构造条件构造器,输出时要用到        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();        queryWrapper.orderByAsc(Category::getSort);        //进行分页查询        categoryService.page(pageInfo, queryWrapper);        return R.success(pageInfo);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

功能测试

删除分类

需求分析


可以看到,我们的分类管理后面其实是可以操作的,而这里要介绍的操作就是删除分类。
这里需要注意是当分类关联了菜品或者套餐时,此分类是不允许删除的。这里解释一下,我们这里只是套餐分类,真正的细节并不是存在这个表里的,而是分别存在相应的表中:


如上图,分类表只能表示有没有当前种类和添加种类,删除不归它管理,如果不存在该种类,查询时自然不显示。

代码开发

我们还是先做简单的删除


注意这里通过id删除,但参数传递时是ids

  @DeleteMapping    public R<String> delete(Long ids){        log.info("删除操作......");        categoryService.removeById(ids);        return  R.success("删除成功");    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

功能完善

这里细节就不多说了。
上代码,不过那些需要导入和构建架构的代码就不上了,太水:
CustomExcption:

package com.example.commons;/** * @author 不止于梦想 * @date 2022/11/17 21:00 *//** * 自定义异常 */public class CustomerExcption extends RuntimeException{    /**     * 传入异常信息,交给父类     * @param msg :异常信息     */    public CustomerExcption(String msg){        super(msg);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

CategoryServiceImpl:

package com.example.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.example.commons.CustomerExcption;import com.example.entity.Category;import com.example.entity.Dish;import com.example.entity.Setmeal;import com.example.mapper.CategoryMapper;import com.example.service.CategoryService;import com.example.service.DishService;import com.example.service.SetmealService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;/** * @author 不止于梦想 * @date 2022/11/17 18:19 */@Servicepublic class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {    @Autowired    DishService dishService;    @Autowired    SetmealService setmealService;    /**     * 通过id删除分类,删除之前检查有没有关联套餐(Setmeal)或者菜品(Dish),需要用到这两者的服务,所以在上边进行注入     * @param id     */    @Override    public void remove(Long id) {        //判断是否关联Dish,设置查询条件        LambdaQueryWrapper<Dish> dish = new LambdaQueryWrapper<>();        //菜品分类id        //    private Long categoryId;比较两者id是否相等        //设置条件判断,条件为传入id与Dish表中的属性CategoryId相等        dish.eq(Dish::getCategoryId,id);        //调用dishService服务,查询相等的条数        int count1 = dishService.count(dish);        //如果存在,则说明关联,抛出异常,提示前台        if(count1>0){            throw new CustomerExcption("菜品已被关联,不能删除");        }        //判断是否关联Dish,设置查询条件        LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();        //分类id        //private Long categoryId;        //设置查询条件        setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);        //调用setmealService服务,查询相等的条数        int count2 = setmealService.count(setmealLambdaQueryWrapper);        //如果存在,则说明关联,抛出异常,提示前台        if(count2>0){            throw new CustomerExcption("套餐已被关联,不能删除");        }        //否则,则没有关联,正常关联分类,调用接口的ById方法        super.removeById(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
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

别忘了再最开始的地方更改为你刚修改的方法:

如果还是留着上次的方法,小心数据丢失(悲伤)。
测试:


接下来验证没有关联的能不能删除,隆江猪脚饭是我刚添加的没有关联:

修改分类

需求分析

当我们点击修改时,前端根据id进行查询,并进行了一个回显操作,这里就不细究了,我们可以看到这里可以更新两个信息,名称和排序。

当点击确定时,会把以上信息作为参数进行查询。

参数时id,name,和sort,但是更新时间什么的都会设置,所以这里直接用对象作为参数。返回值是code、请求时put,路径明细如下:

代码实现

@PutMapping    public R<String> update(@RequestBody Category category){        log.info("参数:{}",category.toString());        categoryService.updateById(category);        return R.success("修改成功");    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

验证:

结尾

创作不易,喜欢的给个三连。

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发