瑞吉外卖项目后台系统开发

发布于:2022-11-28 ⋅ 阅读:(510) ⋅ 点赞:(0)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

目录

文章目录

一、开发环境的搭建

1.数据库环境的搭建

2.maven项目搭建

  3.导入pom文件

4.在java/com/itheima/reggie目录下创建springboot主配置文件ReggieApplication

 5.在resources文件下创建application.yml配置文件

6.导入前端文件

二.后台登录系统开发

1.在java/com/itheima/reggie目录下创建项目中需要使用到的包:

2. 实体类,Mapper,service,Impl的开发

3.封装返回的结果类R

4.Controller

三.退出功能开发

四.完善登陆功能

五.员工管理模块的开发

1.新增员工

2.员工信息分页展示和根据名字进行查询

3.禁用和启动员工账号

注意:测试的时候我们发现出现了问题,就是我们修改员工的状态,提示信息显示修改成功,但是我们去数据库查验证的时候,发现员工的状态码压根就没有变化,这是为什么呢?

4.点击修改按钮的时候数据回显

六.分类管理模块开发

1.公共字段填充

2.分类管理的分页展示

3.分类管理的新增菜品分类(菜品分类和套餐分类请求路径一样)

4.根据ids删除菜品分类或者套餐分类

5.修改菜品分类或者套餐分类

七.菜品管理模块的开发

1.菜品的分页展示和根据name进行查询

2.新增菜品

3.起售和停售菜品

4.菜品删除

5.菜品的修改

八.套餐管理模块的开发

1.分页展示套餐数据,并且根据名字查询

2.新建套餐

3.套餐停售或者起售

4.套餐删除 

5.套餐的修改

后台系统开发完成。



前言

瑞吉外卖后台管理系统主要基于springboot和mybtis-plus框架技术,用于实现后台的账号登录和退出,以及对员工添加,饭店菜品,菜品套餐的管理,下面就来详细的分享项目的编写过程。

一、开发环境的搭建

1.数据库环境的搭建

        (1)创建数据库

           字符集和排序规则可以不用选,直接确定

        (2)运行sql语句

        

 鼠标单击表,然后鼠标右键选择运行sql文件

 这个sql文件在b站中的课程资料中下载,推荐使用百度网盘

2.maven项目搭建

说明:该项目是通过idea将maven项目转换成springboot项目,具体的转换步骤包括6步:

其中pom文件中有4步:

1、在pom文件中添加parent父级依赖

2、在pom文件中添加spring-boot-starter核心依赖和测试依赖

3、在pom文件中添加properties属性配置

4、在pom文件中添加build打包插件配置

其他两步也很重要:

5、在reggie文件下创建springboot启动类ReggieApplication,上面要有注解@ApplicationContext(springboot主配置文件)

6、在resources文件下创建application.yml配置文件 

  上面是maven转换springboot项目的讲解,接下来我们进行操作

   1.创建一个maven项目

          然后next就可以了

  3.导入pom文件

pom文件是资料自带的,直接复制。上面的四个在pom文件导入的步骤也包含在这里面

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
 
    <groupId>com.itheima</groupId>
    <artifactId>reggie_take_out</artifactId>
    <version>1.0-SNAPSHOT</version>
 
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
 
    <dependencies>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>compile</scope>
        </dependency>
 
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
 
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>
 
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
 
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
 
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
 
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.23</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>
 
</project>

4.在java/com/itheima/reggie目录下创建springboot主配置文件ReggieApplication

package com.itheima.reggie;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
/**
 * @author LJM
 * @create 2022/4/14
 */
@Slf4j
@SpringBootApplication
@ServletComponentScan
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
        log.info("项目启动成功...");
    }
}

  @ServletComponentScan跟后面的过滤器有关,用来扫描那些过滤器注解,从而把过滤器创建出来

 5.在resources文件下创建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&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      username: root
      password: root
mybatis-plus:
  configuration:
    #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    # 把SQL的查询的过程输出到控制台
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID

然后在ReggieApplication中运行springboot项目,看能否成功运行

开发环境准备完成。

6.导入前端文件

 将这两个前端资源直接复制到resources文件下

前端资源导入之后, 因为在springboot项目中,网页默认就只能访问 resource目录下的static和template文件夹下的文件,无法访问到backend和front文件夹,可以自己去试一下,因此,我们需要加入一个配置类(也叫做静态资源映射):WebMvcConfig,大家在reggie目录下创建config文件,然后在资料中导入该配置类,该配置类的作用就是让网页访问到backend和front这两个前端文件夹;

开发环境搭建完成。

二.后台登录系统开发

登录页面 :瑞吉外卖管理端

按f12 打开控制台,再点击登录就可以看到登录请求信息:http://localhost:8080/employee/login

 所以我们需要创建employee相关的Controller :EmployeeController,在controller中进行功能实现

在此之前,我们需要做好准备工作:

1.在java/com/itheima/reggie目录下创建项目中需要使用到的包:

common包(用来存放共同使用的类)

config包(放配置类)

mapper包

service包,service包中的放service实现类的Impl包

controller包

entity(放实体类)

filter(放过滤器)

dto(这个在菜品和套餐那块会用到,主要也是放实体类)

2. 实体类,Mapper,service,Impl的开发

(1)在entity导入实体类Employee类;

(2)使用mybatis-plus提供的功能,自动生成EmployeeMapper

package com.itheima.reggie.mapper;
 
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Employee;
import org.apache.ibatis.annotations.Mapper;
 
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}

(3)EmployeeService

package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Employee;
 
public interface EmployeeService extends IService<Employee> {
    
}

(4)EmployeeServiceImpl

