黑马瑞吉外卖 基于spring Boot+mybatis-plus

一、瑞吉外卖项目介绍

1、项目背景介绍

本项目(瑞吉外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括系统管理后台和移动端应用两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品、套餐、订单等进行管理维护。移动端应用主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等

本项目供分为3期进行开发
第一期
实现基本需求,其中移动端应用通过H5实现,用户可以通过手机浏览器访问;
第二期
针对移动端应用进行改进,使用微信小程序实现,用户使用起来更加方便;
第三期
针对系统进行优化升级,提高系统的访问性能

2、产品原型介绍

产品原型一款产品成型之前的一个简单框架,就是将页面的排版布局展现出现,使产品的初步构思有一个可视化的展示。通过原型展示,可以更加直观的了解项目的需求和提供的功能

注意事项:产品原型主要用于展示项目的功能,并不是最终的页面效果

技术选型

在这里插入图片描述

3、功能架构

在这里插入图片描述

4、角色

后台系统管理员

后台系统普通员工

  • 登录后台管理系统,对菜品、套餐订单等进行管理;

C端用户

  • 登录移动端应用,可以浏览菜品、添加购物车、设置地址、在线下单等;

二、开发环境搭建

项目架构

在这里插入图片描述

1、数据库

1.1 创建数据库reggie

1.2 导入db_reggie.sql并执行sql

数据表

表名 信息
employee 员工表
category 菜品和套餐分类
dish 菜品表
setmeal 套餐表
setmeal_dish 套餐菜品关系表
dish_flavor 菜品口味关系表
user 用户
address_book 地址簿表
shopping_cart 购物车表
orders 订单表
order_detail 订单明细表

2、构件Maven项目

2.1 新建Maven项目

在这里插入图片描述

2.2 导入jar包

druid jar包异常

在这里插入图片描述


解决:更换druid版本号

	<!--父依赖-->
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.1.4.RELEASE</version>
    </parent>

    <dependencies>

        <!--spring boot启动依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--spring boot test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--web启动依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--MP启动依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--fastJSON-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.1.23</version>
        </dependency>
        <!--commons-lang-->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <!--MysqL-->
        <dependency>
            <groupId>MysqL</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.11</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.6.6</version>
            </plugin>
        </plugins>
    </build>

2.3 编写配置文件

创建application.yml文件

# 端口号
server:
  port: 8080

spring:
  application:
    # 应用名称,选择型配置
    name: reggie_take_out
  # 数据源
  datasource:
    druid:
      driver-class-name: com.MysqL.cj.jdbc.Driver
      url: jdbc:MysqL://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useRSSL=false
      username: root
      password: 123456
# MP配置
mybatis-plus:
  configuration:
    # 数据库映射 驼峰命名 user_name -> userName
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      # 自动生成id
      id-type: assign_id

2.4 导入静态资源 -> 静态资源映射

直接复制粘贴在resources路径下

在这里插入图片描述


设置静态资源映射

@Configuration
@Slf4j
public class WebMvcConfig extends WebMvcConfigurationSupport {

    /**
     * 设置静态资源映射
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始进行静态资源映射");
        registry.addResourceHandler("/backend/**")
                .addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**")
                .addResourceLocations("classpath:/front/");
    }
}

2.5 编写启动类

@SpringBootApplication
@Slf4j
public class ReggieApplication {
    public static void main(String[] args) {
        log.info("项目启动成功!");
        SpringApplication.run(ReggieApplication.class, args);
    }
}

2.6 测试

在这里插入图片描述

三、后台系统开发

1、登录系统

需求分析
通过访问登录页面http://localhost:8080/backend/page/login/login.html,点击登录按钮时,页面会发送请求login以及提交的参数usernamepassword

在这里插入图片描述


在这里插入图片描述

1.1 用户登录

1、创建实体类

Employee

@Data
public class Employee implements Serializable {
    // 序列化id
    private static final long serialVersionUID=1L;
    // 主键
    private Long id;
    // 姓名
    private String name;
    // 用户名
    private String username;
    // 密码
    private String password;
    // 手机号
    private String phone;
    // 性别
    private String sex;
    // 身份证号
    private String idNumber;
    // 状态:0:禁用,1:正常
    private Integer status;
    // 创建时间
    private LocalDateTime createTime;
    // 更新时间
    private LocalDateTime updateTime;
    // 创建人
    private Long createuser;
    // 修改
    private Long updateUser;

}

2、Dao层

EmployeeMapper

@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}

3、Service层

EmployeeService

public interface EmployeeService extends IService<Employee> {
}

EmployeeServiceImpl

@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
}

4、导入通用结果类

由于前端页面需要后端接口返回对应的信息,所以引入R这个通用结果类;

在这里插入图片描述

R

/**
 * 通用结果类
 * @param <T>
 */
@Data
public class R<T> {
    // 状态码
    private Integer code;
    // 错误信息
    private String msg;
    // 数据
    private T data;
    private Map map=new HashMap();

    /**
     * 成功时返回
     * @param object
     * @param <T>
     * @return
     */
    public static <T> R<T> success(T object){
        R<T> r=new R<>();
        r.data=object;
        r.code=1;
        return  r;
    }

    /**
     * 错误时返回
     * @param msg
     * @param <T>
     * @return
     */
    public static <T> R<T> error(String msg){
        R<T> r=new R<>();
        r.msg=msg;
        r.code=0;

        return r;
    }

    public R<T> add(String key,Object value){
        this.map.put(key,value);
        return this;
    }
}

5、Controller

EmployeeController

@RestController
@Slf4j
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;
    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public R<String> login(@RequestBody Employee employee){
        log.info("employee->{}",employee);

        return null;
    }
}

编写具体实现时,我们需要测试前端数据,后端是否已经接收到;
1.在log.info("employee->{}",employee); 打上断点,运行程序;
2.输入http://localhost:8080/backend/page/login/login.html 进入登录界面点击登录页面跳转登录界面,如下所示。

在这里插入图片描述


3.前端登录的账号密码数据已经接收到,可以继续晚上登录方法

处理逻辑如下
1、将页面提交的密码password进行MD5加密处理
2、根据页面提交的用户名username查询数据库
3、如果没有查询则返回登陆失败结果
4、密码对比,如果不一致则返回登录失败结果
5、查看员工状态,如果已禁用,则返回员工已禁用结果
6、登录成功,将员工id存入session并返回登陆成功结果

EmployeeController

@RestController
@Slf4j
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;
    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public R<Employee> login(@RequestBody Employee employee, HttpSession session){
        log.info("employee->{}",employee);
        // 1.获取页面传递的密码并加密处理
        String password = employee.getpassword();
        password= DigestUtils.md5DigestAsHex(password.getBytes());
        // 2.根据页面提交的username查询数据
        // 2.1 创建条件构造器
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        // 2.2 查询条件
        queryWrapper.eq(Employee::getUsername,employee.getUsername());
        // 2.3 查询结果
        Employee emp = employeeService.getone(queryWrapper);

        // 3.如果没有查到就返回登陆失败
        if (emp==null){
            return R.error("登陆失败");
        }
        // 4.密码对比
        if (!emp.getpassword().equals(password)){
            return R.error("登陆失败");
        }
        // 5.查看员工状态是否可以直接登录  0:禁用 1:正常
        if (emp.getStatus() == 0) {
            return R.error("员工已禁用");
        }
        // 6.登陆成功,将员工id存入session
        session.setAttribute("employee",emp.getId());

        return R.success(emp);
    }
}

