瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)

一、瑞吉外卖项目介绍

1、项目背景介绍

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

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

2、产品原型介绍

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

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

技术选型
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第1张图片

3、功能架构

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第2张图片

4、角色

后台系统管理员

  • 登录后台管理系统,拥有后台系统中的所有操作权限;

后台系统普通员工

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

C端用户

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

二、开发环境搭建

项目架构
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第3张图片

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项目

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第4张图片

2.2 导入jar包

druid jar包异常
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第5张图片
解决:更换druid版本号

	
    <parent>
        <artifactId>spring-boot-starter-parentartifactId>
        <groupId>org.springframework.bootgroupId>
        <version>2.1.4.RELEASEversion>
    parent>

    <dependencies>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.5.1version>
        dependency>
        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.1.23version>
        dependency>
        
        <dependency>
            <groupId>commons-langgroupId>
            <artifactId>commons-langartifactId>
            <version>2.6version>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druid-spring-boot-starterartifactId>
            <version>1.2.11version>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <version>2.6.6version>
            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路径下
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第6张图片
设置静态资源映射

@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 测试

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第7张图片

三、后台系统开发

1、登录系统

需求分析
通过访问登录页面http://localhost:8080/backend/page/login/login.html,点击登录按钮时,页面会发送请求login以及提交的参数usernamepassword
在这里插入图片描述
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第8张图片

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这个通用结果类;
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第9张图片

R

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

    /**
     * 成功时返回
     * @param object
     * @param 
     * @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 
     * @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 进入登录界面点击登录,页面跳转到登录界面,如下所示。
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第10张图片
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,此时会显示当前用户名,如果员工需要退出系统,直接点击右侧的退出按钮即可退出系统,退出系统后页面应转回登陆页面。
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第11张图片
点击后发送logout请求
在这里插入图片描述
代码实现
只需要将当前session里的员工Id清除掉即可,清除后,自动返回index.html页面
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第12张图片

/**
     * 用户退出
     * @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注解解析:

  • Servlet可以直接通过@WebServlet注解自动注册
  • Filter可以直接通过@WebFilter注解自动注册
  • Listener可以直接通过@WebListener 注解自动注册

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;
    }
}

2、员工管理

2.1 新增员工

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

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

1.查看新增页面请求url
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第13张图片
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.新增页面输入数据
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第14张图片
4.查看页面提交的数据
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第15张图片
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' ,只需换一个测试数据,重新输入,后面会对报错进行统一处理。

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第16张图片
再次输入数据提交,测试代码
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第17张图片
7.数据库查看是否新增成功
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第18张图片

全局异常处理

/**
 * 全局异常处理
 */
@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("失败了");
    }
}

功能测试
登陆后,添加一个一个已经存在账号名,看前端页面提示信息,以及看后台是否输出了报错日志;
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第19张图片

2.2 分页查询员工信息

需求分析

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

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第20张图片
代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将分页查询参数page、pageSize、name提交到服务端;
2、服务端Controller接受页面提交的数据并调用Service查询数据;
3、Service调用Mappers操作数据库,查询分页数据;
4、Controller将查询到的分页数据响应给页面;
5、页面接收到分页数据并通过ElemenUI的Table组件展示到页面上。

1.查看页面请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第21张图片
在这里插入图片描述

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页面
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第22张图片
利用name进行过滤
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第23张图片
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);
    }

再次测试

首次查询
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第24张图片
过滤查询
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第25张图片

注意:无论怎么查询,始终共有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)才可以对其他普通用户进行启用/禁用操作,所以普通用户登录系统后启用/禁用不显示
并且如果某个员工账号状态为正常,则按钮显示为禁用,如果员工账号状态为已禁用,则按钮显示为启用
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第26张图片

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

代码开发
页面上的展示,前端代码已经处理好了,我们只要处理后端即可。
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第27张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第28张图片
1.查看前端代码的接口
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第29张图片

页面携带了两个参数:

  • 当前用户id
  • 当前用户的状态
    瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第30张图片

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

2.编写Controller

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

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

       return null;
   }

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第31张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第32张图片

我们发现,当我们进行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启动程序时,观察我们的消息转换器是否被加入
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第33张图片
页面传入的数据从long类型转换为了字符串类型,后端也可以正常接收到id
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第34张图片

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第35张图片
完善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("修改状态成功!");
    }

测试
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第36张图片

2.4 编辑员工信息

需求分析
在员工管理列表页面点击编辑按钮,跳转到编辑页面,在标记页面辉县员工信息并进行修改,最后点击保存按钮完成编辑操作
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第37张图片
在开发代码之前需要梳理一下操作过程和对应的程序的执行流程:
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进行数据回显
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第38张图片
编写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);
    }

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第39张图片
修改回显数据后,点击保存,会发送一个update的请求给后端,前面我们已经写了这个update的controller,所以只需要在前端跳转发请求就行;这样就实现了方法的复用,减少了代码量。
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第40张图片
测试
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第41张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第42张图片

3、菜品分类管理

3.1 公共字段填充

前面我们已经完成了后台系统的员工管理功能开发,在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时需要设置修改时间和修改人等字段,这些字段属于公共字段,也就是很多表中都有这些字段,如下:
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第43张图片
代码实现
Mybatis Plus 公共字段字段填充,也就是在插入或者更新时为指定字段赋予指定值,使用它的好处就是可以统一对这些字段进行处理,避免重复代码。

实现步骤
1、在实体类的属性上加入@TableField注解,指定自动填充的策略
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第44张图片

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);
    }
}