package com.itheima.reggie.service.impl;
 
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.Employee;
import com.itheima.reggie.mapper.EmployeeMapper;
import org.springframework.stereotype.Service;
 
/**
 * @author LJM
 * @create 2022/4/15
 */
@Service						//这两个泛型一个是实体类对应的mapper,一个是实体类
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper,Employee> implements EmployeeService {
 
}

3.封装返回的结果类R

把这个返回结果类R放入common包中,服务端Controller响应的数据最终都会封装成此对象R

这个是资料自带的,直接复制到Common包下

package com.itheima.reggie.common;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
 * 通用返回结果类,服务端响应的数据最终都会封装成此对象
 * @param <T>
 */
@Data
public class R<T> {
 
    private Integer code; //编码:1成功,0和其它数字为失败
    private String msg; //错误信息
    private T data; //数据
    private Map map = new HashMap(); //动态数据
 
    public static <T> R<T> success(T object) {
        R<T> r = new R<T>();
        r.data = object;
        r.code = 1;
        return r;
    }
 
    public static <T> R<T> error(String msg) {
        R 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;
    }
}

4.Controller

先把模块搭建好 

package com.itheima.reggie.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.itheima.reggie.common.R;
import com.itheima.reggie.entity.Employee;
import com.itheima.reggie.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
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;


@RestController
@Slf4j
@RequestMapping("/employee")
public class EmployeeController {
 
    @PostMapping("/login") //使用restful风格开发
    public R<Employee> login()
    }
}

先处理业务逻辑,然后再编码

1、将页面提交的密码password进行md5加密处理
2、根据页面提交的用户名username查询数据库
3、如果没有查询到则返回登录失败结果
4、密码比对,如果不一致则返回登录失败结果
5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
6、登录成功,将员工id存入Session并返回登录成功结果
package com.itheima.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.reggie.common.R;
import com.itheima.reggie.entity.Employee;
import com.itheima.reggie.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;

@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
    @Autowired
    private EmployeeService employeeService;

    @PostMapping("/login")
    public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){
        //1、从前端用户登录拿到的用户密码,并且对用户密码password进行md5加密处理
        String password = employee.getPassword();
        password=DigestUtils.md5DigestAsHex(password.getBytes());

        //2、创建条件构造器,根据页面提交的用户名username查询数据库,看数据库中的信息与用户输入的是否一致
        LambdaQueryWrapper<Employee> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername,employee.getUsername());

        //在设计数据库的时候我们对username使用了唯一索引,所以这里可以使用getOne方法对username设置唯一索引
        Employee emp = employeeService.getOne(queryWrapper);

        //3、如果没有查询到则返回登录失败结果
        if(emp==null){
            return R.error("用户不存在");
        }

        //4、密码比对,如果不一致则返回登录失败结果
        if(!emp.getPassword().equals(password)){
            return R.error("密码不正确");
        }

        //5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
        if (emp.getStatus()==0){
            return R.error("账号已禁用");
        }
        //6、登录成功,将员工id存入Session并返回登录成功结果
        request.getSession().setAttribute("employee",emp.getId());
        return R.success(emp);
    }

三.退出功能开发

登录之后,按f12,点击右上角退出功能键,发现退出功能的请求路径是employee/logout,请求方法是Post

   /**
     * 退出功能
     *    1.在controller中创建对应的处理方法来接受前端的请求,请求方式为post;
     *    2.清理session中的用户id
     *    3.返回结果(前端页面会进行跳转到登录页面)
     */

    @PostMapping("/logout")
    public R<String> logout(HttpServletRequest request){
        request.getSession().removeAttribute("employee");
        return R.success("退出成功");
    }

四.完善登陆功能

问题分析:登录存在一个问题,就是在员工还没有进行登录的情况下,如果直接访问后台管理系统是可以直接访问到的,意思就是直接跳过了登录过程,

这样是不合理的。

解决思路:所以我们需要设置一个过滤器,过滤器来判断用户是否登录,没有登录的话就跳转到登录页面。

解决方法:1.在主配置文件上加入注解@ServletComponentScan

                  2.在filter包下创建过滤器LoginCheckFilter,就可以完成功能

package com.itheima.reggie.filter;

import com.alibaba.fastjson.JSON;
import com.itheima.reggie.common.BaseContext;
import com.itheima.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 检查用户是否已经完成登录
 */
@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;

        //1、获取本次请求的URI
        String requestURI = request.getRequestURI();// /backend/index.html

        log.info("拦截到请求:{}",requestURI);

        //定义不需要处理的请求路径
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };


        //2、判断本次请求是否需要处理
        boolean check = check(urls, requestURI);

        //3、如果不需要处理,则直接放行
        if(check){
            log.info("本次请求{}不需要处理",requestURI);
            filterChain.doFilter(request,response);
            return;
        }

        //4、判断登录状态,如果已登录,则直接放行
        if(request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));

            Long empId = (Long) request.getSession().getAttribute("employee");
            BaseContext.setCurrentId(empId);
            filterChain.doFilter(request,response);
            return;
        }

        log.info("用户未登录");
        //5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
        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;
    }
}

五.员工管理模块的开发

1.新增员工

业务逻辑:

1.对新增的员工设置初始化密码123456,需要进行md5加密处理,后续员工可以直接修改密码

2.设置员工的创造时间和更新时间(这是Employee实体类中的属性,需要设置)

3.获得当前登录用户的id

4.设置创造人和更新人(这个也是Employee中的属性,新增员工的话都要设置)