1.2 用户退出

员工登陆成功后,页面跳转后台系统首页index.html,此时会显示当前用户名,如果员工需要退出系统,直接点击右侧的退出按钮即可退出系统,退出系统后页面应转回登陆页面

在这里插入图片描述


点击后发送logout请求

在这里插入图片描述


代码实现
只需要将当前session里的员工Id清除掉即可,清除后,自动返回index.html页面

在这里插入图片描述

/**
     * 用户退出
     * @return
     */
    @RequestMapping(value = "/logout",method = RequestMethod.POST)
    public R<String> logout(HttpServletRequest request){
        log.info("进入退出功能");
        request.getSession().removeAttribute("employee");

        return R.success("退出成功");
    }

1.3 登录功能完善

问题分析
前面的登录功能虽然已经开发完成,但还存在一个问题:

这种设计并不合理,我们希望看到的效果

解决方拦截

代码实现
1、创建自定义过滤器
2、在启动类上加入注解@ServletComponentScan
3、完善过滤器的处理逻辑

@ServletComponentScan注解解析:

LoginCheckFilter

/**
 * 检查用户是否已经完成登录
 */
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
    // 路径匹配器,支持通配
    public static final AntPathMatcher PATH_MATCHER=new AntPathMatcher();
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, servletexception {

        HttpServletRequest request= (HttpServletRequest) servletRequest;
        HttpServletResponse response= (HttpServletResponse) servletResponse;
        log.info("拦截到请求:{}",request.getRequestURI());
        // 1、获取本次请求的uri
        String requestURI = request.getRequestURI();

        // 不需要处理的请求
        String[] urls=new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };
        // 2、判断本次请求是否需要处理
        boolean check = check(urls, requestURI);
        // 3、如果不需要处理,则直接放行
        if (check){
            log.info("本次请求不需要处理");
            filterChain.doFilter(request,response);
            return;
        }
        // 4、判断登陆状态,如果已登陆,则直接放行
        if (request.getSession().getAttribute("employee")!=null){
            log.info("用户登录");
            filterChain.doFilter(request,response);
            return;
        }

        // 5、如果未登录则返回未登录结果
        log.info("用户登录");
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;
    }

    /**
     * 路径匹配,检查本次请求是否需要放行
     * @param urls
     * @param requestURI
     * @return
     */
    public boolean check(String[] urls,String requestURI){
        for (String url:urls){
            boolean match = PATH_MATCHER.match(url, requestURI);
            if (match){
                return true;
            }
        }
        return false;
    }
}

@L_502_24@2、员工管理

2.1 新增员工

需求分析
后台系统中可以管理员工信息,通过新增员工信息来添加系统用户。点击【添加员工】按钮跳转到新增页面

在这里插入图片描述

代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将新增员工页面输入的数据以json的形式提交到服务端;
2、服务端Controller接收页面提交的数据并调用Service将数据进行保存;
3、Service调用Mapper操作数据库,保存数据。

1.查看新增页面请求url

在这里插入图片描述


2.编写Controller

	@RequestMapping(value = "",method = RequestMethod.POST)
    public R<String> add(@RequestBody Employee employee, HttpServletRequest request){
        log.info("employee=>{}",employee);
        return null;
    }