测试
测试前,需要将新增和修改设置的属性进行注释
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第45张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第46张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第47张图片
修改员工信息
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第48张图片
在这里插入图片描述
功能完善
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第49张图片瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第50张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第51张图片
实现步骤
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
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第52张图片

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

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第53张图片

3.2 新增分类

需求分析
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第54张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第55张图片
数据模型
新增分类,其实就是将我们新增窗口录入的分类数据插入到category表,表结构如下:
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第56张图片
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
实体类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:套餐
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第57张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第58张图片
编写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("新增分类成功");
    }

测试
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第59张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第60张图片

注意:idx_category_namenameUnique 所以添加重复的name会报错
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第61张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第62张图片

3.3 分类信息分页查询

需求分析
进入分类管理页面,分页查询出来所有分类套餐
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第63张图片
代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将分页查询参数page、pageSize提交到服务器
2、服务器Controller接收页面提交的数据并调用Service查询数据
3、Service调用Mapper操作数据库,查询分页数据
4、Controller将查询到的分页数据响应给页面
5、页面接收分页数据并通过ElementUITable组件展示到页面上

页面请求信息
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第64张图片
编写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);
    }

测试
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第65张图片

3.4 删除分类

需求分析
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第66张图片
代码开发
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第67张图片

/**
     * 删除分类
     * @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("删除分类");
    }

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

功能完善
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第68张图片
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()方法
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第69张图片
测试
删除湘菜时,由于关联了数据,所以不能删除并给出错误信息
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第70张图片

3.5 修改分类

需求分析
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第71张图片
页面请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第72张图片

/**
     * 根据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 文件上传下载

文件上传下载介绍
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第73张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第74张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第75张图片
文件下载介绍
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第76张图片
文件上传代码实现
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第77张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第78张图片
启动项目,访问http://localhost:8080/backend/page/demo/upload.html(需要先登录)

页面请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第79张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第80张图片
编写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());打上断点,并运行查看,
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第81张图片
由于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);
    }

文件下载代码实现
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第82张图片
在这里插入图片描述
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();
    }
}

测试
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第83张图片

4.2 新增菜品

需求分析
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第84张图片
数据模型
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第85张图片
代码开发-准备工作

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第86张图片
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次请求即可

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

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第87张图片
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);
    }

测试
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第88张图片
结果:菜品分类已经可以正常显示出来了

新增菜品
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第89张图片
页面请求

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第90张图片
页面提交的数据设计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);
    }

添加事务注解,需要在启动类开启注解支持
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第91张图片

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 菜品类的分页

需求分析
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第92张图片
梳理交互过程

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

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

页面请求

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第93张图片

/**
 * 分页查询菜品信息
 * @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);
}

测试

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第94张图片
我们发现,菜品分类信息没有展现出现,因为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 修改菜品

需求分析
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第95张图片
梳理交互过程

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

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

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

页面请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第96张图片
由于设计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);
    }

测试
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第97张图片
修改完菜品信息后,点击保存按钮,发送请求

页面请求

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第98张图片

携带的参数信息

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第99张图片
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表是否修改成功
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第100张图片
口味表
在这里插入图片描述

4.5 起售与批量起售

需求分析

当我们不需要这个菜品或者这个菜品暂时没有时,我们可以选择停售这个菜品。使得用户可以清晰的了解到这个菜品停售;
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第101张图片

代码开发

页面请求:由于单个和批量属于同一个方法,当我们实现了批量起售或停售时,也就可以实现单个起售/停售了
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第102张图片
DishController

由于status只设计dish表,所以修改起来还是很简单的

/**
     * 批量起售/停售
     * @param status
     * @param ids
     * @return
     */
    @RequestMapping("/status/{status}")
    public R<String> updateStatus(@PathVariable("status") Integer status,@RequestParam("ids") List<Long> ids){
        log.info("修改菜品状态");
        // 构造条件构造器
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(ids!=null,Dish::getId,ids);
        // 批量查询
        List<Dish> dishes = dishService.list(queryWrapper);
        for (Dish dish : dishes) {
            if (dish!=null){
                dish.setStatus(status);
                dishService.updateById(dish);
            }
        }
        return R.success("售卖状态修改成功!");
    }

测试

起售和停售都是同一个方法
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第103张图片

4.6 删除与批量删除

需求分析

当我们已经下架这个菜品时,我们需要删除/批量删除菜品
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第104张图片

代码开发

由于删除操作涉及了dishdish_flavor两张表,所以我们把它写在Service层
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第105张图片
DishService

// 删除菜品信息和对应口味信息
    void deleteWithFlavor(List<Long> ids);

DishServiceImpl

/**
     * 删除菜品信息以及口味信息
     * @param ids
     */
    @Override
    @Transactional
    public void deleteWithFlavor(List<Long> ids) {
        // 删除菜品信息
        LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
        dishLambdaQueryWrapper.in(ids!=null,Dish::getId,ids);
        // 若菜品为起售状态,不可删除
        dishLambdaQueryWrapper.eq(Dish::getStatus,1);
        long count = this.count(dishLambdaQueryWrapper);

        if (count>0){
            // 如果不能删除,则抛出异常
            throw  new CustomException("菜品为起售状态,不可删除");
        }
        // 如果能删除,则删除菜品信息
        this.removeByIds(ids);

        // 删除对应口味信息
        LambdaQueryWrapper<DishFlavor> flavorLambdaQueryWrapper = new LambdaQueryWrapper<>();
        flavorLambdaQueryWrapper.in(ids!=null,DishFlavor::getDishId,ids);

        // 删除对应口味信息
        dishFlavorService.remove(flavorLambdaQueryWrapper);
    }

DishController