5.调用新增方法,返回封装类

    /**
     * 新增员工
     * @return
     */
    @PostMapping
    public R<String> save(HttpServletRequest request, @RequestBody Employee employee){
        //对新增的员工设置初始化密码123456,需要进行md5加密处理,后续员工可以直接修改密码
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));

        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        //获得当前登录用户的id
        Long empId = (Long) request.getSession().getAttribute("employee");
        employee.setCreateUser(empId);
        employee.setUpdateUser(empId);

        //新增方法save
        employeeService.save(employee);

        return R.success("新增成功");
    }

注意:但是因为我们把username设置为唯一索引,所以下次再新增用户名相同的时候,就会出现异常,这个异常是MySQL数据库抛出来的,所以我们需要对这个异常进行捕获处理

 我们使用全局异常捕获的处理方式:

在common包中导入全局异常捕获的处理方法

package com.itheima.reggie.common;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.sql.SQLIntegrityConstraintViolationException;

/**
 * 全局异常处理
 * 用来处理:添加已经存在的账号名的情况,这种情况会抛出异常并显示:账号名已存在
 */
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

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

        if(ex.getMessage().contains("Duplicate entry")){
            String[] split = ex.getMessage().split(" ");
            String msg = split[2] + "账号名已存在";
            return R.error(msg);
        }

        return R.error("未知错误");
    }


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

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

}

2.员工信息分页展示和根据名字进行查询

开发分页查询功能之前,现在config包下导入mybatis-plus的分页插件,配置好分页插件后就可以开发分页功能了(资料中自带)

package com.itheima.reggie.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置mybatis-plus的分页插件
 * 配置好分页插件后就可以去服务端开发分页功能了
 */
@Configuration
public class MybatisPlusConfig {

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

}

信息分页展示和根据名字进行查询业务逻辑:

1.创建分页构造器对象

2.创建条件构造器

3.添加条件,根据员工name进行模糊查询

4.添加条件,根据员工信息更新时间进行降序排序

5.调用employeeService中的page方法,并且将pageInfo和queryWrapper传到page方法中

6.返回封装类

/**
     * 员工分页展示和根据名字进行查询
     */

    @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
        //创建分页对象构造器
        Page pageInfo=new Page(page,pageSize);
        //创建条件构造器
        LambdaQueryWrapper<Employee> queryWrapper=new LambdaQueryWrapper<>();
        //根据条件name进行模糊查询,并且name不能为空
        queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
        //根据更新时间的条件进行降序排序
        queryWrapper.orderByDesc(Employee::getUpdateTime);

        employeeService.page(pageInfo,queryWrapper);

        return R.success(pageInfo);
    }

3.禁用和启动员工账号

业务逻辑:

1.设置员工信息的更新时间

2.通过request获取员工id

3.通过员工id设置更新人

4.调用employeeService中的更新方法,updateById进行更新(ememployeeService继承了Iservice,所以updateById方法是Iservice中的方法)

5.返回封装类

    @PutMapping
    public R<String> update(HttpServletRequest request,@RequestBody Employee employee){


        Long empId =(Long) request.getSession().getAttribute("employee");

        employee.setUpdateTime(LocalDateTime.now());
        employee.setUpdateUser(empId);

        employeeService.updateById(employee);

        return R.success("修改成功");
    }

注意:测试的时候我们发现出现了问题,就是我们修改员工的状态,提示信息显示修改成功,但是我们去数据库查验证的时候,发现员工的状态码压根就没有变化,这是为什么呢?

原因是:mybatis-plus对id使用了雪花算法,所以存入数据库中的id是19为长度,但是前端的js只能保证数据的前16位的数据的精度,对我们id后面三位数据进行了四舍五入,所以就出现了精度丢失;就会出现前端传过来的id和数据里面的id不匹配,就没办法正确的修改到我们想要的数据;

解决思路:既然js对long型的数据会进行精度丢失,那么我们就对数据进行转型,我们可以在服务端Controller给页面响应json格式的数据时进行处理,将long型的数据统一转换为string字符串;

解决办法:

步骤一:在common包下自定义消息转换类:JacksonObjectMapper(资料中有,直接复制)

步骤二:在前面的WebMvcConfig 配置类中扩展spring mvc 的消息转换器,在此消息转换器中使用spring提供的对象转换器进行Java对象到json数据的转换;

    /**
     * 扩展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);
    }

4.点击修改按钮的时候数据回显

业务逻辑:

点击修改的时候,前端页面会传过来的员工 id,我们通过这个员工id进行数据的回显

1.通过employeeService.getById(id).var,获得员工对象Employee

2.判断员工对象是否存在,如果存在,则返回封装类

    /**
     * 根据前端页面传过来的员工id查询数据库进行数据回显给前端
     */

    @GetMapping("/{id}")
    public R<Employee> getById(@PathVariable Long id){
        Employee employee = employeeService.getById(id);
        if (employee!=null){
            return R.success(employee);
        }

        return R.error("没有查询到员工信息");
    }

员工管理模块完成。

六.分类管理模块开发

1.公共字段填充

问题分析:

 功能实现:

1.在Employee实体类的四个相关属性上加入@TableFiled注解,指定自动填充

2.按照框架要求编写对象处理器,在此类中统一为公共字段赋值

在common包下编写对象处理器MyMetaObjecthandler

package com.itheima.reggie.common;

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;

/**
 * 自定义元数据对象处理器
 */