log.info("employee=>{}",employee);打上断点,debug运行程序,查看页面提交到后端的数据;

3.新增页面输入数据

在这里插入图片描述


4.查看页面提交的数据

在这里插入图片描述


5.完善Controller代码

	/**
     * 新增员工
     * @param employee
     * @param request
     * @return
     */
    @RequestMapping(value = "",method = RequestMethod.POST)
    public R<String> add(@RequestBody Employee employee, HttpServletRequest request){
        log.info("employee=>{}",employee);
        /*
            由于页面提交的属性有限,其他属性还需自己手动添加
            只有name username phone sex idNumber
         */

        // 1.设置初识密码123456(需要md5加密)
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
        // 2.获取当前用户id
        Long employeeId = (Long) request.getSession().getAttribute("employee");
        // 3.设置创建时间
        employee.setCreateTime(LocalDateTime.Now());
        employee.setUpdateTime(LocalDateTime.Now());
        // 4.设置更新时间
        // 5.设置当前用户id
        employee.setcreateuser(employeeId);
        // 6.设置修改用户id
        employee.setUpdateUser(employeeId);

        // 添加用户 Duplicate entry 'zhangsan' for key 'idx_username' username重复会报错
        employeeService.save(employee);

        return R.success("添加成功");
    }

6.测试
由于表中账号字段设置唯一,test表中已经存在,所以报错 500:Duplicate entry 'test' for key 'idx_username' ,只需换一个测试数据,重新输入,后面会对报错进行统一处理。

在这里插入图片描述


再次输入数据提交,测试代码

在这里插入图片描述


7.数据库查看是否新增成功

在这里插入图片描述

全局异常处理

/**
 * 全局异常处理
 */
@Slf4j
@ResponseBody
@ControllerAdvice(annotations = {RestController.class, Controller.class}) // 捕捉异常的范围
public class GlobalExceptionHandler {

    /**
     * 异常处理方法
     * @param  exception : 违反数据库的唯一约束条件
     * @return
     */
    @ExceptionHandler(sqlIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(sqlIntegrityConstraintViolationException exception){
        log.error(exception.getMessage());

        if (exception.getMessage().contains("Duplicate entry")){
            // Duplicate entry 'test' for key 'idx_username'
            String [] error=exception.getMessage().split(" ");
            // 'test'
            return R.error(error[2]+"重复了");
        }
        return R.error("失败了");
    }
}

功能测试
登陆后,添加一个一个已经存在账号名,看前端页面提示信息,以及看后台是否输出了报错日志;

@H_342_3502@

2.2 分页查询员工信息

需求分析

系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。

在这里插入图片描述


代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将分页查询参数page、pageSize、name提交到服务端;
2、服务端Controller接受页面提交的数据并调用Service查询数据;
3、Service调用Mappers操作数据库查询分页数据;
4、Controller将查询到的分页数据响应给页面
5、页面接收到分页数据并通过ElemenUI的Table组件展示到页面上。

1.查看页面请求

在这里插入图片描述


在这里插入图片描述

2.编写后端接口

	@RequestMapping(value = "/page",method = RequestMethod.GET)
    public R<Page> page(int page,int pageSize,String name){
        log.info("page=>{},pageSize=>{},name=>{}",page,pageSize,name);

        return null;
    }

测试前端数据是否可以接收到
首次进入index.html页面

在这里插入图片描述


利用name进行过滤

在这里插入图片描述


3.完善Controller代码

	/**
     * 分页查询员工信息
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @RequestMapping(value = "/page",method = RequestMethod.GET)
    public R<Page<Employee>> page(int page,int pageSize,String name){
        log.info("page=>{},pageSize=>{},name=>{}",page,pageSize,name);
        // 构造分页构造器
        Page<Employee> pageInfo = new Page<>(page, pageSize);

        // 构造条件查询
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        // 添加过滤条件
        queryWrapper.like(!Strings.isEmpty(name),Employee::getName,name);
        // 添加排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);

        // 执行查询
        pageInfo=employeeService.page(pageInfo, queryWrapper);

        return R.success(pageInfo);
    }

再次测试

首次查询

在这里插入图片描述


过滤查询

在这里插入图片描述

注意:无论怎么查询,始终共有0条数据

问题分析

  • 没有创建MybatisPlusInterceptor(MyBatisPlus分页拦截器)实例,导致total值一直为0。

解决方法

  • 创建MybatisPlusInterceptor实例。
/**
 * 配置mybatis-plus提供的分页插件拦截器
 */
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

2.3 启用/禁用员工账号

需求分析
在员工管理列表页面中,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录管理系统,启用后可以正常登录
需要注意的是:只有管理员(admin)才可以对其他普通用户进行启用/禁用操作,所以普通用户登录系统后启用/禁用不显示
并且如果某个员工账号状态为正常,则按钮显示禁用,如果员工账号状态为已禁用,则按钮显示启用

在这里插入图片描述

流程分析
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将参数id、status提交到服务端;
2、服务端Controller接收页面提交的数据并调用Service更新数据;
3、Service调用Mapper操作数据库

代码开发
页面上的展示,前端代码已经处理好了,我们只要处理后端即可。

在这里插入图片描述


在这里插入图片描述


1.查看前端代码的接口

在这里插入图片描述

页面携带了两个参数:

注意:启用/禁用员工账号,本质就是一个更新操作,修改员工状态的方法

2.编写Controller