/**
     * 删除/批量删除
     * @param ids
     * @return
     */
    @RequestMapping(value = "",method = RequestMethod.DELETE)
    public R<String> delete(@RequestParam("ids") List<Long> ids){
        log.info("删除或批量删除菜品");
        dishService.deleteWithFlavor(ids);
        return R.success("删除菜品成功!");
    }

测试

删除菜品为test88的菜品
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第106张图片
由于为起售状态,所以不能删除
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第107张图片
停售之后删除

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第108张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第109张图片

5、套餐管理

5.1 新增套餐

需求分析
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第110张图片
数据模型
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第111张图片
准备工作
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第112张图片
梳理交互过程

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

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

根据菜品分类将对应菜品信息展示到添加菜品窗口中
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第113张图片
DishController

	/**
     * 根据条件查询菜品信息
     * @param dish
     * @return
     */
    @RequestMapping(value = "/list",method = RequestMethod.GET)
    public R<List<Dish>> list(Dish dish){
        log.info("根据菜品id查询菜品信息");
        // 构造条件构造器
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId());
        // 查询状态为 1(起售的菜品)
        queryWrapper.eq(Dish::getStatus,1);
        // 添加排序条件
        queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

        List<Dish> dishes = dishService.list(queryWrapper);

        return R.success(dishes);
    }

效果

点击添加菜品按钮,展现出来菜品信息
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第114张图片
添加套餐

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第115张图片
点击保存按钮,发送页面请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第116张图片
在这里插入图片描述
由于新增套餐中的setmealDishessetmeal中不存在,所以需要引入SetmealDto

@Data
public class SetmealDto extends Setmeal {

    private List<SetmealDish>  setmealDishes;

    private String categoryName;
}

新增套餐业务涉及了setmealsetmeal_dish两张表,所以在Service层中进行判断

SetmealService

// 新增套餐,以及菜品关系信息
    void saveWithDish(SetmealDto setmealDto);

SetmealServiceImpl

/**
     * 新增套餐,同时需要保存套餐和菜品的关联关系
     * @param setmealDto
     */
    @Override
    @Transactional
    public void saveWithDish(SetmealDto setmealDto) {
        // 保存套餐基本信息
        this.save(setmealDto);

        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
        setmealDishes.stream().map((item)->{
            item.setSetmealId(setmealDto.getId());
            return item;
        }).collect(Collectors.toList());

        // 保存套餐和菜品的关联信息
        setmealDishService.saveBatch(setmealDishes);
    }

SetmealController