@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", BaseContext.getCurrentId());
        metaObject.setValue("updateUser", BaseContext.getCurrentId());
    }

    /**
     * 更新操作,自动填充
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("公共字段自动填充[update]...");
        log.info(metaObject.toString());

        long id = Thread.currentThread().getId();
        log.info("线程id为:{}",id);

        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("updateUser", BaseContext.getCurrentId());
    }
}

 之后就不需要再手动设置这四个属性了,mybatis-plus会帮我们自动填充。

 功能完善

 

 实现步骤:BaseContext工具类用于保存和获取当前登录用户id

2.分类管理的分页展示

1.在entity包中导入实体类Category 

2.在mapper,service,service中的Impl,controller包中分别创建好相应内容

3.在CategoryController中@Autowired自动注入categoryService

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

    /**
     * 分页功能的开发
     */

    @GetMapping("/page")
    public R<Page> page(int page,int pageSize){
        //分页构造器
        Page<Category> categoryPage=new Page<>(page,pageSize);

        //条件构造器
        LambdaQueryWrapper<Category> queryWrapper=new LambdaQueryWrapper<>();

        //根据sort升序条件进行排序
        queryWrapper.orderByAsc(Category::getSort);

        categoryService.page(categoryPage,queryWrapper);
        return R.success(categoryPage);

    }
}

3.分类管理的新增菜品分类(菜品分类和套餐分类请求路径一样)

 代码逻辑:

直接用Iservice中的save方法进行新增菜品分类

save是mybatis-plus自带的新增功能方法,也就是IService中新增功能的方法,save(实体类)

   /**
     * 新增菜品
     * save是mybatis-plus自带的新增功能方法,也就是IService中新增功能的方法,save(实体类)
     */
    @PostMapping
    public R<String> save(@RequestBody Category category){
        categoryService.save(category);
        return R.success("新增成功");
    }

4.根据ids删除菜品分类或者套餐分类

代码逻辑:

由请求路径可知:参数前面不需要加@PathVariable注解,因为ids在请求路径后面

分类管理中的删除直接通过IService中的removeById()的方法进行删除  

 @DeleteMapping()
    public R<String> delete( Long ids){
        categoryService.removeById(ids);
       return R.success("删除成功");
    }

5.修改菜品分类或者套餐分类

 代码逻辑:

直接使用Iservice中的updateById进行修改

    /**
     * 修改菜品或套餐
     */

    @PutMapping
    public R<String> update(@RequestBody Category category){

        categoryService.updateById(category);
        return R.success("修改成功");
    }

七.菜品管理模块的开发

1.菜品的分页展示和根据name进行查询

1.将Dish实体类导入entity中,并且创建好相对应的Mapper(@Mapper),Service,Impl(@Service),Controller(@RestController,@RequestMapping(“/dish”))

2.将实体类DishDto导入到entity包中,这个实体类继承了Dish实体类,并且包含了CategoryName这个属性,这个属性在菜品分页展示中的菜品分类这一栏数据会要用到

3.在DishController中注入DishService

@Autowired
    private DishService dishService;

4.模块搭建好

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

    @Autowired
    private DishService dishService;
 
    @GetMapping("/page")
    public R<Page> page( int page, int pageSize, String name){
        

    }

代码逻辑:

1.创建分页构造器

2.创建条件构造器

3.添加条件

4.调用dishService中的Iservice中的方法page,并且将分页构造器和条件构造器传到page方法的参数中去

5.返回封装类

 @GetMapping("/page")
    public R<Page> page( int page, int pageSize, String name){
        //对象构造器
        Page<Dish> dishPage= new Page<>(page,pageSize);
        //条件构造器
        LambdaQueryWrapper<Dish> queryWrapper=new LambdaQueryWrapper<>();
        //添加条件
        queryWrapper.like(StringUtils.isNotEmpty(name),Dish::getName,name);
        queryWrapper.orderByDesc(Dish::getUpdateTime);
        dishService.page(dishPage,queryWrapper);
        
        return R.success(dishpage);
    }

代码优化:

启动项目后可以发现,菜品管理分页中出现了菜品信息,但是菜品分类这一栏没有信息,原因是菜品分类这一栏需要的参数是CategoryName,

而Dish中传替的参数是CategoryId,所以菜品分类这一栏没有信息;

解决办法:

使用实体类DishDto

代码逻辑:

1.在分页构造器那里添加一个分页构造器,里面使用DishDto对象

2.使用BeanUtils进行对象拷贝,将dishPage中的信息拷贝到dishDtoPage,但是忽略records这个属性

3.通过  dishPage.getRecords获取到之前的records信息,并且返回的是一个list集合:List<Dish> records = dishPage.getRecords();

4.需要将List<Dish>  Dish的list集合转换成  list<DishDto>  DishDto的list集合,这里使用stream流的方式进行处理:

List<DishDto> list ==records.stream.map((item)->){                其中的item就是Dish对象

}

5.处理完之后,使用dishdto分页构造器来注入records属性:dishDtoPage.setRecords(list);

       /**
     * 分页展示菜品管理
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")
    public R<Page> page( int page, int pageSize, String name){
        //对象构造器
        Page<Dish> dishPage= new Page<>(page,pageSize);
        Page<DishDto> dishDtoPage = new Page<>(page,pageSize);
        //条件构造器
        LambdaQueryWrapper<Dish> queryWrapper=new LambdaQueryWrapper<>();
        //添加条件
        queryWrapper.like(StringUtils.isNotEmpty(name),Dish::getName,name);
        queryWrapper.orderByDesc(Dish::getUpdateTime);
        dishService.page(dishPage,queryWrapper);
        //对象拷贝
        BeanUtils.copyProperties(dishPage,dishDtoPage,"records");
        //
        List<Dish> records = dishPage.getRecords();

        List<DishDto> list = records.stream().map((item) -> {
            DishDto dishDto = new DishDto();

            BeanUtils.copyProperties(item,dishDto);

            Long categoryId = item.getCategoryId();//分类id
            //根据id查询分类对象
            Category category = categoryService.getById(categoryId);

            if(category != null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }
            return dishDto;
        }).collect(Collectors.toList());

        dishDtoPage.setRecords(list);

        return R.success(dishDtoPage);
    }

2.新增菜品

(1)菜品分类下拉框

 新增菜品的请求路径是category/list   

请求方式是Get

这里的type查数据库后可以知道type=1,意思是类型是菜品

 代码实现:

   /**
     * 获得新增菜品中的菜品分类信息
     */

    @GetMapping("/list")
    private R<List<Category>> list( Category category){
        //条件构造器
        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);
    }