	@RequestMapping(value = "",method = RequestMethod.PUT)
	public R<String> status(@RequestBody Employee employee){

       log.info("员工状态信息=>{}",employee);

       return null;
   }

在这里插入图片描述


在这里插入图片描述

我们发现,当我们进行debug查询时,发现前端传过来的id和我们数据库中的id不一样
原因是:mybatis-plus 对id 使用了雪花算法,所以存入数据库中的id是19长度,但是前端的js只能保证数据的前16位数据的精度,对我们id后面的3位数据进行四舍五入,所以就出现了精度丢失;
就会出现前端传过来的id和数据库中的id不一致,就无法修改数据库中的信息。

解决方法

自定义消息转换器

由于js对long类型的数据精度会丢失,那么我们就把数据进行转型,我们可以在服务端给页面响应的json格式数据进行处理,将long类型数据统一转换为string字符串;

代码实现
1、提供对象转换器JacksonObjectMapper,基于Jackson进行java对象到json数据的转换
2、在WebMvcConfig配置类中扩展Spring MVC的消息转换器,在此消息转换器中提供的对象转换器进行java对象到json数据的转换

消息转换器

/**
 * 对象映射器:
 * 基于jackson将java对象转为json,或者将json转换为java对象
 * 将java对象生成json的过程称为:【序列化java对象到json】
 * 将json解析为java对象的过程称为:【从json反序列话java对象】
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT="yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT="yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper(){
        super();

        // 收到位置属性时不报异常
        this.configure(DeserializationFeature.FAIL_ON_UNKNowN_PROPERTIES,false);

        // 反序列化时,属性不存在兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNowN_PROPERTIES);

        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance)
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

配置到spring中

/**
     * 扩展mvc框架的消息转换器
     * @param converters
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

        log.info("扩展消息转换器");
        // 创建消息转换器对象
        MappingJackson2HttpMessageConverter messageConverter=new MappingJackson2HttpMessageConverter();
        // 设置对象转换器,底层使用jackson将java对象转为json
        messageConverter.setobjectMapper(new JacksonObjectMapper());
        // 将上面的消息转换器对象追加到mvc框架的消息转换器中
        // 转换器是有优先顺序的,这里我们把自定义的消息转换器设置为第一优先级,优先转换
        converters.add(0,messageConverter);
    }

可以在log.info("扩展消息转换器");打上断点,当我debug启动程序时,观察我们的消息转换器是否被加入

在这里插入图片描述


页面传入的数据从long类型转换为了字符串类型,后端也可以正常接收到id

在这里插入图片描述

在这里插入图片描述


完善Controller代码

/**
     * 根据id修改状态(启用/禁用)
     * @param employee
     * @return
     */
    @RequestMapping(value = "",method = RequestMethod.PUT)
    public R<String> status(@RequestBody Employee employee,HttpServletRequest request){

        log.info("员工状态信息=>{}",employee);
        Long employeeId = (Long) request.getSession().getAttribute("employee");
        employee.setUpdateTime(LocalDateTime.Now());
        employee.setUpdateUser(employeeId);

        employeeService.updateById(employee);
        return R.success("修改状态成功!");
    }

测试

在这里插入图片描述

2.4 编辑员工信息

需求分析
在员工管理列表页面点击编辑按钮,跳转编辑页面,在标记页面辉县员工信息并进行修改,最后点击保存按钮完成编辑操作

在这里插入图片描述


在开发代码之前需要梳理一下操作过程和对应的程序的执行流程:
1、点击编辑按钮时,页面跳转add.html,并在url中携带参数员工id
2、在add.html页面获取url中的参数员工id;
3、发送ajax请求,请求服务端,同时提交员工id参数;
4、服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
5、页面接收服务端响应的json数据,通过vue的数据绑定进行员工信息回显;
6、点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端;
7、服务端接收员工信息,并进行处理,完成后给页面响应;
8、页面接收到服务端响应信息进行相应处理;

代码开发
当我们点击编辑按钮时,页面会发送一个请求来获取员工信息,在add.html进行数据回显

在这里插入图片描述


编写Controller代码

/**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    @RequestMapping(value = "/{id}",method = RequestMethod.GET)
    public R<Employee> getById(@PathVariable("id") Long id){
        log.info("id=>{}",id);
        Employee employee = employeeService.getById(id);
        return R.success(employee);
    }

在这里插入图片描述


修改回显数据后,点击保存,会发送一个update的请求给后端,前面我们已经写了这个update的controller,所以只需要在前端跳转发请求就行;这样就实现了方法的复用,减少了代码量。

在这里插入图片描述


测试

在这里插入图片描述


在这里插入图片描述

3、菜品分类管理

3.1 公共字段填充

前面我们已经完成了后台系统的员工管理功能开发,在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时需要设置修改时间和修改等字段,这些字段属于公共字段,也就是很多表中都有这些字段,如下:

在这里插入图片描述


代码实现
Mybatis Plus 公共字段字段填充,也就是在插入或者更新时为指定字段赋予指定值,使用它的好处就是可以统一对这些字段进行处理,避免重复代码

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

在这里插入图片描述

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

/**
 * 自定义元数据对象管理器
 */
@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", 1L);
        MetaObject.setValue("updateUser",1L);

    }

    /**
     * 更新时填充数据
     * @param MetaObject
     */
    @Override
    public void updateFill(MetaObject MetaObject) {
        log.info("公共字段填充【update】...");
        log.info(MetaObject.toString());
        MetaObject.setValue("updateTime",LocalDateTime.Now());
        MetaObject.setValue("updateUser",1L);
    }
}