/**
     * 添加套餐
     * @param setmealDto
     * @return
     */
    @RequestMapping(value = "",method = RequestMethod.POST)
    public R<String> save(@RequestBody SetmealDto setmealDto){
        log.info("套餐信息:"+setmealDto.toString());

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

测试

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第117张图片
setmeal表
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第118张图片
setmeal_dish表
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第119张图片

5.2 套餐信息分页查询

需求分析
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第120张图片
梳理交互过程

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

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

页面请求

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第121张图片
代码开发

SetmealController

/**
     * 分页查询套餐信息
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @RequestMapping(value = "/page",method = RequestMethod.GET)
    public R<Page<Setmeal>> page(int page,int pageSize,String name){
        // 分页构造器
        Page<Setmeal> pageInfo = new Page<>(page,pageSize);
        // 条件构造器
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        // 添加查询条件,根据name进行模糊查询
        queryWrapper.like(name!=null,Setmeal::getName,name);
        // 添加排序条件
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);

        setmealService.page(pageInfo,queryWrapper);

        return R.success(pageInfo);
    }

测试

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第122张图片
缺少了套餐分类的信息

代码改进

/**
     * 分页查询套餐信息
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @RequestMapping(value = "/page",method = RequestMethod.GET)
    public R<Page<SetmealDto>> page(int page,int pageSize,String name){
        // 分页构造器
        Page<Setmeal> pageInfo = new Page<>(page,pageSize);
        Page<SetmealDto> dtoPage = new Page<>(page,pageSize);

        // 条件构造器
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        // 添加查询条件,根据name进行模糊查询
        queryWrapper.like(name!=null,Setmeal::getName,name);
        // 添加排序条件
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);

        setmealService.page(pageInfo,queryWrapper);
        // 对象拷贝
        BeanUtils.copyProperties(pageInfo,dtoPage);

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

        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);

        return R.success(dtoPage);
    }

测试
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第123张图片

5.3 删除与批量删除

需求分析
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第124张图片
页面请求

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第125张图片
由于套餐信息涉及setmealsetmeal_dish 两张表,所以在Service层进行判断

SetmealService

// 删除套餐,以及菜品关系信息
    void deleteWithDish(List<Long> ids);

SetmealServiceImpl

/**
     * 删除套餐,以及菜品关系信息
     * @param ids
     */
    @Override
    @Transactional
    public void deleteWithDish(List<Long> ids) {
        // 1.删除套餐表中信息
        LambdaQueryWrapper<Setmeal> setmealQueryWrapper = new LambdaQueryWrapper<>();
        // 添加查询条件
        setmealQueryWrapper.in(ids != null, Setmeal::getId, ids);
        // 是否起售
        setmealQueryWrapper.eq(Setmeal::getStatus, 1);

        // 查询删除中起售套餐的数量
        long count = this.count(setmealQueryWrapper);

        if (count > 0) {
            // 如果删除的套餐中为起售状态,不能删除
            throw new CustomException("起售中的套餐不能删除");
        }
        // 删除套餐表中的数据
        this.removeByIds(ids);

        // 2.删除套餐菜品信息表中数据
        LambdaQueryWrapper<SetmealDish> setmealDishQueryWrapper = new LambdaQueryWrapper<>();
        setmealDishQueryWrapper.in(ids != null, SetmealDish::getSetmealId, ids);

        setmealDishService.remove(setmealDishQueryWrapper);
    }

SetmealController

/**
     * 删除与批量删除套餐
     * @param ids
     * @return
     */
    @RequestMapping(value = "",method = RequestMethod.DELETE)
    public R<String> delete(@RequestParam("ids") List<Long> ids){
        log.info("删除与批量删除套餐");

        setmealService.deleteWithDish(ids);
        return R.success("删除套餐信息成功!");
    }

测试

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第126张图片
修改售卖状态后再次尝试
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第127张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第128张图片

5.4 起售与批量起售

需求分析

在套餐管理列表页面点击停售按钮,可以停售对应的套餐。也可以通过复选框选择多个套餐,点击批量停售按钮一次停售多个套餐。
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第129张图片
页面请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第130张图片
代码开发

SetmealController

/**
     * 修改售卖状态
     * @param status
     * @param ids
     * @return
     */
    @RequestMapping(value = "/status/{status}",method = RequestMethod.POST)
    public R<String> update(@PathVariable("status") Integer status,@RequestParam("ids") List<Long> ids){
        log.info("停售与批量停售套餐");
        // 条件构造器
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(ids!=null,Setmeal::getId,ids);

        List<Setmeal> list = setmealService.list(queryWrapper);

        for (Setmeal setmeal : list) {
            if (setmeal!=null){
                setmeal.setStatus(status);
                setmealService.updateById(setmeal);
            }
        }

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

测试
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第131张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第132张图片

5.5 修改套餐

需求分析

在套餐管理列表页面点击修改按钮,可以修改对应的套餐。
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第133张图片
修改套餐之前,我们需要根据id获取套餐的信息来进行数据回显。
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第134张图片
页面请求

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第135张图片
代码开发

/**
     * 根据id获取套餐信息
     * @param id
     * @return
     */
    @RequestMapping(value = "/{id}",method = RequestMethod.GET)
    public R<SetmealDto> getById(@PathVariable("id") Long id){

        log.info("根据id:{}获取套餐信息",id);
        // 1.获取套餐信息
        Setmeal setmeal = setmealService.getById(id);
        // 将套餐信息复制到dto中
        SetmealDto setmealDto = new SetmealDto();
        BeanUtils.copyProperties(setmeal,setmealDto);

        // 2.获取套餐菜品信息
        LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SetmealDish::getSetmealId,id);

        List<SetmealDish> dishes = setmealDishService.list(queryWrapper);

        setmealDto.setSetmealDishes(dishes);

        return R.success(setmealDto);
    }

效果

点击修改按钮,根据套餐id,页面进行数据的回显
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第136张图片
修改套餐信息

页面请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第137张图片
套餐的信息涉及setmealsetmeal_dish 两张表

SetmealService

// 修改套餐,以及菜品关系信息
    void updateWithDish(SetmealDto setmealDto);

SetmealServiceImpl

/**
     * 更新信息(含菜品信息)
     * @param setmealDto
     */
    @Override
    @Transactional
    public void updateWithDish(SetmealDto setmealDto) {
        // 1.更新套餐信息(setmeal表)
        this.updateById(setmealDto);
        // 清除当前菜品关系信息
        LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SetmealDish::getSetmealId,setmealDto.getId());
        setmealDishService.remove(queryWrapper);

        // 添加菜品关系信息
        List<SetmealDish> dishes = setmealDto.getSetmealDishes();

        dishes=dishes.stream().map((item)->{
            item.setSetmealId(setmealDto.getId());
            return item;
        }).collect(Collectors.toList());

        setmealDishService.saveBatch(dishes);
    }

SetmealController

/**
     * 修改套餐信息
     * @param setmealDto
     * @return
     */
    @RequestMapping(value = "",method = RequestMethod.PUT)
    public R<String> update(@RequestBody SetmealDto setmealDto){
        log.info("setmealDto:{}",setmealDto.toString());

        setmealService.updateWithDish(setmealDto);
        return R.success("修改成功!");
    }

四、移动端功能开发

1、短信发送

1.1 短信服务介绍

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第138张图片

1.2 阿里云短信服务

Short Message Service 是广大企业客户快速触达手机用户所优选使用的通信能力;调用API或群发助手,即可发送验证码、通知类和营销类短信;

国内验证短信秒级触达,到达率可达99%;国际/港澳短信覆盖200多个国家和地区,安全稳定,广受出海企业选用。

应用场景

  • 验证码
  • 短信通知
  • 推广短信

1.3 代码开发

  1. 导入Maven坐标

<dependency>
    <groupId>com.aliyungroupId>
    <artifactId>aliyun-java-sdk-coreartifactId>
    <version>3.2.6version>
dependency>
<dependency>
    <groupId>com.aliyungroupId>
    <artifactId>aliyun-java-sdk-cmsartifactId>
    <version>7.0.24version>
dependency>
  1. 调用Api
/**
 * 短信发送工具类
 */
public class SMSUtils {

	/**
	 * 发送短信
	 * @param signName 签名
	 * @param templateCode 模板
	 * @param phoneNumbers 手机号
	 * @param param 参数
	 */
	public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
		DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
		IAcsClient client = new DefaultAcsClient(profile);

		SendSmsRequest request = new SendSmsRequest();
		request.setSysRegionId("cn-hangzhou");
		request.setPhoneNumbers(phoneNumbers);
		request.setSignName(signName);
		request.setTemplateCode(templateCode);
		request.setTemplateParam("{\"code\":\""+param+"\"}");
		try {
			SendSmsResponse response = client.getAcsResponse(request);
			System.out.println("短信发送成功");
		}catch (ClientException e) {
			e.printStackTrace();
		}
	}

}