(2)新增菜品

新增菜品的时候因为要上传图片,所以:

1.yml配置文件:配置上传图片的存储位置;

reggie:
  path: D:\img\

2.在资料中导入一个负责图片文件上传和下载的controller(CommonController)

package com.itheima.reggie.controller;

import com.itheima.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;

/**
 * 文件上传和下载
 */
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {

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

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

        //原始文件名
        String originalFilename = file.getOriginalFilename();//abc.jpg
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));

        //使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
        String fileName = UUID.randomUUID().toString() + suffix;//dfsdfdfd.jpg

        //创建一个目录对象
        File dir = new File(basePath);
        //判断当前目录是否存在
        if(!dir.exists()){
            //目录不存在,需要创建
            dir.mkdirs();
        }

        try {
            //将临时文件转存到指定位置
            file.transferTo(new File(basePath + fileName));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return R.success(fileName);
    }

    /**
     * 文件下载
     * @param name
     * @param response
     */
    @GetMapping("/download")
    public void download(String name, HttpServletResponse response){

        try {
            //输入流,通过输入流读取文件内容
            FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));

            //输出流,通过输出流将文件写回浏览器
            ServletOutputStream outputStream = response.getOutputStream();

            response.setContentType("image/jpeg");

            int len = 0;
            byte[] bytes = new byte[1024];
            while ((len = fileInputStream.read(bytes)) != -1){
                outputStream.write(bytes,0,len);
                outputStream.flush();
            }

            //关闭资源
            outputStream.close();
            fileInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

因为菜品的添加涉及到了菜品口味,所以菜品添加涉及到了dish和dish-flavor两张表

 所以我们需要在Service中创建一个添加菜品的方法并且在Impl中进行实现,最后在Controller中直接调用这个方法。

service

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.Dish;


public interface DishService extends IService<Dish> {
    void saveWithFlavor(DishDto dishDto);

}

 Impl

@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
    @Autowired
    private DishFlavorService dishFlaovrService;

    @Autowired
    private DishService dishService;


    @Override
    @Transactional
    public void saveWithFlavor(DishDto dishDto) {
        //添加菜品的基本信息到菜品表dish
        this.save(dishDto);

        Long dishId = dishDto.getId();//菜品id

        //绑定菜品setDishId,添加保存菜品口味
        List<DishFlavor> flavors = dishDto.getFlavors();
        flavors = flavors.stream().map((item) -> {
            item.setDishId(dishId);
            return item;
        }).collect(Collectors.toList());

        //保存菜品口味数据到菜品口味表dish_flavor
        dishFlaovrService.saveBatch(flavors);

    }

controller

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

    @Autowired
    private DishService dishService;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private DishFlavorService dishFlavorService;

    /**
     * 新增菜品,包括菜品和菜品口味
     */
    @PostMapping
    public R<String> save(@RequestBody DishDto dishDto){
        dishService.saveWithFlavor(dishDto);
        return R.success("新增菜品成功");
    }

}

3.起售和停售菜品

 起售和停售在菜品管理中算是最简单的了

代码逻辑:

1.先根据ids获取对应的dish对象: dishService.getById(ids).var

2.然后在对dish做判断,判断是否为空

3.如果不为空,那么就更新status状态

    /**
     * 停售和起售
     * @param status
     * @param ids
     * @return
     */
    @PostMapping("/status/{status}")
    public R<String> update(@PathVariable Integer status,Long ids){
        //先根据ids获取对应的dish对象:  dishService.getById(ids).var
        Dish dish = dishService.getById(ids);
        //然后在对dish做判断,判断是否为空
        if (dish != null){
            //如果不为空,那么就更新status状态
            dish.setStatus(status);
            dishService.updateById(dish);
            return R.success("状态更新");
        }

        return R.error("菜品不存在");

    }

4.菜品删除

先自定义一个业务异常类CustomException,因为这里的删除功能需要抛异常了(菜品管理和套餐管理功能模块的删除功能需要抛异常;因为删除之前需要进行判断,如果达到了删除条件,比如status==0,那么就可以成功删除,否则抛异常,并且抛出相应的中文提示信息)

package com.itheima.reggie.common;

/**
 * 自定义业务异常类
 */
public class CustomException extends RuntimeException {
    public CustomException(String message){
        super(message);
    }
}

然后在前面写的GlobalExceptionHandler全局异常捕获器中添加该异常CostomException,这样就可以把相关的异常信息显示给前端页面操作的人员看见(在捕获器中添加了该异常,那么就可以抛出相关的中文提示的异常信息)

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

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

代码逻辑:

1.在Service中创建一个删除的方法

2.在Impl中实现这个方法

3.在Controller中调用这个方法

Service

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.Dish;


public interface DishService extends IService<Dish> {
 
    void deleteWithFlavor(Long ids);
}

Impl

    @Override
    @Transactional
    public void deleteWithFlavor(Long ids) {
        //删除菜品
        Dish dish = dishService.getById(ids);
        Integer status = dish.getStatus();
        if (status != 1) {
            this.removeById(ids);
        } else {
            throw new CustomException("菜品正在售卖,不能删除");
        }

    }