测试
测试前,需要将新增和修改设置的属性进行注释

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


修改员工信息

在这里插入图片描述


在这里插入图片描述


功能完善

在这里插入图片描述

在这里插入图片描述


在这里插入图片描述


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

/**
 * 基于ThreadLocal封装工具类,保存和获取当前用户ID
 */
public class BaseContext {

    private static ThreadLocal<Long> threadLocal=new ThreadLocal<>();

    // 保存用户id
    public static void setCurrentId(long id){
        threadLocal.set(id);
    }

    // 获取用户id
    public static Long getCurrentId(){
        return threadLocal.get();
    }
}

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

在这里插入图片描述

3、在MyMetaObjectHandler方法调用BaseContext获取登录用户id

在这里插入图片描述

3.2 新增分类

需求分析

在这里插入图片描述


在这里插入图片描述


数据模型
新增分类,其实就是将我们新增窗口录入的分类数据插入到category表,表结构如下:

在这里插入图片描述


在开发业务功能前,先将需要用到的类和接口基本结构创建好:
实体类Category

/**
 * 分类
 */
@Data
public class Category implements Serializable {
    // 序列化id
    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;

}

Mapper接口CategoryMapper

@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
}

业务层接口CategoryService

public interface CategoryService extends IService<Category> {
}

业务层实现类CategoryeServiceImpl

@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
}

控制层CategoryController

@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
    @Autowired
    private CategoryService categoryService;

}

查看前端请求接口
新增分类和新增套餐共用一个接口,唯一的区别就是type 不一样,1:分类,2:套餐

在这里插入图片描述


在这里插入图片描述


编写Controller

/**
     * 新增分类/套餐
     * @param category
     * @return
     */
    @RequestMapping(value = "",method = RequestMethod.POST)
    public R<String> save(@RequestBody Category category){

        log.info("category=>{}", category);
        categoryService.save(category);
        return R.success("新增分类成功");
    }

测试

在这里插入图片描述


在这里插入图片描述

注意:idx_category_namenameUnique 所以添加重复的name会报错

在这里插入图片描述


在这里插入图片描述

3.3 分类信息分页查询

需求分析
进入分类管理页面分页查询出来所有分类套餐

在这里插入图片描述


代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将分页查询参数page、pageSize提交到服务器
2、服务器Controller接收页面提交的数据并调用Service查询数据
3、Service调用Mapper操作数据库查询分页数据
4、Controller查询到的分页数据响应给页面
5、页面接收分页数据并通过ElementUITable组件展示到页面

页面请求信息

在这里插入图片描述


编写Controller

/**
     * 分页查询分类信息
     * @param page 第几页
     * @param pageSize 页面大小
     * @return
     */
    @RequestMapping(value = "/page",method = RequestMethod.GET)
    public R<Page<Category>> page(int page, int pageSize){
        log.info("page:{},pageSize:{}",page,pageSize);
        // 构造分页构造器
        Page<Category> pageInfo = new Page<>();

        // 构造条件构造器
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
        // 添加排序条件  (根据sort进行正序排列)
        queryWrapper.orderByAsc(Category::getSort);
        // 分页查询
        categoryService.page(pageInfo,queryWrapper);

        return R.success(pageInfo);
    }

测试

在这里插入图片描述

3.4 删除分类

需求分析

在这里插入图片描述


代码开发

在这里插入图片描述

/**
     * 删除分类
     * @param id
     * @return
     */
    @RequestMapping(value = "",method = RequestMethod.DELETE)
    public R<String> delete(@RequestParam("ids") long id){
        log.info("删除分类:id为{}",id);

        categoryService.removeById(id);

        return R.success("删除分类");
    }

注意:这里的删除功能是不完善的,因为可能需要删除的数据是与其他表关联的,所以删除前需要判断该条数据是否关联其他表

功能完善

在这里插入图片描述


dish

/**
 * 菜品类
 */
@Data
public class dish implements Serializable {

    private static final long serialVersionUID=1L;
    // 主键
    private Long id;
    // 菜品名称
    private String name;
    // 菜品分类id
    private Long categoryId;
    // 菜品价格
    private BigDecimal price;
    // 商品码
    private String code;
    // 图片
    private String image;
    // 描述信息
    private String description;
    // 状态 0:停售 1:起售
    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;
}

Setmeal

/**
 * 套餐类
 */
@Data
public class Setmeal implements Serializable {

    private static final long serialVersionUID=1L;
    // 主键
    private Long id;
    // 菜品分类id
    private Long categoryId;
    // 套餐名称
    private String name;
    // 套餐价格
    private BigDecimal price;
    // 状态 0:停用 1:启用
    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;
}

dishMapper

@Mapper
public interface dishMapper extends BaseMapper<dish> {
}

SetmealMapper

@Mapper
public interface SetmealMapper extends BaseMapper<Setmeal> {
}

dishMapperService

public interface dishService extends IService<dish> {
}