2、手机验证码登录

需求分析
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第139张图片
数据模型
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第140张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第141张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第142张图片
SMSUtils

/**
 * 短信发送工具类
 */
public class SMSUtils {

	/**
	 * 发送短信
	 * @param signName 签名
	 * @param templateCode 模板
	 * @param phoneNumbers 手机号
	 * @param param 参数
	 */
	public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
		DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
		IAcsClient client = new DefaultAcsClient(profile);

		/*SendSmsRequest request = new SendSmsRequest();
		request.setSysRegionId("cn-hangzhou");
		request.setPhoneNumbers(phoneNumbers);
		request.setSignName(signName);
		request.setTemplateCode(templateCode);
		request.setTemplateParam("{\"code\":\""+param+"\"}");
		try {
			SendSmsResponse response = client.getAcsResponse(request);
			System.out.println("短信发送成功");
		}catch (ClientException e) {
			e.printStackTrace();
		}*/
	}

}

ValidateCodeUtils

/**
 * 随机生成验证码工具类
 */
public class ValidateCodeUtils {
    /**
     * 随机生成验证码
     * @param length 长度为4位或者6位
     * @return
     */
    public static Integer generateValidateCode(int length){
        Integer code =null;
        if(length == 4){
            code = new Random().nextInt(9999);//生成随机数,最大为9999
            if(code < 1000){
                code = code + 1000;//保证随机数为4位数字
            }
        }else if(length == 6){
            code = new Random().nextInt(999999);//生成随机数,最大为999999
            if(code < 100000){
                code = code + 100000;//保证随机数为6位数字
            }
        }else{
            throw new RuntimeException("只能生成4位或6位数字验证码");
        }
        return code;
    }

    /**
     * 随机生成指定长度字符串验证码
     * @param length 长度
     * @return
     */
    public static String generateValidateCode4String(int length){
        Random rdm = new Random();
        String hash1 = Integer.toHexString(rdm.nextInt());
        String capstr = hash1.substring(0, length);
        return capstr;
    }
}

代码开发
在这里插入图片描述
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第143张图片

若前端页面点击获取验证码,验证码框自动呈现验证码,则休要修改前端页面front/page/login.html

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第144张图片
front/api/login.js中添加sendMsgApi
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第145张图片
效果

点击获取验证码,发起sendMsg请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第146张图片
UserController

/**
     * 获取验证码
     * @param user
     * @param session
     * @return
     */
    @RequestMapping(value = "/sendMsg", method = RequestMethod.POST)
    public R<String> sendMsg(@RequestBody User user,HttpSession session) {
        // 获取手机号
        String phone = user.getPhone();
        if (StringUtils.isNotEmpty(phone)){
            // 生成随机的4位验证码
            String code = ValidateCodeUtils.generateValidateCode(4).toString();
            log.info("code:{}",code);
            // 调用阿里云提供的短信服务API完成发送短信
            //SMSUtils.sendMessage("瑞吉外卖","",phone,code);//仅供测试

            // 需要将生成的验证码保存到Session
            session.setAttribute(phone,code);

            return R.success("手机验证码短信发送成功");
        }
        return R.error("短信发送失败");
    }

效果

控制台输出验证码,我们用验证码来进行登录
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第147张图片

2.1 用户登录

页面请求

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第148张图片
如果页面只传入phone则需要修改前端页面front/page/login.html
在这里插入图片描述
效果

在这里插入图片描述

UserController

/**
     * 移动端用户登录
     * @param map
     * @return
     */
    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public R<User> login(@RequestBody Map map, HttpSession session){
        log.info(map.toString());

        // 获取手机号
        String phone = map.get("phone").toString();
        // 获取验证码
        String code = map.get("code").toString();
        // 从Session中获取保存的验证码
        Object codeInSession = session.getAttribute(phone);
        // 进行验证码比对
        if (codeInSession!=null&&codeInSession.equals(code)){
            // 如果能够比对成功,说明登录成功
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getPhone,phone);
            // 查询当前用户
            User user = userService.getOne(queryWrapper);
            // 判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
            if (user == null) {
                user = new User();

                user.setPhone(phone);
                user.setStatus(1);
                userService.save(user);
            }
            return R.success(user);
        }

        return R.error("登陆失败");
    }

注意:将userId放入Session

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第149张图片

测试

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第150张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第151张图片
使用一个数据库中没有的号码进行登录

在这里插入图片描述
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第152张图片
在这里插入图片描述
登录成功并添加到数据库

2.2 用户退出

页面请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第153张图片
代码开发