Controller

    /**
     * 删除菜品
     * @param ids
     * @return
     */
    @DeleteMapping
    public R<String> delete(Long ids){
        dishService.deleteWithFlavor(ids);
        return R.success("删除成功");

    }

总结一下(重要):我们在项目中有id和ids的请求就只有这三种情况,这三种情况对应的controller中的使用的注解和参数不一样,要学会区分

1. 


    /**
     * 根据id进行回显
     */

    @GetMapping("/{id}")
    public R<DishDto> get(@PathVariable Long id){

    }

2.

   /**
     * 停售和起售
     * @param status
     * @param ids
     * @return
     */
    @PostMapping("/status/{status}")
    public R<String> update(@PathVariable Integer status,Long ids){


    }

3.这一种因为ids在路径后面,所以都不需要@PathVariable注解或者@PathParam注解,参数

    /**
     * 删除菜品
     * @param ids
     * @return
     */
    @DeleteMapping
    public R<String> delete(Long ids){
       
    }

5.菜品的修改

(1)根据id回显菜品和菜品口味信息

 代码逻辑:

1.在Impl中,先用 this.getById(id).var  返回Dish,获取菜品信息(Dish dish = this.getById(id);)

2.接下来使用条件构造器来添加条件,获取菜品对应的菜品口味,并且将菜品口味返回成一个list集合

3.new DishDto对象,并且先把Dish对象拷贝到DishDto对象里面去:

DishDto dishDto = new DishDto();

BeanUtils.copyProperties(dish, dishDto);

4.使用dishdto中的设置口味的方法,把菜品口味的list集合也放到dishdto对象里去

5.最后返回dishdto,在controller中调用方法

Service

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.Dish;


public interface DishService extends IService<Dish> {
    DishDto getByIdWithFlavor(Long id);

}

Impl



    @Override
    public DishDto getByIdWithFlavor(Long id) {

        //获取菜品信息,根据id获取
        Dish dish = this.getById(id);

        //条件构造器
        LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
        //添加条件获得菜品对应口味
        queryWrapper.eq(DishFlavor::getDishId, dish.getId());
        List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);

        //把查到的数据放到DishDto对象里面去
        DishDto dishDto = new DishDto();

        BeanUtils.copyProperties(dish, dishDto);
        dishDto.setFlavors(flavors);
        return dishDto;

    }

Controller

    /**
     * 根据id进行回显
     */

    @GetMapping("/{id}")
    public R<DishDto> get(@PathVariable Long id){

        DishDto dishDto = dishService.getByIdWithFlavor(id);

        return R.success(dishDto);
    }

(2)将修改数据进行保存

 代码逻辑:

在Service中创建保存修改数据的方法,在Impl中实现

在Impl中:1.通过 this.updateById更新菜品的基本信息

                  2.创造菜品口味的条件构造器

                  3.添加条件,获取菜品对应的菜品口味

                  4.将菜品对应的菜品口味remove删除

                  5.添加用户提交的口味数据

                  6.使用stream流的方式保存口味

Service

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.Dish;


public interface DishService extends IService<Dish> {

    void updateWithFlavor(DishDto dishDto);

}

Impl

    @Override
    @Transactional
    public void updateWithFlavor(DishDto dishDto) {

        //更新菜品基本信息
        this.updateById(dishDto);

        LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
        //添加条件,先删除口味,再让用户重新添加口味数据
        queryWrapper.eq(DishFlavor::getDishId, dishDto.getId());
        dishFlavorService.remove(queryWrapper);
        //添加当前用户提交的口味数据
        List<DishFlavor> flavors = dishDto.getFlavors();

        //口味保存操作
        flavors = flavors.stream().map((item) -> {
            item.setDishId(dishDto.getId());
            return item;
        }).collect(Collectors.toList());

        dishFlavorService.saveBatch(flavors);

    }

Controller

    /**
     * 菜品更新后的保存功能
     * @param dishDto
     * @return
     */
    @PutMapping
    public R<String> update(@RequestBody DishDto dishDto){
        dishService.updateWithFlavor(dishDto);
        return R.success("菜品信息更新成功");
    }

八.套餐管理模块的开发

1.分页展示套餐数据,并且根据名字查询

 代码逻辑:

1.请求方式为Get,请求路径为setmeal/page

所以先在entity包中导入实体类Setmeal,然后在Mapper,Service,Impl,Controller中创建好相对应的数据

Controller

@RestController
@RequestMapping("/setmeal")
public class SetmealController {

    @Autowired
    private SetmealService setmealService;

    /**
     * 套餐信息分页查询
     */
    @GetMapping("/page")
    public R<Page> page(int page, int pageSize, String name){

    }

}

2.创建好数据后,在controller中创建分页构造器

3.创建条件构造器

4.添加条件,根据name进行查询,并且name不为null;根据套餐的更新时间进行降序排序

5.调用Iservice中的Page方法,将分页构造器和条件构造器对象作为参数传进去

@Slf4j
@RestController
@RequestMapping("/setmeal")
public class SetmealController {

    @Autowired
    private SetmealService setmealService;

    @Autowired
    private CategoryService categoryService;

    /**
     * 套餐信息分页查询
     */
    @GetMapping("/page")
    public R<Page> page(int page, int pageSize, String name){
        //分页对象构造器
        Page<Setmeal> pageInfo=new Page<>(page,pageSize);
        Page<SetmealDto> dtoPage = new Page<>();
        //条件构造器
        LambdaQueryWrapper<Setmeal> queryWrapper=new LambdaQueryWrapper<>();
        //添加条件
        queryWrapper.like(name!=null,Setmeal::getName,name);
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);
        setmealService.page(pageInfo,queryWrapper);