SetmealService

public interface SetmealService extends IService<Setmeal> {
}

dishMapperServiceImpl

@Service
public class dishServiceImpl extends ServiceImpl<dishMapper, dish> implements dishService {
}

SetmealServiceImpl

@Service
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {
}

CategoryService添加自定义remove()方法

// 根据id删除分类
   void remove(Long id);

CategoryServiceImpl实现remove()方法,并进行判断

/**
    * 根据id删除分类删除之前要进行判断
    * @param id
    */
   @Override
   public void remove(Long id) {
       LambdaQueryWrapper<dish> dishQueryWrapper = new LambdaQueryWrapper<>();
       // 添加查询条件,根据分类id进行查询
       dishQueryWrapper.eq(dish::getCategoryId,id);
       long count1 = dishService.count(dishQueryWrapper);

       // 查询当前分类是否关联了菜品,如果已关联,抛出一个业务异常
       if (count1>0){
           // 已经关联了菜品,抛出一个业务异常
           throw new CustomException("当前分类下关联了菜品,不能删除");
       }

       // 查询当前分类是否关联了套餐,如果已关联,抛出一个业务异常
       LambdaQueryWrapper<Setmeal> setmealQueryWrapper = new LambdaQueryWrapper<>();
       // 添加查询条件,根据分类id进行查询
       setmealQueryWrapper.eq(Setmeal::getCategoryId,id);
       long count2 = setmealService.count(setmealQueryWrapper);

       if (count1>0){
           // 已经关联了套餐,抛出一个业务异常
           throw new CustomException("当前分类下关联了套餐,不能删除");
       }
       // 正常删除
       super.removeById(id);
   }

自定义异常类CustomException

/**
 * 自定义异常类
 */
public class CustomException extends RuntimeException {

    public CustomException(String message){
        super(message);
    }
}

全局异常捕获GlobalExceptionHandler,将异常信息页面展现出来

/**
     * 异常处理方法
     * @param  exception
     * @return
     */
    @ExceptionHandler(CustomException.class)
    public R<String> exceptionHandler(CustomException exception){
        log.error(exception.getMessage());

        return R.error(exception.getMessage());
    }

CategoryController调用我们自己写的remove()方法

在这里插入图片描述


测试
删除湘菜时,由于关联了数据,所以不能删除并给出错误信息

在这里插入图片描述

3.5 修改分类

需求分析

在这里插入图片描述


页面请求

在这里插入图片描述

/**
     * 根据id修改分类信息
     * @param category
     * @return
     */
    @RequestMapping(value = "",method = RequestMethod.PUT)
    public R<String> update(@RequestBody Category category){
        log.info("修改分类信息:{}",category);

        categoryService.updateById(category);

        return R.success("修改分类信息成功");
    }

4、菜品管理

4.1 文件上传下载

文件上传下载介绍

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


文件下载介绍

在这里插入图片描述


文件上传代码实现

在这里插入图片描述


在这里插入图片描述


启动项目,访问http://localhost:8080/backend/page/demo/upload.html(需要先登录)

页面请求

在这里插入图片描述


在这里插入图片描述


编写CommonController

@RestController
@Slf4j
@RequestMapping("/common")
public class CommonController {
    /**
     * 文件上传
     * @param file
     * @return
     */
    @RequestMapping(value = "/upload",method = RequestMethod.POST)
    public R<String> upload(multipartfile file){
        // file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件删除
        log.info(file.toString());

        return null;
    }
}

注意: file的名字不可更改,否者会为null

log.info(file.toString());打上断点,并运行查看,

在这里插入图片描述


由于file一个临时文件,执行结束后,就会自动删除,所以我们需要将文件保存到指定位置。

# 上传图片存放的位置
reggie:
  path: D:\WorkSpaces\IdeaProject\reggie_take_out\work\

完善文件上传代码

	@Value("${reggie.path}")
    private String basePath;

    /**
     * 文件上传
     * @param file
     * @return
     */
    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public R<String> upload(multipartfile file) {
        // file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件删除
        log.info(file.toString());

        // 获取上传文件名称
        String originalFilename = file.getoriginalFilename();
        // 获取后缀
        val suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        // uuid生成新的文件名称
        String fileName = UUID.randomUUID().toString() + suffix;

        try {
            // 判断文件夹是否存在
            File dir = new File(basePath);
            if (!dir.exists()) {
                // 自动生成文件
                dir.mkdirs();
            }
            // 将临时文件转存到指定位置
            file.transferTo(new File(basePath + fileName));
        } catch (IOException e) {
            e.printstacktrace();
        }

        return R.success(fileName);
    }

文件下载代码实现

在这里插入图片描述


在这里插入图片描述


name为刚才上传图片名称

/**
 * 文件下载
 * @param name
 * @param response
 * @return
 */
@RequestMapping(value = "/download",method = RequestMethod.GET)
public void download(String name, HttpServletResponse response){
    log.info("name:{}",name);

    try {
        // 读取文件
        FileInputStream fis=new FileInputStream(new File(basePath+name));

        // 写文件
        ServletoutputStream os = response.getoutputStream();

        response.setContentType("image/jpeg");

        int len;
        byte[] bytes=new byte[1024];
        while ((len=fis.read(bytes))!=-1){
            os.write(bytes,0,len);
            os.flush();
        }
        // 关闭资源
        os.close();
        fis.close();

    } catch (FileNotFoundException e) {
        e.printstacktrace();
    } catch (IOException e) {
        e.printstacktrace();
    }
}