/**
     * 退出功能
     * @param request
     * @return
     */
    @RequestMapping("/loginout")
    public R<String> logout(HttpServletRequest request){
        // 清楚session中的phone
        request.getSession().removeAttribute("user");

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

3、导入用户地址簿

需求分析
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第154张图片
数据模型
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第155张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第156张图片

3.1 新增收货地址

当我们输入要添加的用户信息,点击保存时,页面发送ajax请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第157张图片
AddressBookController

/**
     * 新增收货地址
     * @param addressBook
     * @return
     */
    @RequestMapping(value = "", method = RequestMethod.POST)
    public R<AddressBook> save(@RequestBody AddressBook addressBook) {

        log.info(addressBook.toString());
        // 获取当前userId
        Long userId = BaseContext.getCurrentId();
        addressBook.setUserId(userId);

        addressBookService.save(addressBook);
        return R.success(addressBook);

    }

测试

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第158张图片
address_book表
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第159张图片

3.2 展示所有收货地址

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第160张图片
AddressBookController

/**
     * 查询所有收货地址信息
     * @return
     */
    @RequestMapping(value = "/list",method = RequestMethod.GET)
    public R<List<AddressBook>> list(){
        log.info("查询所有收货地址信息");
        // 构造条件构造器
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        // 添加条件
        queryWrapper.eq(AddressBook::getUserId,BaseContext.getCurrentId());
        // 排序条件
        queryWrapper.orderByDesc(AddressBook::getUpdateTime);
        List<AddressBook> list = addressBookService.list(queryWrapper);

        return R.success(list);
    }

效果

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第161张图片

3.3 修改默认地址

页面请求

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第162张图片
AddressBookController

/**
     * 修改默认地址
     * @param addressBook
     * @return
     */
    @RequestMapping(value = "/default",method = RequestMethod.PUT)
    public R<AddressBook> setDefault(@RequestBody AddressBook addressBook){
        log.info("修改地址簿默认地址");
        // 条件构造器
        LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(AddressBook::getUserId,BaseContext.getCurrentId());
        // 将所有的默认字段设置为 0(没有一个默认字段)
        wrapper.set(AddressBook::getIsDefault,0);
        // SQL: update address_book set is_default = 0 where user_id = ?
        addressBookService.update(wrapper);

        addressBook.setIsDefault(1);
        // SQL: update address_book set is_default =1 where id = ?
        addressBookService.updateById(addressBook);

        return R.success(addressBook);
    }

效果

当我们点击设为默认地址,即可切换当前的默认地址

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第163张图片

3.4 修改地址信息

点击按钮,进行修改默认地址信息。但修改之前,我们需要根据 id 获取地址信息来进行修改页面的数据回显
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第164张图片
根据id获取地址信息

页面请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第165张图片
AddressBookController

/**
     * 根据id获取地址簿信息
     * @param id
     * @return
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public R<AddressBook> getById(@PathVariable("id") Long id) {
        log.info("获取id为:{}的地址簿信息", id);
        // 条件构造器
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
        // 添加查询条件
        queryWrapper.eq(AddressBook::getId, id);
        // 查询对象
        AddressBook addressBook = addressBookService.getOne(queryWrapper);
        if (addressBook != null) {
            return R.success(addressBook);
        }
        return R.error("没有找到对象");
    }

效果

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第166张图片
我们发现,数据回显的页面性别未被选中,而在响应的数据中也并未出现问题

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第167张图片
由于前端页面sex属性用的是字符串,而我们后端数据库是int类型的 所以不匹配,只需修改前端页面sex属性,或者修改数据库即可

front/page/address-edit.html
在这里插入图片描述
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第168张图片

重新Build Project即可生效

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第169张图片

修改地址簿

页面请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第170张图片

/**
     * 修改地址簿信息
     * @param addressBook
     * @return
     */
    @RequestMapping(value = "",method = RequestMethod.PUT)
    public R<String> update(@RequestBody AddressBook addressBook){
        log.info("修改地址簿信息:{}",addressBook);

        addressBookService.updateById(addressBook);

        return R.success("地址簿信息修改成功");
    }

测试

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第171张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第172张图片
这个问题与刚才那个问题一样,只需修改sex属性即可

front/page/address.html
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第173张图片
修改之后的效果

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第174张图片

4、菜品展示

需求分析
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第175张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第176张图片
cartData.json

{"code": 1,"msg": null,"data": [],"map": {}}

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第177张图片
修改DIshController的list方法

/**
     * 根据条件查询菜品信息
     *
     * @param dish
     * @return
     */
    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public R<List<DishDto>> list(Dish dish) {
        log.info("根据菜品id查询菜品信息");
        // 构造条件构造器
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId());
        // 查询状态为 1(起售的菜品)
        queryWrapper.eq(Dish::getStatus, 1);
        // 添加排序条件
        queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

        List<Dish> list = dishService.list(queryWrapper);

        List<DishDto> dishDtoList = list.stream().map((item) -> {
            // 创建dishDto对象
            DishDto dishDto = new DishDto();
            // 拷贝对象
            BeanUtils.copyProperties(item, dishDto);

            Long categoryId = item.getCategoryId();
            // 根据id查询分类对象 获取分类名字
            Category category = categoryService.getById(categoryId);
            if (category != null) {
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }

            // 当前菜品id
            Long dishId = item.getId();
            // 获取口味信息
            LambdaQueryWrapper<DishFlavor> flavorQueryWrapper = new LambdaQueryWrapper<>();
            flavorQueryWrapper.eq(DishFlavor::getDishId, dishId);
            List<DishFlavor> dishFlavorList = dishFlavorService.list(flavorQueryWrapper);
            // 设置口味信息
            dishDto.setFlavors(dishFlavorList);

            return dishDto;

        }).collect(Collectors.toList());

        return R.success(dishDtoList);
    }

页面展示效果

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第178张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第179张图片
创建SetmealController的list方法

页面请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第180张图片

/**
     * 根据条件查询套餐数据
     * @param setmeal
     * @return
     */
    @RequestMapping(value = "/list",method = RequestMethod.GET)
    public R<List<Setmeal>> list(@RequestBody Setmeal setmeal){
        //条件构造器
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        // 添加条件 (分类id 和 售卖状态)
        queryWrapper.eq(setmeal.getCategoryId()!=null,Setmeal::getCategoryId,setmeal.getCategoryId());
        queryWrapper.eq(setmeal.getStatus()!=null,Setmeal::getStatus,setmeal.getStatus());
        // 添加排序条件
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);

        List<Setmeal> list = setmealService.list(queryWrapper);

        return R.success(list);
    }

测试

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第181张图片