        return R.success(pageInfo);
    }

代码优化:

启动项目后,发现分页可以正常显示,但是套餐管理中的套餐分类这一栏没有数据,这是为什么呢?

因为套餐分类需要的参数是CategoryName,而传替的是Setmeal中的CategoryId参数,所以没有数据

解决方法:

1.在dto包中导入SetmealDto,这个是一个实体类,继承了Setmeal,并且有CategoryName属性

2.导入之后在分页构造器那里再创造一个SetmealDto的分页构造器

3.然后在Iservice调用page方法的后面,进行拷贝(BeanUtils.copyProperties),但是忽略records这个属性

4.使用 Setmeal的分页构造器对象 pageInfo ,pageInfo.getRecords().var  获取到records,返回的是一个list集合

(List<Setmeal> records=pageInfo.getRecords())

5.接着使用stream流的方式,将List<Setmeal> 转换成 List<SetmealDto>  :

List<SetmealDto> list= records.stream.map((item)->{

})

6.在stream流中new一个setmealDto对象,将records拷贝到setmealDto中,并且根据Setmeal中的CategoryId,获取到套餐分类对象category,再根据category.getName获取套餐分类名字

7.在stream流里面处理完后,要set到setmeaDto的分页构造器中

8.最后返回setmealDto的分页构造器对象就可以了

@Slf4j
@RestController
@RequestMapping("/setmeal")
public class SetmealController {

    @Autowired
    private SetmealService setmealService;

    @Autowired
    private CategoryService categoryService;

    /**
     * 套餐信息分页查询
     */
    @GetMapping("/page")
    public R<Page> page(int page, int pageSize, String name){
        //分页对象构造器
        Page<Setmeal> pageInfo=new Page<>(page,pageSize);
        Page<SetmealDto> dtoPage = new Page<>();

        //条件构造器
        LambdaQueryWrapper<Setmeal> queryWrapper=new LambdaQueryWrapper<>();
        //添加条件
        queryWrapper.like(name!=null,Setmeal::getName,name);
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);
        setmealService.page(pageInfo,queryWrapper);


        //对象拷贝,将对象构造器的dishPage中的数据拷贝到dishDtoPage中,除了"records"不拷贝
        //BeanUtils.copyProperties(pageInfo,dtoPage,"records");
        //获取records的list集合对象 (Page中的 protected List<T> records;)
       // List<Setmeal> records = pageInfo.getRecords();
        //但是上面的list集合对象的list泛型是 List<Setmeal> ,我们想要的泛型是List<SetmealDto>,
        // 可以通过上面的泛型为<Setmeal>的records进行处理,处理完之后赋给泛型为<SetmealDto>的
        // List<SetmealDto> list 就可以了

     /*   List<SetmealDto> list = records.stream().map((item) -> {
            SetmealDto setmealDto = new SetmealDto();
            //对象拷贝
            BeanUtils.copyProperties(item,setmealDto);
            //分类id
            Long categoryId = item.getCategoryId();
            //根据分类id查询分类对象
            Category category = categoryService.getById(categoryId);
            if(category != null){
                //分类名称
                String categoryName = category.getName();
                setmealDto.setCategoryName(categoryName);
            }
            return setmealDto;
        }).collect(Collectors.toList());

        dtoPage.setRecords(list);*/

        BeanUtils.copyProperties(pageInfo,dtoPage,"records");
        List<Setmeal> records = pageInfo.getRecords();

        List<SetmealDto> list=records.stream().map((item)->{
            SetmealDto setmealDto=new SetmealDto();

            BeanUtils.copyProperties(item,setmealDto);

            Long categoryId = item.getCategoryId();
            Category category = categoryService.getById(categoryId);

            if (category !=null){
                String categoryName = category.getName();
                setmealDto.setCategoryName(categoryName);
            }
            return setmealDto;
        }).collect(Collectors.toList());

        dtoPage.setRecords(list);

        return R.success(dtoPage);
    }

2.新建套餐

 业务逻辑:

新增套餐包含了套餐的基本信息,还有套餐的菜品信息,所以新建套餐涉及到了两个表的操作

 代码逻辑:

跟之前一样,在Service中创建新增套餐的方法,然后在Impl中实现,最后在Controller中调用这个方法并且返回封装类

方法中的参数也为SetmealDto,因为SetmealDto继承了Setmeal,并且有SetmealDish(套餐的菜品)的信息

Service:

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.Setmeal;


public interface SetmealService extends IService<Setmeal> {
    void saveWithDish(SetmealDto setmealDto);
}

Impl:

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

    @Autowired
    private SetmealDishService setmealDishService;

    @Autowired
    private SetmealService setmealService;

    @Override
    @Transactional
    public void saveWithDish(SetmealDto setmealDto) {
        //新增套餐的基本信息
        this.save(setmealDto);
        //新增套餐的菜品
        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
        setmealDishes=setmealDishes.stream().map((item)->{
            //套餐菜品绑定设置对应的套餐id
            item.setSetmealId(setmealDto.getId());
            return item;
        }).collect(Collectors.toList());

        setmealDishService.saveBatch(setmealDishes);

/*        //新增套餐的基本信息 :this.save
        this.save(setmealDto);

        //套餐中的菜品,通过stream流的方式来处理
        List<SetmealDish> setmealDishes=setmealDto.getSetmealDishes();

        //在保存setmealDishes 集合之前,该list集合中的 setmealId 还没有赋上值,所以需要遍历setmealDishes集合,然后将该属性赋上值
        //这里可以使用stream流的方式来给该 setmealDishes集合中的 setmealId 赋上值
        //或者说套餐菜品绑定对应的套餐id

        setmealDishes=setmealDishes.stream().map((item) ->{
            item.setSetmealId(setmealDto.getId());     // setmealDto.getId 是该新增套餐的id
            return item;
        }).collect(Collectors.toList());

        //保存套餐中的菜品到 setmeal_dish表:  setmealDishService.saveBatch
        setmealDishService.saveBatch(setmealDishes);*/


    }