测试

4.2 新增菜品

需求分析

在这里插入图片描述


数据模型

在这里插入图片描述


代码开发-准备工作

在这里插入图片描述


dishFlavor

/**
 * 菜品口味
 */
@Data
public class dishFlavor implements Serializable {
    // 序列化id
    private static final long serialVersionUID=1L;
    // 主键
    private Long id;
    // 菜品id
    private Long dishId;
    // 口味名字
    private String name;
    // 口味数据
    private String value;
    @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;

}

dishFlavorMapper

@Mapper
public interface dishFlavorMapper extends BaseMapper<dishFlavor> {
}

dishFlavorSerivce

public interface dishFlavorService extends IService<dishFlavor> {
}

dishFlavorServiceImpl

@Service
public class dishFlavorServiceImpl extends ServiceImpl<dishFlavorMapper, dishFlavor> implements dishFlavorService {
}

dishController

@RequestMapping("/dish")
@RestController
@Slf4j
public class dishController {

    @Autowired
    private dishService dishService;

    @Autowired
    private dishFlavorService dishFlavorService;
}

梳理交互过程
在开发代码之前,需要梳理一下新增菜品对前端页面和服务端的交互过程:
1、页面backend/page/food/add.html发送ajax请求,请求服务器获取菜品分类数据并展示到下拉框中
2、页面发送请求进行图片上传,请求服务端将图片保存到服务器
3、页面发送请求进行图片下载,将上传图片进行回显
4、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端

开发新增菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可

点击新增菜品,需要先获取菜品分类的列表

在这里插入图片描述


CategoryController

/**
     * 获取菜品分类列表
     * @param category
     * @return
     */
    @RequestMapping(value = "/list",method = RequestMethod.GET)
    public R<List<Category>> list(Category category){
        log.info("获取菜品分类");
        // 构造条件查询
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
        // 添加条件
        queryWrapper.eq(category.getType()!=null,Category::getType,category.getType());
        // 添加排序条件
        queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);

        List<Category> list = categoryService.list(queryWrapper);
        return R.success(list);
    }

测试

在这里插入图片描述


结果:菜品分类已经可以正常显示出来了

新增菜品

在这里插入图片描述


页面请求

在这里插入图片描述


页面提交的数据设计dishdishFlavor两张表,所以我们需要封装另外一个dishDto来接收数据

DTO:Data Transfer Object 即数据传输对象,一般用于展示层和服务层之间的数据传输。

@Data
public class dishDto extends dish {
    // 封装页面口味等。
    private List<dishFlavor> flavors=new ArrayList<>();

    private String categoryName;

    private Integer copies;
}

dishService

// 新增菜品,同时插入菜品对应的口味数据
    void saveWithFlavor(dishDto dishDto);

dishServiceImpl

/**
     * 新增菜品,同时插入菜品对应的口味数据
     * @param dishDto
     */
    @Override
    @Transactional
    public void saveWithFlavor(dishDto dishDto) {
        // 保存菜品的基本信息
        this.save(dishDto);

        // 保存菜品口味数据到菜品口味表dish_flavor
        Long dishId = dishDto.getId(); // 菜品id
        // 菜品口味
        List<dishFlavor> flavors = dishDto.getFlavors();

        flavors=flavors.stream().map((item)->{
            item.setdishId(dishId);
            return item;
        }).collect(Collectors.toList());

        dishFlavorService.saveBatch(flavors);
    }

添加事务注解,需要在启动类开启注解支持

在这里插入图片描述

dishController

/**
     * 新增菜品
     * @param dishDto
     * @return
     */
    @RequestMapping(value = "", method = RequestMethod.POST)
    public R<String> save(@RequestBody dishDto dishDto) {
        log.info(dishDto.toString());

        dishService.saveWithFlavor(dishDto);
        return R.success("新增菜品成功");
    }

测试

在这里插入图片描述


数据添加dish表中

在这里插入图片描述


数据添加dish_flavor表中

在这里插入图片描述

4.3 菜品类的分页

需求分析

在这里插入图片描述


梳理交互过程

在开发代码之前,需要梳理一下菜品分页查询时前端页面和服务器的交互过程:
1、页面backend/page/food/list.html 发送ajax请求,将分页查询参数page,pageSize,name提交到服务端,获取分页数据
2、页面发送请求,请求服务端进行图片下载,用于页面图片展示

开发菜品信息分页查询功能,其实就是在服务端编写代码去处理前端发送的这2次请求

页面请求

在这里插入图片描述

/**
 * 分页查询菜品信息
 * @param page
 * @param pageSize
 * @param name
 * @return
 */
@RequestMapping(value = "/page",method = RequestMethod.GET)
public R<Page<dish>> page(int page,int pageSize,String name){
    log.info("page=>{},pageSize=>{},name=>{}",page,pageSize,name);
    // 构造分页构造器
    Page<dish> pageInfo = new Page<>(page,pageSize);
    // 构造条件构造器
    LambdaQueryWrapper<dish> queryWrapper = new LambdaQueryWrapper<>();
    // 添加查询条件
    queryWrapper.like(name!=null,dish::getName,name);
    // 添加排序条件
    queryWrapper.orderByDesc(dish::getUpdateTime);

    dishService.page(pageInfo,queryWrapper);

    return R.success(pageInfo);
}