5、购物车

需求分析

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第182张图片

数据模型

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第183张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第184张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第185张图片

5.1 添加购物车

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第186张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第187张图片
由此可见,添加套餐和菜品都是同一个方法

/**
     * 添加购物车
     * @param shoppingCart
     * @return
     */
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public R<ShoppingCart> save(@RequestBody ShoppingCart shoppingCart) {
        log.info("购物车数据:{}", shoppingCart);

        // 设置用户id,指定当前是那个用户的购物车数据
        shoppingCart.setUserId(BaseContext.getCurrentId());
        // 查询当前菜品或套餐是否在购物车中
        Long dishId = shoppingCart.getDishId();

        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
        if (dishId != null) {
            // 添加的是菜品
            queryWrapper.eq(ShoppingCart::getDishId, dishId);
        } else {
            // 添加的是套餐
            queryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId());
        }
        // SQL: select * from shopping_cart where user_id = ? and dish_id/setmeal_id =?
        ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);
        if (cartServiceOne != null) {
            // 如果已经存在,则在原来的基础上加 1
            Integer number = cartServiceOne.getNumber();
            cartServiceOne.setNumber(number + 1);
            shoppingCartService.updateById(cartServiceOne);
        } else {
            // 如果不存在,则添加到购物车,数量默认 为 1
            shoppingCart.setNumber(1);
            shoppingCart.setCreateTime(LocalDateTime.now());
            shoppingCartService.save(shoppingCart);
            cartServiceOne = shoppingCart;
        }

        return R.success(cartServiceOne);
    }

5.2 查看购物车

将之前修改的js修改回来
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第188张图片

/**
     * 查看购物车
     * @return
     */
    @RequestMapping(value = "/list",method = RequestMethod.GET)
    public R<List<ShoppingCart>> list(){
        log.info("查看购物车...");
        // 条件构造器
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
        // 添加排序条件
        queryWrapper.orderByDesc(ShoppingCart::getCreateTime);
        List<ShoppingCart> list = shoppingCartService.list(queryWrapper);
        
        return R.success(list);
    }

效果
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第189张图片

5.3 清空购物车

页面请求

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第190张图片
代码开发

/**
     * 清空购物车
     * @return
     */
    @RequestMapping(value = "/clean",method = RequestMethod.DELETE)
    public R<String> clean(){
        // SQL:delete from shopping_cart where user_id = ?
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());

        shoppingCartService.remove(queryWrapper);

        return R.success("清空购物车成功");

    }

效果
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第191张图片

6、用户下单

需求分析

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第192张图片

数据模型

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第193张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第194张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第195张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第196张图片

6.1 获取地址簿默认地址信息

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第197张图片
AddressBookController

/**
     * 获取默认地址簿信息
     * @return
     */
    @RequestMapping(value = "/default", method = RequestMethod.GET)
    public R<AddressBook> queryDefault() {
        // 条件构造器
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        // 添加查询条件
        queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
        queryWrapper.eq(AddressBook::getIsDefault, 1);
        // 执行查询
        AddressBook addressBook = addressBookService.getOne(queryWrapper);
        if (addressBook != null) {
            return R.success(addressBook);
        }
        return R.error("没有找到该对象");
    }

效果

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第198张图片

6.2 下单功能