Controller

    /**
     * 新增套餐
     * @param setmealDto
     * @return
     */
    @PostMapping
    public R<String> save(@RequestBody SetmealDto setmealDto){

        setmealService.saveWithDish(setmealDto);
        return R.success("新增成功");
    }

3.套餐停售或者起售

 因为ids是在请求路径外面,根据上面说过的三种情况,我们可以做出判断这是哪一种情况

    /**
     * 更改套餐状态
     * @param status
     * @param ids
     * @return
     */
    @PostMapping("/status/{status}")
    public R<String> status(@PathVariable Integer status, Long ids){
        //根据ids获取Setmeal对象
        Setmeal setmeal = setmealService.getById(ids);
        //进行判断
        if (setmeal!=null){
            setmeal.setStatus(status);
            setmealService.updateById(setmeal);
            return R.success("状态更新成功");
        }
        return R.error("对象不存在");

    }

4.套餐删除 

ids在请求路径外面,所以我们可以判断这是第三种情况

代码逻辑:

在Service中创建删除套餐的方法,在Impl中实现,最后在Controller中调用这个方法,并返回封装类

Impl中的代码逻辑:

先获取Setmeal对象,获取到对象后使用Setmeal中的属性status 用getStatus的方法获取到当前套餐的status信息(是起售或者停售)

如果是停售,那么可以删除,如果是起售中,那么就抛异常

Service

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.Setmeal;


public interface SetmealService extends IService<Setmeal> {
    void deleteWithDish(Long ids);
}

Impl

    @Override
    public void deleteWithDish(Long ids) {
        Setmeal setmeal = setmealService.getById(ids);
        Integer status = setmeal.getStatus();
        if (status == 0) {
            this.removeById(ids);
        } else {
            throw new CustomException("套餐还在售卖,无法删除");
        }

    }

Controller

  @DeleteMapping
    public R<String> delete( Long ids){
        setmealService.deleteWithDish(ids);
        return R.success("删除成功");
    }

5.套餐的修改

(1)套餐信息的回显

 这个id在请求路径里面

义务逻辑:

这个套餐的回显,回显了套餐的基本信息,而且回显了套餐的菜品,涉及到了两个表

所以还是在Service中创建回显套餐方法,在Impl中实现,最后在Controller中调用这个方法,并返回封装类

Service



public interface SetmealService extends IService<Setmeal> {
 
    SetmealDto getWithDish(Long id);

}

Impl

@Override
    public SetmealDto getWithDish(Long id) {
        //通过id获取套餐基本信息
        Setmeal setmeal = this.getById(id);
        //获取跟套餐关联的菜品信息
        //条件构造器
        LambdaQueryWrapper<SetmealDish> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(SetmealDish::getSetmealId,setmeal.getId());
        List<SetmealDish> setmealDishes = setmealDishService.list(queryWrapper);

        //将Setmeal中获取到的信息复制到SetmealDto中
        SetmealDto setmealDto=new SetmealDto();

        BeanUtils.copyProperties(setmeal,setmealDto);
        //将跟套餐关联的菜品信息set到setmealDto中
        setmealDto.setSetmealDishes(setmealDishes);

        return setmealDto;
    }

Controller

  /**
     * 修改套餐->回显套餐信息
     */

    @GetMapping("/{id}")
    public R<SetmealDto> get(@PathVariable Long id){

        SetmealDto setmealDto = setmealService.getWithDish(id);
        return R.success(setmealDto);
    }

(2)修改套餐的保存

在Service中创建套餐修改保存的方法,在Impl中实现,最后在Controller中调用这个方法,并返回封装类

代码逻辑:

更新修改的套餐基本信息(this.updateById())

创建条件构造器,先获取菜品对应的套餐id,在删除掉之前的菜品,删除掉之前的菜品后,获取用户新添加的套餐菜品

然后使用stream流的方式将新添加的套餐菜品绑定设置当前的套餐id,最后将套餐的菜品信息保存到套餐菜品sql表中

Service

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.Setmeal;


public interface SetmealService extends IService<Setmeal> {
    void saveWithDish(SetmealDto setmealDto);

    SetmealDto getWithDish(Long id);

    void updateWithDish(SetmealDto setmealDto);

    void deleteWithDish(Long ids);
}

Impl

    @Override
    public void updateWithDish(SetmealDto setmealDto) {
        //更新套餐基本信息
        this.updateById(setmealDto);

        //删除原来的菜品
        LambdaQueryWrapper<SetmealDish> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(SetmealDish::getSetmealId,setmealDto.getId());
        setmealDishService.remove(queryWrapper);

        //获取更新后的套餐中的菜品集合
        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
        //通过stream流的方式来遍历setmealDishes集合并且获取setmealId
        setmealDishes=setmealDishes.stream().map((item)->{
            item.setSetmealId(setmealDto.getId());
            return item;
        }).collect(Collectors.toList());

        setmealDishService.saveBatch(setmealDishes);
    }

Controller

   /**
     * 修改套餐->保存修改的套餐信息
     */

    @PutMapping
    public R<String> update(@RequestBody SetmealDto setmealDto){
        setmealService.updateWithDish(setmealDto);
        return R.success("修改成功");
    }

后台系统开发完成。

本文含有隐藏内容,请 开通VIP 后查看