测试

在这里插入图片描述


我们发现,菜品分类信息没有展现出现,因为dish表中只有菜品分类id

完善代码

/**
 * 分页查询菜品信息
 *
 * @param page
 * @param pageSize
 * @param name
 * @return
 */
@RequestMapping(value = "/page", method = RequestMethod.GET)
public R<Page<dishDto>> page(int page, int pageSize, String name) {
    log.info("page=>{},pageSize=>{},name=>{}", page, pageSize, name);
    // 构造分页构造器
    Page<dish> pageInfo = new Page<>(page, pageSize);
    Page<dishDto> dishDtoPage = new Page<>();

    // 构造条件构造器
    LambdaQueryWrapper<dish> queryWrapper = new LambdaQueryWrapper<>();
    // 添加查询条件
    queryWrapper.like(name != null, dish::getName, name);
    // 添加排序条件
    queryWrapper.orderByDesc(dish::getUpdateTime);

    dishService.page(pageInfo, queryWrapper);

    // 对象拷贝
    BeanUtils.copyProperties(pageInfo, dishDtoPage, "records");

    List<dish> records = pageInfo.getRecords();

    List<dishDto> list = records.stream().map((item) -> {
        dishDto dishDto = new dishDto();
        BeanUtils.copyProperties(item, dishDto);
        Long categoryId = item.getCategoryId();// 分类id
        Category category = categoryService.getById(categoryId);
        // 获取分类名称
        String categoryName = category.getName();
        dishDto.setCategoryName(categoryName);
        return dishDto;
    }).collect(Collectors.toList());

    // 设置records
    dishDtoPage.setRecords(list);

    return R.success(dishDtoPage);
}

测试

在这里插入图片描述

4.4 修改菜品

需求分析

在这里插入图片描述


梳理交互过程

在开发代码之前,需要梳理一下修改菜品时前端页面add.html和服务端交互过程:
1、页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示
2、页面发送ajax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显
3、页面发送请求,请求服务端进行图片下载,用于页图片回显
4、点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端

开发修改菜品功能,其实就是在负端编写代码去处理前端页面发送的这4次请求

点击修改按钮,根据id获取菜品信息,进行菜品信息回显
页面请求

在这里插入图片描述


由于设计dishdish_flavor两张表,所以返回值应该为dishDto
dishService

// 根据id查询菜品信息和对应的口味信息
    dishDto getByIdWithFlavor(Long id);

dishServiceImpl

/**
     * 根据id查询菜品信息和对应的口味信息
     * @param id
     * @return
     */
    @Override
    public dishDto getByIdWithFlavor(Long id) {
        // 查询菜品基本信息,从dish表中查询
        dish dish = this.getById(id);

        dishDto dishDto = new dishDto();
        BeanUtils.copyProperties(dish,dishDto);
        // 查询当前菜品信息对应的口味信息,从dish_flavor表查询
        LambdaQueryWrapper<dishFlavor> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dishFlavor::getdishId,dish.getId());
        List<dishFlavor> flavors = dishFlavorService.list(queryWrapper);
        // 设置口味
        dishDto.setFlavors(flavors);

        return dishDto;
    }

dishController

/**
     * 根据id查询菜品信息
     * @param id
     * @return
     */
    @RequestMapping(value = "/{id}",method = RequestMethod.GET)
    public R<dishDto> getById(@PathVariable("id") Long id){
        log.info("根据id获取菜品信息:{}",id);

        dishDto dishDto = dishService.getByIdWithFlavor(id);


        return R.success(dishDto);
    }

测试

在这里插入图片描述


修改完菜品信息后,点击保存按钮,发送请求
页面请求

在这里插入图片描述


携带的参数信息

在这里插入图片描述


dishService

// 更新菜品信息和对应口味信息
void updateWithFlavor(dishDto dishDto);

dishServiceImpl

/**
 * 更新菜品信息和对应口味信息
 * @param dishDto
 */
@Override
@Transactional
public void updateWithFlavor(dishDto dishDto) {
    // 更新dish表基本信息
    this.updateById(dishDto);
    // 清理当前dish_flavor表口味信息 - dish_flavor delete操作
    // delete from dish_flavor where dish_id=?
    LambdaQueryWrapper<dishFlavor> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(dishFlavor::getdishId,dishDto.getId());

    dishFlavorService.remove(queryWrapper);

    // 添加当前提交过来的口味数据 - dish_flavor insert操作
    List<dishFlavor> flavors = dishDto.getFlavors();
    // dish_flavor表中 缺少dish_id
    flavors=flavors.stream().map((item)->{
        item.setdishId(dishDto.getId());
        return item;
    }).collect(Collectors.toList());

    dishFlavorService.saveBatch(flavors);
}

dishController

/**
 * 修改菜品
 * @param dishDto
 * @return
 */
@RequestMapping(value = "", method = RequestMethod.PUT)
public R<String> update(@RequestBody dishDto dishDto) {
    log.info(dishDto.toString());

    dishService.updateWithFlavor(dishDto);
    return R.success("修改菜品成功");
}

测试

重点是dish_flavor表是否修改成功

在这里插入图片描述


口味表

在这里插入图片描述

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...