点击去支付,发送下单的ajax请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第199张图片
页面只提供了`addressBookId、payMethod、remark``地址簿id,支付方式和备注,因为当前用户的id后台已经获取到,可以通过用户id查询购物车的信息。

OrdersService

// 用户下单
    void submit(Orders orders);

OrderServiceImpl

/**
     * 用户下单
     *
     * @param orders
     */
    @Override
    @Transactional
    public void submit(Orders orders) {
        // 获取当前用户id
        Long userId = BaseContext.getCurrentId();

        // 查询当前用户的购物车数据
        LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(ShoppingCart::getUserId, userId);
        List<ShoppingCart> shoppingCarts = shoppingCartService.list(lambdaQueryWrapper);
        // 判断购物车是否有数据
        if (shoppingCarts == null || shoppingCarts.size() == 0) {
            throw new CustomException("购物车数据为空,不能下单");
        }

        // 查询用户数据
        User user = userService.getById(userId);
        // 查询地址数据
        Long addressBookId = orders.getAddressBookId(); //获取地址簿id
        AddressBook addressBook = addressBookService.getById(addressBookId);//查询地址簿信息
        if (addressBook == null) {
            throw new CustomException("用户地址信息有误,不能下单");
        }

        long orderId = IdWorker.getId();// 订单号
        // 初始化总金额 amount
        AtomicInteger amount = new AtomicInteger(0);

        List<OrderDetail> orderDetails = shoppingCarts.stream().map((item) -> {
            // 创建订单明细对象
            OrderDetail orderDetail = new OrderDetail();

            orderDetail.setOrderId(orderId);
            orderDetail.setNumber(item.getNumber());
            orderDetail.setDishFlavor(item.getDishFlavor());
            orderDetail.setDishId(item.getDishId());
            orderDetail.setSetmealId(item.getSetmealId());
            orderDetail.setName(item.getName());
            orderDetail.setImage(item.getImage());
            orderDetail.setAmount(item.getAmount());

            amount.addAndGet(item.getAmount().multiply(new BigDecimal(item.getNumber())).intValue());

            return orderDetail;

        }).collect(Collectors.toList());

        // 向订单表插入数据(一条数据)

        orders.setId(orderId);
        orders.setOrderTime(LocalDateTime.now());
        orders.setCheckoutTime(LocalDateTime.now());
        orders.setStatus(2);
        orders.setAmount(new BigDecimal(amount.get()));
        orders.setUserId(userId);
        orders.setNumber(String.valueOf(orderId));
        orders.setUserName(user.getName());
        orders.setConsignee(addressBook.getConsignee());
        orders.setPhone(addressBook.getPhone());
        orders.setAddress(
                (addressBook.getProvinceName() == null ? "" : addressBook.getProvinceName())
                        + (addressBook.getCityName() == null ? "" : addressBook.getCityName())
                        + (addressBook.getDistrictName() == null ? "" : addressBook.getDistrictName())
                        + (addressBook.getDetail() == null ? "" : addressBook.getDetail())
        );
        this.save(orders);

        // 向订单明细表插入数据(多条数据)
        orderDetailService.saveBatch(orderDetails);

        // 清空购物车数据

        shoppingCartService.remove(lambdaQueryWrapper);
    }

OrderServiceController

/**
     * 下单功能
     * @param orders
     * @return
     */
    @RequestMapping(value = "/submit",method = RequestMethod.POST)
    public R<String> submit(@RequestBody Orders orders){
        log.info("订单信息"+orders.toString());

        ordersService.submit(orders);

        return R.success("下单成功!");
    }

6.3 分页查询订单信息

页面请求
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第200张图片
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第201张图片
由于前端页面展示的数据需要orderorder_detail两张表,所以我们需要引入OrdersDto

OrdersDto

@Data
public class OrdersDto extends Orders {

    private String userName;

    private String phone;

    private String address;

    private String consignee;

    private List<OrderDetail> orderDetails;
	
}

OrdersService

// 分页查询订单
    Page<OrdersDto> page(int page, int pageSize);

OrdersServiceImpl

/**
     * 分页查询订单
     *
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public Page<OrdersDto> page(int page, int pageSize) {
        // 分页构造器
        Page<Orders> pageInfo = new Page<>();
        Page<OrdersDto> pageDto = new Page<>();

        // 构造条件查询对象
        LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Orders::getUserId, BaseContext.getCurrentId());

        // 添加排序条件
        queryWrapper.orderByDesc(Orders::getOrderTime);
        this.page(pageInfo, queryWrapper);

        // 对orderDto进行赋值
        List<Orders> records = pageInfo.getRecords();

        List<OrdersDto> ordersDtos = records.stream().map((item) -> {
            OrdersDto ordersDto = new OrdersDto();
            // 拷贝
            BeanUtils.copyProperties(item, ordersDto);
            // 对ordersDto进行赋值
            Long orderId = item.getId();// 订单id
            // 通过orderId查询orderDetail
            LambdaQueryWrapper<OrderDetail> detailLambdaQueryWrapper = new LambdaQueryWrapper<>();
            detailLambdaQueryWrapper.eq(OrderDetail::getOrderId, orderId);

            List<OrderDetail> orderDetails = orderDetailService.list(detailLambdaQueryWrapper);
            // 设置OrderDetails属性
            ordersDto.setOrderDetails(orderDetails);

            return ordersDto;
        }).collect(Collectors.toList());

        // 除了 records 其余拷贝
        BeanUtils.copyProperties(pageInfo, pageDto, "records");
        // 设置 records
        pageDto.setRecords(ordersDtos);

        return pageDto;
    }

OrdersController

/**
     * 分页查询订单信息
     * @param page
     * @param pageSize
     * @return
     */
    @RequestMapping(value = "/userPage",method = RequestMethod.GET)
    public R<Page<OrdersDto>> page(int page, int pageSize){

        Page<OrdersDto> dtoPage = ordersService.page(page, pageSize);

        return R.success(dtoPage);
    }

效果

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第202张图片

6.4 再来一单

需求分析

当我们订单完成后,可以点击再来一单按钮进行下单,方便快捷
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第203张图片
页面请求

瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第204张图片
代码开发

/**
     * 再来一单
     *
     * @param map
     * @return
     */
    @RequestMapping(value = "/again", method = RequestMethod.POST)
    public R<String> again(@RequestBody Map<String, String> map) {
        log.info("map:{}", map);

        // 获取订单id
        String id = map.get("id");
        long orderId = Long.parseLong(id);
        // 条件构造器
        LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(OrderDetail::getOrderId, orderId);
        // 获取订单详细信息
        List<OrderDetail> list = orderDetailService.list(queryWrapper);

        // 清空购物车
        shoppingCartService.clean();

        // 获取当前用户id
        Long userId = BaseContext.getCurrentId();

        List<ShoppingCart> shoppingCarts = list.stream().map((item) -> {

            ShoppingCart shoppingCart = new ShoppingCart();
            // 把订单数据赋值给购物车对象
            shoppingCart.setUserId(userId);
            shoppingCart.setName(item.getName());
            shoppingCart.setImage(item.getImage());
            Long dishId = item.getDishId();// 菜品id

            if (dishId != null) {
                // 如果是菜品就添加菜品id
                shoppingCart.setDishId(dishId);
            } else {
                // 否则就添加套餐id
                shoppingCart.setSetmealId(item.getSetmealId());
            }
            shoppingCart.setDishFlavor(item.getDishFlavor());
            shoppingCart.setNumber(item.getNumber());
            shoppingCart.setAmount(item.getAmount());
            shoppingCart.setCreateTime(LocalDateTime.now());

            return shoppingCart;

        }).collect(Collectors.toList());

        // 把携带数据的购物车批量插入购物车表
        shoppingCartService.saveBatch(shoppingCarts);

        return R.success("操作成功");

    }

测试

点击再来一单后,购物车中的数据已经选择完毕,等待用户支付
瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)_第205张图片

你可能感兴趣的:(mybatis,spring,boot,java)