笑霸餐厅 | 瑞吉外卖项目 | 完整基础部分(后端学习笔记)

发布于:2022-12-19 ⋅ 阅读:(196) ⋅ 点赞:(0)

前言
👏作者简介:我是笑霸final,一名热爱技术的在校学生。
📝个人主页:个人主页1 || 笑霸final的主页2
📕系列专栏:项目专栏
📧如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀
🔥如果感觉博主的文章还不错的话,👍点赞👍 + 👀关注👀 + 🤏收藏🤏
在这里插入图片描述

瑞吉外卖项目学习笔记

零、项目简介

本项目可以练习自己的思想,是一个学习的好项目,因为老师有让学生自己动手的部分比如菜品管理和分类管理有一部分要求自己写,可以检验一下自己同东没有
项目视频教程搜索 B站黑马程序员 瑞吉买卖

项目分类两个系统 后台管理系统和用户界面

用户界面
在这里插入图片描述
后台界面
在这里插入图片描述

本项目特点
基础部分
技术栈:springboot 、mybatisplus.还用到jdk8新特性lamada表达式 和steam流
可以大量练习CRUD 、
特点:老师领进门修行看个人 项目留有很多地方需要自己动手才能完善此项目。本项目有优化部分 涉及到 redis、nginx。
前端页面 是vue 资料已经给出 能懂vue最好(可以自己修改一些逻辑) 不懂也没关系

一、各种注解的解释

1.1Lombok注解

@Slf4j

@Slf4j :提供打印日志
log4j定义了8个级别的log(除去OFF和ALL,可以说分为6个级别),优先级从高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。

ALL 最低等级的,用于打开所有日志记录,很低的日志级别,一般不会使用。
DEBUG 指出细粒度信息事件对调试应用程序是非常有帮助的,主要用于开发过程中打印一些运行信息。

INFO 消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。

WARN 表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示。

ERROR 指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。

FATAL 指出每个严重的错误事件将会导致应用程序的退出。这个级别比较高了。重大错误,这种级别你可以直接停止程序了。

OFF 最高等级的,用于关闭所有日志记录。

如果将log level设置在某一个级别上,那么比此级别优先级高的log都能打印出来。例如,如果设置优先级为WARN,那么OFF、FATAL、ERROR、WARN 4个级别的log能正常输出,而INFO、DEBUG、TRACE、 ALL级别的log则会被忽略。Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG,并且,log4j默认的优先级为ERROR。

1.2spring框架注解

@Controller, @Repository, @Service

@Component, @Service, @Controller, @Repository是spring注解,注解后可以被spring框架所扫描并注入到spring容器来进行管理
@Component是通用注解,其他三个注解是这个注解的拓展,并且具有了特定的功能
@Repository注解在持久层中,具有将数据库操作抛出的原生异常翻译转化为spring的持久层异常的功能。
@Controller层是spring-mvc的注解,具有将请求进行转发,重定向的功能。
@Service层是业务逻辑层注解,这个注解只是标注该类处于业务逻辑层。
用这些注解对应用进行分层之后,就能将请求处理,义务逻辑处理,数据库操作处理分离出来,为代码解耦,也方便了以后项目的维护和开发。

@ResponseBody、@RestController

@ResponseBody表示方法的返回值直接以指定的格式写入Http response body中,而不是解析为跳转路径。
@RestController的作用等同于@Controller + @ResponseBody。如果要求方法返回的是json格式数据,而不是跳转页面,可以直接在类上标注@RestController,而不用在每个方法中标注@ResponseBody,简化了开发过程。

@RequestBody、@RequestParam、@PathVariable

@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);而最常用的使用请求体传参的无疑是POST请求了,所以使用@RequestBody接收数据时,一般都用POST方式进行提交。
@RequestParam用于将请求参数区数据映射到功能处理方法的参数上。
@PathVariable是spring3.0的一个新功能:接收请求路径中占位符的值

@ServletComponentScan

在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码。

@ControllerAdvice

首先,ControllerAdvice本质上是一个Component,因此也会被当成组建扫描,。
ControllerAdvice就是aop思想的一种实现,你告诉我需要拦截规则
定义拦截规则
ControllerAdvice 提供了多种指定Advice规则的定义方式,默认什么都不写,则是Advice所有Controller,当然你也可以通过下列的方式指定规则
1.比如@ControllerAdvice("org.my.pkg") 或者 @ControllerAdvice(basePackages="org.my.pkg"), 则匹配org.my.pkg包及其子包下的所有Controller,当然也可以用数组的形式指定,如:@ControllerAdvice(basePackages={"org.my.pkg", "org.my.other.pkg"}), 也可以通过指定注解来匹配,比如我自定了一个 @CustomAnnotation 注解,我想匹配所有被这个注解修饰的 Controller, 可以这么写:@ControllerAdvice(annotations={Controller.class})

@Value

该注解的作用是将我们配置文件的属性读出来,有@Value(“${}”)和@Value(“#{}”)两种方式
@Value的值有两类:
① ${ property : default_value }
② #{ obj.property? :default_value }
第一个注入的是外部配置文件对应的property,第二个则是SpEL表达式对应的内容。 那个
default_value,就是前面的值为空时的默认值。注意二者的不同,#{}里面那个obj代表对象。

1.3javax.servlet注解

@WebFilter

@WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。该注解具有下表给出的一些常用属性 ( 以下所有属性均为可选属性,但是 value、urlPatterns、servletNames 三者必需至少包含一个,且 value 和 urlPatterns(过滤器的匹配模式) 不能共存,如果同时指定,通常忽略 value 的取值 )

1.4MybatisPlus注解

@TableField

二、环境搭建

在这里插入图片描述

pom文件内容就不用写了就是那么几个 直接看教程

2.1配置application.yml文件

server:
  port: 8080
spring:
  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: 0615


mybatis-plus:
  configuration:
    #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID

2.2配置类config

创建WebMvcConfig需要继承WebMvcConfigurationSupport

package com.xbfinal.reggie.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;


@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    /**
     * 设置静态类资源映射
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        //使用 registry来设置访问路劲
        registry.addResourceHandler("/backend/**")
                .addResourceLocations("classPath:/backend/");
        registry.addResourceHandler("/front/**")
                .addResourceLocations("classPath:/front/");
    }
}

addResourceHandler:开放的路劲
addResourceLocations:加载的资源

WebMvcConfigurationSupport说明

说明
WebMvcConfigurationSupport 在整个应用程序中只会生效一个
该类提供了主要的 MVC 配置方法,通过直接继承 WebMvcConfiguration ,并在继承类上 加上 @EnableWebMvc 和 @Configuration 注解之后。便可以在子类中实现父类的方法 ,更甚至可以实现含有@Bean 注解的方法,但记得一定要在实现的方法上加上 @Bean

常重写的接口

/** 静态资源处理 避免静态资源被拦截**/
void addResourceHandlers( ResourceHandlerRegistry registry);
/** 视图跳转控制器 /
void addViewControllers(ViewControllerRegistry registry)
/
这里配置视图解析器 /
void configureViewResolvers(ViewResolverRegistry registry);
/
视图跳转控制器 /
void addViewControllers(ViewControllerRegistry registry);
/
添加拦截器 /
void addInterceptors(InterceptorRegistry registry);
/
解决跨域问题 **/
void addCorsMappings(CorsRegistry registry) ;

三、系统登陆功能

3.1需求分析

需求分析
1.登陆请求:http://localhost:63342/employee/login
2.请求方式:post
3.以json方式提交到服务端
在这里插入图片描述
服务端
Controller接受到用户名和密码
service来调maper来查询数据库
查看employee
前端页面分析
在这里插入图片描述
所以后端处理完成后应该给页面相应什么样的数据(json格式数据
登陆成功后 可以看到是前端跳转的页面 /backend/index.html

3.2后端实现

3.2.1创建基本的结构

创建如下文件
在这里插入图片描述

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


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

}

都是继承或实现了mybatis-plus的接口
IService<Employee>: Service层
ServiceImpl<EmployeeMapper, Employee> ServiceImpl层
BaseMapper<Employee> Mapper层

3.2.2创建返回结果类 R

此类事一个结果类,服务端响应的所有结果最终都会包装成此类对象返回给前端

/**
 * 通用返回结果类
 *
 * @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;
    }

    //用来操作 map = new HashMap(); 动态数据
    public R<T> add(String key, Object value) {
        this.map.put(key, value);
        return this;
    }

}

3.2.3代码开发

分析
在这里插入图片描述

== 代码==

package com.xbfinal.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xbfinal.reggie.common.R;
import com.xbfinal.reggie.pojo.Employee;
import com.xbfinal.reggie.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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;
import javax.servlet.http.HttpServletRequest;


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

    @Autowired
    private EmployeeService employeeService;

    /**
     * 登陆功能
     * 前端在请求体中带来了 两个参数
     * @return
     */
    @PostMapping("/login")
    public R<Employee> login(HttpServletRequest request,
                             @RequestBody Employee employee){
        //创建request 需要把信息存入session中

        //1.把前端提交的密码进行md5 加密处理
         String password = employee.getPassword();
         password = DigestUtils.md5DigestAsHex(password.getBytes());

         //2.更具用户名查数据库
        final LambdaQueryWrapper<Employee> queryWrapper =
                new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername, employee.getUsername());
        final Employee emp = employeeService.getOne(queryWrapper);

        //3.没查到就返回失败
        if(emp==null){
            return R.error("登陆失败,请检查用户名是否正确");
        }

        //4.密码比对(比对不成功返回错误信息)
        if(!emp.getPassword().equals(password)){
            return R.error("登陆失败,请检查密码是否正确");
        }

        //5.查看员工状态,1可用 0禁用
        if(emp.getStatus() == 0){
            return R.error("登陆失败,员工已被禁用,请联系老板");
        }
        //6.登陆成功 再把id存入session
        request.getSession().setAttribute("employee",emp.getId());
        return R.success(emp);

        
    }
}

代码分析
在这里插入图片描述
代码分析DigestUtils.md5DigestAsHex

password = DigestUtils.md5DigestAsHex(password.getBytes());

这是spring自带的import org.springframework.util.DigestUtils;系统是把用户输入的密码计算成MD5值返回一个string类型的数据(MD5加密后的)
password.getBytes()把字符串转成对应的字节数组(如果没有指定码表(软件也没有指定)—默认使用的是系统平台码—Windows中文版—GBK),如果指定了码表就按指定的来

代码分析LambdaQueryWrapperMyBatis-Plus提供的

final LambdaQueryWrapper<Employee> queryWrapper =
                new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername, employee.getUsername());
        final Employee emp = employeeService.getOne(queryWrapper);

MyBatis-Plus提供的提供的一种查询方法
可以直接在表达式中判断值是否存在,空值判断,Wrappers使用方法这种写法可以避免sql的编写

3.3.4登陆优化

设置登陆检查过滤器LoginCheckFilter

防止没有登陆就进入后台
逻辑
在这里插入图片描述
代码

package com.xbfinal.reggie.Filter;


import com.alibaba.fastjson.JSON;
import com.xbfinal.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;

/**
 * 检查是否登陆
 * urlPatterns拦截的路劲   /* 表示所有请求
 * Filter是javax下的
 * 别忘记在启动类上加 @ServletComponentScan注解
 */
@Slf4j
@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*")
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.获取本次请求的url
        final String requestURI = request.getRequestURI();

        //2.本次是否需要处理(检查登陆状态、不需要过滤的页面 如登陆页面)
        String[] uls = new String[]{
          //不需要处理的请求,静态资源等
             "/employee/login" ,
             "/employee/logout",
             "/backend/**"   ,
             "/front/**"
        };
        final boolean check = check(uls, requestURI);

        //3.如果不需要处理就放行
        if(check){
            //放行
            filterChain.doFilter(request,response);
            //结束语句
            return;
        }

        //4.判断登陆状态 如果登陆则放行
        final Object employee = request.getSession().getAttribute("employee");
        if(employee!= null){
            //放行
            filterChain.doFilter(request,response);
            return;
        }

        //返回登陆页面,通过输出流的方式向客户端相应数据
        //返回这个 NOTLOGIN 交给前端跳转
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));

        return;

    }

    public boolean check(String[] urls,String requestUrl){
        for (String url : urls) {
            final boolean match = PATH_MATCHER.match(url, requestUrl);
            //final boolean match = url.equals(requestUrl);实现同样的功能
            if(match){
                return true;
            }
        }
        return false;
    }
}

细节优化重定向到登录页
每次调试都要输入路劲很麻烦 现在直接访问http://localhost:8080/就能进去登陆页
新建如下类
在这里插入图片描述

代码实现


@Slf4j
@Controller
public class SystemController {


    @GetMapping("/")
    public void tologin(HttpServletResponse response) throws IOException {
        log.info("进入@GetMapping(\"/\")");
        //Redirect
        response.sendRedirect("/backend/page/login/login.html");

    }

}


四、后台退出功能

4.1需求分析

员工登陆成功后,需要退出登陆的需求
1.登陆请求:http://localhost:8080/employee/logout
2.请求方式:POST

4.2代码实现

步骤
1.清理session中的用户id
2.返回结果(成功过后 页面会跳转到登陆页面前端实现
在这里插入图片描述
代码:

/**
     * 退出登陆
     * @return
     */
    @PostMapping("logout")
    public R<String> logout(HttpServletRequest request){

        //清理session的当前员工的id
        request.getSession().removeAttribute("employee");
        return R.success("退出成功");
    }

五、员工管理

5.1新增员工

1.需求分析
实现录入员工信息的操作
请求url:http://localhost:8080/employee
请求方式:post
请求数据:json

2.数据模型
将员工信息添加到employee注意表里对username字段有唯一约束,status有默认值 1

3.代码开发
controller接受页面提交的数据 调用service进行保存
service调用mapper操作数据库

/**
     * 新增员工
     * @param employee
     * @return
     */
    @PostMapping
    public R<String> save(@RequestBody Employee employee,
                          HttpServletRequest request){
        log.info("新增的员工信息{}",employee.toString());
        //给员工设置初始密码并加密处理
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
        employee.setCreateTime(LocalDateTime.now());//创建时间
        employee.setUpdateTime(LocalDateTime.now());//更新时间
        Long CreateUser=(Long)request.getSession().getAttribute("employee");
        employee.setCreateUser(CreateUser);//创建人
        employee.setUpdateUser(CreateUser);
        final boolean save = employeeService.save(employee);
        if (save){
            return  R.success("员工新增成功");
        }

        return R.error("员工添加失败");
    }

5.2新增员工的优化

因为有username字段有唯一约束所以 username一样会有异常发生
这里写一个全局异常全局异常处理器GlobalExceptionHandler
在这里插入图片描述

/**
 * 全局异常全局异常处理器
 * 底层是一个代理 通过aop拦截到异常
 */
@ControllerAdvice(annotations = {RestController.class, Controller.class})//拦截那些Controller
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

  
}

编写 处理usernamer的异常
SQLIntegrityConstraintViolationException异常

  /**
     * 处理SQLIntegrityConstraintViolationException异常
     * @return
     */
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(){
         if(ex.getMessage().contains("Duplicate entry")){
            //异常信包含Duplicate entry字符串说明就是违反了唯一约束

            return R.error("账号已经有人使用,请更换账号");
        }
        return R.error("未知错误");
    }

5.3员工信息分页查询 🥇

在这里插入图片描述

1.需求分析
分页展示:员工可能较多 我们一般会用分页的方式来展示数据
请求url:http://localhost:8080/employee/page?page=1&pageSize=10
发送ajax请求 将分页查询的(page 、pageSize)提交到服务端
按名字查找时url 请求 URL: http://localhost:8080/employee/page?page=1&pageSize=10&name=%E4%BC%98%E7%A7%80发送ajax请求 将分页查询的(page 、pageSize\name)提交到服务端
请求方式:get

2.代码开发
使用了分页插件

先创建mp分页插件的配置类
在这里插入图片描述

/**
 * 配置MP分页插件
 */
 @Configuration
 public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){

        final MybatisPlusInterceptor mybatisPlusInterceptor
                = new MybatisPlusInterceptor();

        mybatisPlusInterceptor.addInnerInterceptor( new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }

}

编写controller代码

/**
     * /employee/page?page=1&pageSize=10
     * employee/page?page=1&pageSize=10&name=%E4%BC%98%E7%A7%80
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("/page")
    public R<Page> page(int page ,int pageSize,String name){//泛型不能用employee、Page时分页插件带的

        log.info("page={}  pageSize={}& name={}",page,pageSize,name);
        //基于MP的分页插件

        //分页构造器 Page
        Page pageInfo = new Page(page,pageSize);

        //条件构造器 lambdaQueryWrapper
        final LambdaQueryWrapper<Employee> queryWrapper
                = new LambdaQueryWrapper();
        //添加一个过滤条件 注意 是org.apache.commons.lang.StringUtils;
        queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
        //添加一个排序条件(根据更新时间排序)
        queryWrapper.orderByDesc(Employee::getUpdateTime);

        //执行查询
        employeeService.page(pageInfo,queryWrapper);//处理完后会自己封装到pageInfo

        return R.success(pageInfo);
    }

}

5.4禁用/启用/编辑员工账号

需求分析

对某个员工进行 禁用/启用账号禁用后不能登陆系统
只有管理员可以禁用启用 和编辑 员工
在这里插入图片描述
发送请求:http://localhost:8080/employee
请求方式 put
发送格式 json
在这里插入图片描述
前端存在js处理Long数据类型可能会损失精度
解决方案使用消息转换器
点此查看消息转换器

禁用/启用代码部分

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

        log.info(employee.toString());
        //更新时间
        employee.setUpdateTime(LocalDateTime.now());
        //修改了逻辑
        employee.setUpdateUser((Long) request.getSession().getAttribute("employee"));
        employeeService.updateById(employee);
        return R.success("修改成功");
    }

编辑员工信息部分

分析
点击编辑 就会进入修改界面
url:http://localhost:8080/backend/page/member/add.html?id=1566955935820181505
get请求
在这里插入图片描述
这里只是跳转去这个页面 进入上面的页面后应该查询信息并显示

查询 员工信息

url:http://localhost:8080/employee/1566955935820181505
get请求

 /**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public R<Employee> getById(@PathVariable Long id){

        final Employee employee = employeeService.getById(id);
        return R.success(employee);
    }

在这里插入图片描述
最后点保存的时候还是走的禁用/启用代码部分的代码
在这里插入图片描述

5.5功能完善(公共字段填充)

问题一:以上还有一个问题 就是id过长 js处理的时候会丢失进度
(解决方案 把long转成 string)这里我们用消息转换器json

1.由于篇幅较长这里直接放链接:消息转换器json-资料

2.配置好后还需要扩张MVC消息的转换器
public class WebMvcConfig extends WebMvcConfigurationSupport添加如下代码

/**
     * 扩张MVC消息的转换器
     * @param converters
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //创建消息转换器对象
        final MappingJackson2HttpMessageConverter MessageConverter
                = new MappingJackson2HttpMessageConverter();

        //设置对象转换器
        MessageConverter.setObjectMapper(new JacksonObjectMapper());

        //将上面的消息转换器追加到mvc框架的转换器集合中
        converters.add(0,MessageConverter);//追加到最前面优先使用
    }

问题二:我们发现 每次创建/更新员工信息都会记录日期 、操作人。我们可以利用mp的功能来自定义得元处理器

1.我们先创建一个类来继承MetaObjectHandler

public class MyMetaObjecthandler implements MetaObjectHandler 

2.然后重写insertFill、和updateFill记得在pojo里面要自动注入的字段加上 @TableField(fill = FieldFill.INSERT)或者 @TableField(fill = FieldFill.INSERT_UPDATE)注解
在这里插入图片描述

@Slf4j
@Component
public class MyMetaObjecthandler implements MetaObjectHandler {

    /**
     * 插入填充
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {

        //threadLocal并不是一个线程 jdk提供 具有线程隔离效果
        //常用方法 set   get
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("createUser", BaseContext.getCurrentId());
        metaObject.setValue("updateUser", BaseContext.getCurrentId());

        //在插入字段得时候调用
        log.info("【insert】==============");
        log.info(metaObject.toString());
    }

    /**
     * 更新字段填充
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        //在更新字段得时候调用

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

但是又有新的问题 因为我们创建者id是存在session里面的而这个MetaObjectHandler元处理器是不能访问的
所以为了解决这个问题我们用ThreadLocal
我们创建一个基于ThreadLocal封装的工具类
然后getset方法
代码:

/**
 * 基于ThreadLocal封装的工具类
 * 一个线程是一个作用域
 */
public class BaseContext {
    private static ThreadLocal<Long> threadLocal =
             new ThreadLocal<>();

    public static void setCurrentId(long id) {
        threadLocal.set(id);
    }
    public static long getCurrentId() {
        return threadLocal.get();
    }
}

接下来在过滤器拿到我们要的值
在这里插入图片描述
最后我们在元处理器调用基于ThreadLocal封装的工具类的get方法
在这里插入图片描述
完成!!!!

六、分类管理业务

一共有两类
1.菜皮品分类
2.套餐分类
此分类也会体现在移动端首页
在这里插入图片描述
数据模型
category分类的表
在这里插入图片描述

6.1搭建基础的环境

1.编写实体类(根据表写就行了)
2.编写mapper 继承BaseMapper(也不具体演示了)
3.编写service接口继承IService(也不具体演示了)
4.编写Impl 继承ServiceImpl<CategoryMapper,Category>还要实现对应的service
在这里插入图片描述
5.编写controller
在这里插入图片描述

6.2新增分类

需求分析
1.登陆请求:http://localhost:8080/category
2.请求方式:post
3.以json方式提交到服务端
在这里插入图片描述
4.type:1表示菜品分类 2表示套餐分类

前端在这里插入图片描述
代码:在CategoryController中写

  /**
     * 新增分类
     * @param category
     * @return
     */
    @PostMapping
    public R<String> save(@RequestBody Category category){
        final boolean save = categoryService.save(category);
        if(save){
            return R.success("新增成功");
        }else {
            return R.error("新增失败");
        }

6.3分页查询

需求分析(和员工的分页查询一样的)
请求 URL: http://localhost:8080/category/page?page=1&pageSize=10
请求方法: GET

代码:

/**
     * 分页查询
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("/page")
    public R<Page> page(int page,int pageSize){
        final Page<Category> categoryPage = new Page<>(page,pageSize);
        //        条件构造器
        LambdaQueryWrapper<Category> lambdaQueryWrapper = new LambdaQueryWrapper();
        //        添加排序条件
        lambdaQueryWrapper.orderByAsc(Category::getSort);
        // 进行分页查询
        categoryService.page(categoryPage,lambdaQueryWrapper);
        return R.success(categoryPage);
    }

6.4删除分类

分析
如果当前分类已经关联了一些菜品和套餐是不能删除的
请求 URL: http://localhost:8080/category?ids=1397844263642378242
请求方法: DELETE

代码:

 /**
     * 删除
     * @param ids
     * @return
     */
    @DeleteMapping
    public R<String> delete(Long ids){
        final boolean b = categoryService.removeById(ids);
        if(b){
            return  R.success("删除成功");
        }else {
            return  R.error("删除失败");
        }

    }

6.5删除功能完善(全局异常处理) 🥇

功能完善
我们完成了根据ids删除的功能,但是并没有检查该分类是否关联了菜品或者套餐
步骤(需要先准备基础的类和接口)
1:实体类Dish(菜品)和Setmeal(套餐)
2:Mapper接口和SetmealMapper、DishMapper
3 Service接口SetmealService和DishService
4:Impl接口 SetmealServiceImpl和DishServiceImpl

定义一个异常类CustomException继承`RuntimeException

package com.xbfinal.reggie.common;

/**
 * @autor 笑霸fianl~
 * 自定义业务异常
 */
public class CustomException extends RuntimeException{
    public CustomException(String message){
        super(message);
    }
}

代码改造
CategoryService中新增方法public void remove(Long id);
并在CategoryServiceImpl去实现它

/**
 * @autor 笑霸fianl~
 * 欢迎访问GitHub:https://github.com/XBfinal
 * 欢迎访问Gitee:https://gitee.com/XBfianl
 * 欢迎访问CSDN:https://blog.csdn.net/weixin_52062043
 */
@Service
public class CategoryServiceImpl
        extends ServiceImpl<CategoryMapper, Category>
        implements CategoryService {

    @Autowired
    private DishService dishService;

    @Autowired
    SetmealService setmealService;
    /**
     * 根据id删除分类,删除之前需要进行判断
     * @param id
     */
    //扩展自己的方法
    public  void remove(Long id){
        LambdaQueryWrapper<Dish> dishLambdaQueryWrapper=
                new LambdaQueryWrapper<>();
        //添加查询条件,根据分类id进行查询
        dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);//(等值查询)
        final int count1
                = dishService.count(dishLambdaQueryWrapper);

        //查询当前分类是否关联了菜品,如果关联就抛出异常
        if(count1>0){
            //已经关联了菜品,抛出一个异常
            throw new CustomException("关联了菜品,不能删除");
        }
        LambdaQueryWrapper<Setmeal> SetmealLambdaQueryWrapper=
                new LambdaQueryWrapper<>();
        SetmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
        final int count2
                = setmealService.count(SetmealLambdaQueryWrapper);
        //查询当前分类是否关联了套餐,如果关联就抛出异常
        if(count2>0){
            //已经关联了套餐,抛出一个异常
            throw new CustomException("关联了套餐,不能删除");
        }
        //正常删除
        super.removeById(id);
    }
}

然后我们希望在前台看到这个异常消息
我们可以在全局异常处理器GlobalExceptionHandler去捕获我们这个异常

 /**
     * 处理自己的异常
     * exceptionHandler重载
     * @param message
     * @return
     */
    @ExceptionHandler(CustomException.class)
    public R<String> exceptionHandler(CustomException message ){
        
        return R.error(message.getMessage());
    }

6.6 修改分类

需求分析
url:http://localhost:8080/category
请求方法: PUT
返回格式 json
在这里插入图片描述

代码:

/**
     * 根据id修改分类信息
     * @param category
     * @return
     */
    @PutMapping
    public R<String> update(@RequestBody Category category){
        categoryService.updateById(category);
        return R.success("修改成功");
    }

七、菜品管理 🐉

在这里插入图片描述

功能:
批量删除 批量启售 批量停售 新建菜品 分页查询 修改菜品
文件上传/下载

7.1文件上传/下载 🥇

文件上传
对页面form表单有如下要求
1.methond="post"
2.enctype="multipart/form-data"
type="file"


文件上传需要用到Apachecommons-fileuploadcommons-io
spring中对这个进行了封装 我们只需要在Controller代码中声明一个MultipartFile类型的参数就能接受上传的文件
请求 URL: http://localhost:8080/common/upload
请求方法: POST

代码实现
为了动态的实现文件的转存,在yml中写上
在这里插入图片描述

package com.xbfinal.reggie.controller;


import com.xbfinal.reggie.common.R;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

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

    //读取yml配置文件的位置
    @Value("${reggie.path}")
     private String basePath;
    
    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public R<String> upload(MultipartFile file){
        
        //原始文件名
        final String originalFilename = file.getOriginalFilename();
        //获取 .jpg文件后缀
        final String substring = originalFilename.substring(originalFilename.lastIndexOf("."));

        //随机名
        String uuid=UUID.randomUUID().toString() +substring;
        //创建一个目录
        File file1 = new File(basePath);
        //判断目录是否存在
        if(!file1.exists()){
            //目录不存在就创建
            file1.mkdirs();
        }
        //将文件转存
        try{
            file.transferTo(new File(basePath+uuid));
        }catch (IOException e){
            
        }
        return R.success(uuid);//应该返回文件名 文件名前端或做相关的关联
    }
}

文件下载
两种表现形式:1.以附件形式下载 2.直接用浏览器打开
本质服务端 讲文件以流的形式回写浏览器的过程
urlhttp://localhost:8080/common/download
请求方式:get

代码:

 /**
     * 文件下载
     * @param name
     * @param response
     */
    @GetMapping("/download")
    public void download(String name, HttpServletResponse response) throws IOException {
        //输入流读取文件内容
        final FileInputStream fileInputStream 
                = new FileInputStream(new File(basePath+name));

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

        int len=0;
        byte[] bytes=new byte[1024];
        while ((len=fileInputStream.read(bytes)) != -1){
            outputStream.write(bytes,0,len);
            outputStream.flush();
        }
        response.setContentType("image/jpg");
        
        //关闭流
        outputStream.close();
        fileInputStream.close();

    }

7.2新增菜品

在这里插入图片描述

数据模型
将录入的菜品插入到dish表 如果添加了口味还要dish_flavor表
创建对应的实体类和mapper service这里就不多说了。
注意:dishflavor不用创建controller 都放在dish的controller取超控

/**
 * 菜品管理
 */
@RestController
@RequestMapping("/dish")
public class dishController {
    
    @Autowired
    private DishService dishService;

    @Autowired
    private DishFlavorService dishFlavorService;
    
    
}

搭建好了后 就开始分析了

菜品分类请求

添加菜品时有一个选着分类 这是先查询后菜选着
请求 URL: http://localhost:8080/category/list?type=1
请求方法: GET
所以代码写在 CategoryController
在这里插入图片描述

 /**
     * 根据条件查询分类数据
     * @param category
     * @return
     */

    @GetMapping("/list")
    public R<List<Category>> listR(Category category){
        //条件构造器
        final LambdaQueryWrapper<Category> lambdaQueryWrapper
                = new LambdaQueryWrapper();
        //添加条件
        lambdaQueryWrapper.eq(category.getType()!=null,Category::getType,category.getType());

        //添加排序条件
        lambdaQueryWrapper.orderByAsc(Category::getSort).orderByAsc(Category::getUpdateTime);
        //放入List集合
        List<Category> list=categoryService.list(lambdaQueryWrapper);
    return R.success(list);
    }

提交添加菜品表单🉑

请求 URL: http://localhost:8080/dish
请求方法: POST
JSON提交
在这里插入图片描述
这里会操作两张表,没有任何一个实体类可以接受前端发来的参数 ,所以写一个DTO类,即数据传输对象,一般用于展示层和服务层之间的数据传输
在这里插入图片描述

package com.xbfinal.reggie.dto;

import com.xbfinal.reggie.pojo.Dish;
import com.xbfinal.reggie.pojo.DishFlavor;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;

@Data
public class DishDto extends Dish {//继承了Dish

    //接受页面传来的flavors
    private List<DishFlavor> flavors = new ArrayList<>();

    //下面两个属性后面再说
    private String categoryName;

    private Integer copies;
}

然后写提交添加菜品表单的代码🉑

先写自己得service
使用了事务 * 要是事务 @Transactional生效得在启动类开启事务的支持 * @EnableTransactionManagement在这里插入图片描述

package com.xbfinal.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xbfinal.reggie.dto.DishDto;
import com.xbfinal.reggie.mapper.DishMapper;
import com.xbfinal.reggie.pojo.Dish;
import com.xbfinal.reggie.pojo.DishFlavor;
import com.xbfinal.reggie.service.DishService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @autor 笑霸fianl~
 * 欢迎访问GitHub:https://github.com/XBfinal
 * 欢迎访问Gitee:https://gitee.com/XBfianl
 * 欢迎访问CSDN:https://blog.csdn.net/weixin_52062043
 */
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish>
implements DishService {

    @Autowired
    DishFlavorService dishFlavorService;

    /**
     * 新增菜品的同时保存口味的数据
     * 同时操作多张表 所以需要事务控制
     * 要是事务 @Transactional生效得在启动类开启事务的支持
     * @EnableTransactionManagement
     * @param dishDto
     */
    @Override
    @Transactional
    public void saveWithFlavor(DishDto dishDto) {
        //先保存菜品的基本信息到菜品表
        this.save(dishDto);
        //菜品id
        final Long id = dishDto.getId();
        //保存菜品口味表
        List<DishFlavor> flavors=dishDto.getFlavors();
        flavors=flavors.stream().map(item->{
            item.setDishId(id);
            return item;
        }).collect(Collectors.toList());

        //保存菜品口味数据到菜品口味表dish_favor
        dishFlavorService.saveBatch(dishDto.getFlavors());
    }
}

然后controller代码如下

/**
     * 添加菜品
     * @param dishDto
     * @return
     */
    @PostMapping
    public R<String> save(@RequestBody DishDto dishDto){

        log.info(dishDto.toString());
        dishService.saveWithFlavor(dishDto);
        return R.success("保存成功");
    }

}

7.3菜品信息分页查询🚹

请求 URL: http://localhost:8080/dish/page?page=1&pageSize=10
请求方法: GET
还要展示图片
如果还是象以前那么写 就会有一个问题不能展示分类名称
在这里插入图片描述现有得实体类并不能完成这个任务所以我们需要有一个tdo来满足前端需要满足得数据。

/**
     * 菜品信息分页查询
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String 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
            //根据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);
    }



7.4修改菜品

修改页面和新增页面用的时同一个页面前端和服务器交互过程
1. 页面发生ajax请求分类数据用于菜品分类
2. 发生ajax请求服务器查询根据id查询菜品信息
3. 用于下载图片
4. 点击保存,发生ajax将修改后的信息的相关数据以json形式提交

发生ajax请求服务器查询根据id查询菜品信息
请求 URL: http://localhost:8080/dish/1413384757047271425
请求方法: GET

由于也是多表查询我们封装方法在service中

/**
     * 根据菜品id查询菜品基本信息和对应的口味
     * @param id
     * @return
     */
    @Override
    @Transactional
    public DishDto getByIdWithFlavor(Long id) {
        //1.查菜品基本
        final Dish dish = this.getById(id);
        final DishDto dishDto = new DishDto();
        //1.5使用对象拷贝
        BeanUtils.copyProperties(dish,dishDto);
        //2.查口味信息,从dish_flavor查
            //2.1条件构造器
        LambdaQueryWrapper<DishFlavor> QueryWrapper
                =new LambdaQueryWrapper<>();
            //2.2条件查询
        QueryWrapper.eq(DishFlavor::getDishId,dish.getId());
        final List<DishFlavor> list
                = dishFlavorService.list(QueryWrapper);
        dishDto.setFlavors(list);

        return dishDto;
    }

然后在controller对应写就行


    /**
     * 根据菜品id查询菜品基本信息和对应的口味
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public R<DishDto> getDishDto(@PathVariable("id") Long id){
        final DishDto dishDto = dishService.getByIdWithFlavor(id);
        return R.success(dishDto);
    }

点击保存,发生ajax将修改后的信息的相关数据以json形式提交

请求 URL: http://localhost:8080/dish
请求方法: PUT
在这里插入图片描述

依然自己封装一个方法

@Override
    @Transactional
    public void updateWithFlavor(DishDto dishDto) {
        //更新dish
        this.updateById(dishDto);
        
        //先清理dish_flavor
         LambdaQueryWrapper<DishFlavor> LambdaQueryWrapper 
                 = new LambdaQueryWrapper<>();
        LambdaQueryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
        dishFlavorService.remove(LambdaQueryWrapper);
        //添加dish_flavor
        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("保存成功");
    }

7.5删除和批量删除

请求 URL: http://localhost:8080/dish?ids=1568491426675707905
请求方法: DELETE
删除要删除两个表的内容
还是因为操作两张表DishServiceImpl


    /**
     * 删除菜品信息,同时删除对应的口味信息
     * @param  ids
     */
    @Override
    @Transactional
    public void deleteWithFlavor(List<Long> ids){
        //删除菜品信息
        this.removeByIds(ids);
        //查询对应的口味信息的id
        LambdaQueryWrapper<DishFlavor> LambdaQueryWrapper
                = new LambdaQueryWrapper<>();
        for (Long id:ids) {
            LambdaQueryWrapper.eq(DishFlavor::getDishId,id);
            dishFlavorService.remove(LambdaQueryWrapper);
        }

    }

然后controller写上

 /**
     *删除
     * 请求 URL: http://localhost:8080/dish?ids=1568491426675707905
     * 请求方法: DELETE
     * @param ids
     * @return
     */
    @DeleteMapping
    public R<String> myDelete(String ids){

        //把ids切割成List
        //.stream().map(s -> Long.parseLong(s.trim())).collect(Collectors.toList())
        List<String> List = Arrays.asList(ids.split(","));
        List<Long> idsList = List.stream().map(s -> Long.parseLong(s.trim())).collect(Collectors.toList());

        dishService.deleteWithFlavor(idsList);
        return R.success("删除成功");
    }

7.6停售和批量停售/起售

停售
请求 URL: http://localhost:8080/dish/status/0?ids=1413384757047271425
请求方法: POST

/**
     * `停售`
     * 请求 URL: `http://localhost:8080/dish/status/0?ids=1413384757047271425`
     * 请求方法: `POST`
     */
    @PostMapping("/status/0")
    public R<String> stop(String ids){
        List<String> List = Arrays.asList(ids.split(","));
        List<Long> idsList = List.stream().map(s -> Long.parseLong(s.trim())).collect(Collectors.toList());
        final Dish dish = new Dish();
        for (Long id:idsList) {
            dish.setId(id);
            dish.setStatus(0);
            dishService.updateById(dish);
        }
        return R.success("停售成功");
    }

起售
请求 URL: http://localhost:8080/dish/status/1?ids=1413384757047271425,1413385247889891330,1413342036832100354,1397862477831122945
请求方法: POST

/**
     * 起售
     * @param ids
     * @return
     */
    @PostMapping("/status/1")
    public R<String> starting (String ids){

        List<String> List = Arrays.asList(ids.split(","));
        List<Long> idsList = List.stream().map(s -> Long.parseLong(s.trim())).collect(Collectors.toList());
        final Dish dish = new Dish();
        for (Long id:idsList) {
            dish.setId(id);
            dish.setStatus(1);
            dishService.updateById(dish);
        }
        return R.success("起售成功");
    }


八、套餐管理 🐉

在这里插入图片描述

数据模型
setmal 套餐表
setmeal_dish 套餐关系表

准备工作
1.setmal 和 SetmealDish 实体类
mapper接口、service(业务层接口)、
serviceimpl(业务层实现类)、controller(控制层)

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

    @Autowired
    private SetmealService setmealService;

    @Autowired
    private setmealDishService setmealDishService;
    
}

8.1新增套餐

在这里插入图片描述

前面有很多功能已经写完了
现在应该写下面的功能
请求 URL: http://localhost:8080/dish/list?categoryId=1568032693294288897
请求方法: GET

在这里插入图片描述

/**
     * 根据条件查询对应的菜品数据
     * @param dish
     * @return
     */
    @GetMapping("/list")
    public R<List<Dish>> listR(Dish dish){
        //查询条件对象
         LambdaQueryWrapper<Dish> lambdaQueryWrapper
                = new LambdaQueryWrapper();
        lambdaQueryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
        //添加起售状态的条件
        lambdaQueryWrapper.eq(Dish::getStatus,1);
        //添加排序
        lambdaQueryWrapper.orderByAsc(Dish::getSort).orderByAsc(Dish::getUpdateTime);

        final List<Dish> list = dishService.list(lambdaQueryWrapper);

        return R.success(list);
    }
}

在这里插入图片描述
然后就是提交保存

请求 URL: http://localhost:8080/setmeal
请求方法: POST
提交方式:json
在这里插入图片描述

所以我们应该创建一个能接收此对象的tdo

@Data
public class SetmealDto extends Setmeal {

    private List<SetmealDish> setmealDishes;

    private String categoryName;
}

由于控制两张表 所以我们创建自己的service

@Service
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal>
implements SetmealService {
   
    @Autowired
    private SetmealDishService setmealDishService;
    /**
     * 新增套餐 同时 保存套餐和菜品的关联关系
     * @param setmealDto
     */
    @Override
    @Transactional
    public void saveWitDish(SetmealDto setmealDto) {
        //保存基本信息 执行Setmeal表
        this.save(setmealDto);
        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
        setmealDishes.stream().map(item->{
            item.setSetmealId(setmealDto.getId());
            return item;
        }).collect(Collectors.toList());
        //保存套餐和菜品的关联关系 执行Setmeal_Dish表
        setmealDishService.saveBatch(setmealDishes);
        
    }
}

然后在controller中

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

        //套餐表插入数据  套餐关系表也要插入数据
        setmealService.saveWitDish(setmealDto);
        return R.success("添加成功");
    }

8.2 分页查询

请求 URL: http://localhost:8080/setmeal/page?page=1&pageSize=10
请求方法: GET

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

        //分页构造器
        final Page<Setmeal> objectPage = new Page<>(page,pageSize);
        final Page<SetmealDto> DtoPage = new Page<>(page,pageSize);
        LambdaQueryWrapper<Setmeal> queryWrapper
                =new LambdaQueryWrapper<>();
        //添加查询条件
        queryWrapper.like(name!=null,Setmeal::getName,name);
        //添加排序条件
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);
        setmealService.page(objectPage,queryWrapper);
        
        //对象拷贝
        BeanUtils.copyProperties(objectPage,DtoPage,"records");
        final List<Setmeal> records = objectPage.getRecords();
        List<SetmealDto> list=records.stream().map(item->{
            SetmealDto setmealDto=new SetmealDto();
            BeanUtils.copyProperties(item,setmealDto);
            //分类id
            final Long categoryId = item.getCategoryId();
            //根据分类的id查询
            final Category byId = categoryService.getById(categoryId);
            if(byId!=null){
                setmealDto.setCategoryName(byId.getName());
            }
            return setmealDto;
        }).collect(Collectors.toList());

        DtoPage.setRecords(list);
        return R.success(DtoPage);
    }

8.3(批量)删除套餐

请求 URL: http://localhost:8080/setmeal?ids=1568959917405011969
请求方法: DELETE
注意 起售套餐不能删除
操作两张表写自己的逻辑在service中

/**
     *  删除套餐还应该删除对应的关联关系
     *
     * @param ids
     */
    @Transactional
    @Override
    public void removeWithDish(List<Long> ids) {
        //只能删除停售的
        LambdaQueryWrapper<Setmeal> queryWrapper
                =new LambdaQueryWrapper<>();
        queryWrapper.in(Setmeal::getId,ids);
        queryWrapper.eq(Setmeal::getStatus,1);//售卖状态
        final int count = this.count(queryWrapper);
        if(count>0){
            //如果不能删除,那就抛出异常
            throw new CustomException("套餐在售卖中,不能删除");
        }
        //如果可以删除,先删除套餐中的数据
        this.removeByIds(ids);
        //删除对应关系中的数据
        LambdaQueryWrapper<SetmealDish> queryWrapper1
                =new LambdaQueryWrapper<>();
        queryWrapper1.in(SetmealDish::getSetmealId,ids);
        setmealDishService.remove(queryWrapper1);
    }

然后在controller中写入

 /**
     * 删除套餐
     * @param ids
     * @return
     */
    @DeleteMapping
    public R<String> delete(@RequestParam List<Long> ids){
        //删除套餐还应该删除对应的关联关系
        setmealService.removeWithDish(ids);
        return R.success("套餐删除成功");
    }

8.4修改套餐

请求 URL: http://localhost:8080/setmeal/1568959917405011969
请求方法: GET
由于都是多表查询我们写自己的查询方法
在对应deservice写代码 并在对应的impl写上如下代码

/**
     * 修改套餐还应该修改对应的关联关系
     * @param id
     * @return
     */
    @Override
    public SetmealDto updateWithDish(Long id) {

        //1.查套餐基本信息
        final Setmeal setmeal = this.getById(id);
        final SetmealDto setmealDto = new SetmealDto();
        //1.5使用对象拷贝
        BeanUtils.copyProperties(setmeal,setmealDto);
        //2.查套餐关联的信息,从Setmeal_Dish查
        //2.1条件构造器
        LambdaQueryWrapper<SetmealDish> QueryWrapper
                =new LambdaQueryWrapper<>();
        //2.2条件查询
        QueryWrapper.eq(SetmealDish::getSetmealId,setmeal.getId());
        final List<SetmealDish> list
                = setmealDishService.list(QueryWrapper);
        setmealDto.setSetmealDishes(list);
        return  setmealDto;
    }

然后在controller写如下逻辑就okl

 /**
     * 修改套餐
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public R<SetmealDto> update(@PathVariable Long id){
        //log.info("{}",id);
        final SetmealDto setmealDto = setmealService.updateWithDish(id);
        return R.success(setmealDto);
    }

然后点击提交会发生新请求
请求 URL: http://localhost:8080/setmeal
请求方法: PUT
请求方式JSON
在这里插入图片描述
思路
先更新setmeal
然后删除原来的对应关系
最后新设置对应关系

/**
     * //更新菜品信息,同时更新对应的口味信息
     * @param setmealDto
     */
    @Override
    public void updateWithDish(SetmealDto setmealDto) {

        //setmealDto
        this.updateById(setmealDto);

        //先清理setmealDish
        LambdaQueryWrapper<SetmealDish> LambdaQueryWrapper
                = new LambdaQueryWrapper<>();
        LambdaQueryWrapper.eq(SetmealDish::getSetmealId,setmealDto.getId());
        setmealDishService.remove(LambdaQueryWrapper);
        //setmealDish
        List<SetmealDish> flavors = setmealDto.getSetmealDishes();

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

在controller调用

/**
     * 更新套餐
     * @param setmealDto
     * @return
     */
    @PutMapping
    public R<String> saveUpdate(@RequestBody SetmealDto setmealDto){
        //套餐表插入数据  套餐关系表也要插入数据
        setmealService.updateWithDish(setmealDto);
        return R.success("修改成功");
    }

8.5(批量)停售和起售

停售
请求 URL: http://localhost:8080/setmeal/status/0?ids=1568954574184722434,1415580119015145474
请求方法: POST

/**
     *停售
     * @param ids
     * @return
     */
    @PostMapping("/status/0")
    public R<String> stop(@RequestParam List<Long> ids){
        //这里我们使用@RequestParam参数
         Setmeal setmeal = new Setmeal();
        for (long id: ids ) {
            setmeal.setStatus(0);
            setmeal.setId(id);
            setmealService.updateById(setmeal);
        }

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

起售
请求 URL: http://localhost:8080/setmeal/status/1?ids=1568954574184722434,1415580119015145474
请求方法: POST

/**
     *起售
     * @param ids
     * @return
     */
    @PostMapping("/status/1")
    public R<String> starting(@RequestParam List<Long> ids){
        //这里我们使用@RequestParam参数
        Setmeal setmeal = new Setmeal();
        for (long id: ids ) {
            setmeal.setStatus(1);
            setmeal.setId(id);
            setmealService.updateById(setmeal);
        }

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

九、订单明细

准备相应的环境实
体类OrderDetail订单明细
OrderDetailMapperOrderDetailServiceOrderDetailServiceImpl
在这里插入图片描述

9.1查询功能

请求 URL: http://localhost:8080/order/page?page=1&pageSize=10
请求方法: GET

创建tdo

@Data
public class OrdersDto extends Orders {

    private String userName;

    private String phone;

    private String address;

    private String consignee;

    private List<OrderDetail> orderDetails;
	
}

编写controller

 /**
     * 查询订单详细
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("page")
    public R<Page> listPage(int page ,int pageSize){
        Page pageInfo = new Page(page, pageSize);//前端传过来分页的当前码和分页的每一页的大小
        Page OrdersInfo=new Page(page, pageSize);

        LambdaQueryWrapper<Orders> queryWrapper
                =new LambdaQueryWrapper<>();

        queryWrapper.orderByDesc(Orders::getOrderTime);

        orderService.page(pageInfo,queryWrapper);

        //对象拷贝
        BeanUtils.copyProperties(pageInfo,OrdersInfo);
        final List<Orders> records = pageInfo.getRecords();
        List<OrdersDto> list=records.stream().map(item->{
            OrdersDto ordersDto=new OrdersDto();
            final Long userId = item.getId();
            BeanUtils.copyProperties(item,ordersDto);

            //根据分类的id查询
            final Orders byId = orderService.getById(userId);
            if(byId!=null){
                //getConsignee是下单用户
                ordersDto.setUserName(byId.getConsignee());
            }
            return ordersDto;
        }).collect(Collectors.toList());
        OrdersInfo.setRecords(list);
        return R.success(OrdersInfo);

    }

9.2修改订单状态

请求 URL: http://localhost:8080/order
请求方法: PUT
提交json数据
在这里插入图片描述
功能
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码部分

 /**
     * 修改订单状态
     * @param orders
     * @return
     */
    @PutMapping
    public R<String> update(@RequestBody Orders orders){

        //查看id
        LambdaQueryWrapper<Orders> queryWrapper
                =new LambdaQueryWrapper<>();
        queryWrapper.eq(Orders::getId,orders.getId());

        //修改对应id的状态
        final Orders one = orderService.getOne(queryWrapper);
        one.setStatus(orders.getStatus());
        orderService.updateById(one);
        return R.success("完成");
    }

十 、手机验证码登陆

用于移动端的登陆
使用阿里云的短信服务

步骤:
1.导入maven坐标

<dependency>
  <groupId>com.aliyun</groupId>
  <artifactId>dysmsapi20170525</artifactId>
  <version>2.0.18</version>
</dependency>

2.导入代码 在官方文档那里复制过来

// This file is auto-generated, don't edit it. Thanks.
package com.aliyun.sample;

import com.aliyun.tea.*;

public class Sample {

    /**
     * 使用AK&SK初始化账号Client
     * @param accessKeyId
     * @param accessKeySecret
     * @return Client
     * @throws Exception
     */
    public static com.aliyun.dysmsapi20170525.Client createClient(String accessKeyId, String accessKeySecret) throws Exception {
        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
                // 您的 AccessKey ID
                .setAccessKeyId(accessKeyId)
                // 您的 AccessKey Secret
                .setAccessKeySecret(accessKeySecret);
        // 访问的域名
        config.endpoint = "dysmsapi.aliyuncs.com";
        return new com.aliyun.dysmsapi20170525.Client(config);
    }

    public static void main(String[] args_) throws Exception {
        java.util.List<String> args = java.util.Arrays.asList(args_);
        com.aliyun.dysmsapi20170525.Client client = Sample.createClient("accessKeyId", "accessKeySecret");
        com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest = new com.aliyun.dysmsapi20170525.models.SendSmsRequest()
                .setSignName("阿里云短信测试")
                .setTemplateCode("SMS_154950909")
                .setPhoneNumbers("15682522894")
                .setTemplateParam("{\"code\":\"1234\"}");
        com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
        try {
            // 复制代码运行请自行打印 API 的返回值
            client.sendSmsWithOptions(sendSmsRequest, runtime);
        } catch (TeaException error) {
            // 如有需要,请打印 error
            com.aliyun.teautil.Common.assertAsString(error.message);
        } catch (Exception _error) {
            TeaException error = new TeaException(_error.getMessage(), _error);
            // 如有需要,请打印 error
            com.aliyun.teautil.Common.assertAsString(error.message);
        }        
    }
}

随机验证码生成器

package com.xbfinal.reggie.Ulits;

import java.util.Random;

/**
 * 随机生成验证码工具类
 */
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;
    }
}

然后创建对应的实体类User
UserMapper、UserSerice、UserSericeImpl、Usercontroller

10.1短信发送

添加过滤器
在这里插入图片描述

				"/user/sendMsg",
                "/user/login"

过滤器新增代码

//4.判断移动端用户是否登陆
        final Object user = request.getSession().getAttribute("user");
        if(user!= null){
            //插入基于ThreadLocal封装的工具类
            final Long userId
                    = (Long)request.getSession().getAttribute("user");
            BaseContext.setCurrentId(userId);//绑定当前id
            //放行
            filterChain.doFilter(request,response);
            return;
        }

数据模型
>Uuser
一共有两个请求
1./user/sendMsg
post

 /**
     * 发送验证码
     * @param user
     * @return
     */
    @PostMapping("/sendMsg")
    public R<String> sendMsg(@RequestBody User user,
                             HttpSession session){
        log.info("进来");
        //获取手机号
        final String phone = user.getPhone();
        //判断手机号不为空
        if(StringUtils.isNotEmpty(phone)){
            //生成4位验证码
            final String code =
                    ValidateCodeUtils.generateValidateCode(4).toString();
            System.out.println("==========");
            System.out.println(code);
            System.out.println("==========");
            //调用阿里云的短信服务发送短信api
            //SMSUtils.createClient()
            //需要保存一下验证码,后面用来验证
          session.setAttribute(phone,code);
          return R.success("发送成功");
        }

        return R.error("短信发送失败");
    }

第二次请求

请求 URL: http://localhost:8080/user/login
请求方法: POST
提交方式json
在这里插入图片描述

十一、用户首页

业务功能
展示菜品和套餐
添加购物车和删除购物车 和清空购物车
结算页面

11.1导入用户地址簿相关功能的代码

用户登陆后可以管理自己的地址,可以有多个但是只有一个是默认的地址信息
address_book地址管理的数据库
环境搭建
1.创建实体类AddressBook
2.对应的mapper service serviceimpl

package com.xbfinal.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.xbfinal.reggie.common.BaseContext;
import com.xbfinal.reggie.common.R;
import com.xbfinal.reggie.pojo.AddressBook;
import com.xbfinal.reggie.service.AddressBookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 地址簿管理
 */
@Slf4j
@RestController
@RequestMapping("/addressBook")
public class AddressBookController {

    @Autowired
    private AddressBookService addressBookService;

    /**
     * 新增地址簿
     */
    @PostMapping
    public R<AddressBook> save(@RequestBody AddressBook addressBook) {
        
        addressBook.setUserId(BaseContext.getCurrentId());
        log.info("addressBook:{}", addressBook);
        addressBookService.save(addressBook);
        return R.success(addressBook);
    }

    /**
     * 设置默认地址
     */
    @PutMapping("default")
    public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) {
        log.info("addressBook:{}", addressBook);
        LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
        wrapper.set(AddressBook::getIsDefault, 0);//设置所有的Default为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);
    }

    /**
     * 根据id查询地址
     */
    @GetMapping("/{id}")
    public R get(@PathVariable Long id) {
        AddressBook addressBook = addressBookService.getById(id);
        if (addressBook != null) {
            return R.success(addressBook);
        } else {
            return R.error("没有找到该对象");
        }
    }

    /**
     * 查询默认地址
     */
    @GetMapping("default")
    public R<AddressBook> getDefault() {
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
        queryWrapper.eq(AddressBook::getIsDefault, 1);

        //SQL:select * from address_book where user_id = ? and is_default = 1
        AddressBook addressBook = addressBookService.getOne(queryWrapper);

        if (null == addressBook) {
            return R.error("没有找到该对象");
        } else {
            return R.success(addressBook);
        }
    }

    /**
     * 查询指定用户的全部地址
     */
    @GetMapping("/list")
    public R<List<AddressBook>> list(AddressBook addressBook) {
        addressBook.setUserId(BaseContext.getCurrentId());
        log.info("addressBook:{}", addressBook);

        //条件构造器
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId());
        queryWrapper.orderByDesc(AddressBook::getUpdateTime);

        //SQL:select * from address_book where user_id = ? order by update_time desc
        return R.success(addressBookService.list(queryWrapper));
    }
}

11.1.1修改收获地址 🐉

请求 URL: http://localhost:8080/addressBook
请求方法: PUT
提交json数据
在这里插入图片描述

11.2菜品展示

1.请求 URL: http://localhost:8080/category/list
请求方法: GET
这个代码已经写完了

3.再发一个ajax请求查询当前分类下的菜品
修改以前的方法
请求 URL: http://localhost:8080/dish/list?categoryId=1568032693294288897&status=1
请求方法: GET

 /**
     * 根据条件查询对应的菜品数据
     * @param dish
     * @return
     */
//    @GetMapping("/list")
//    public R<List<Dish>> listR(Dish dish){
//        //查询条件对象
//         LambdaQueryWrapper<Dish> lambdaQueryWrapper
//                = new LambdaQueryWrapper();
//        lambdaQueryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
//        //添加起售状态的条件
//        lambdaQueryWrapper.eq(Dish::getStatus,1);
//        //添加排序
//        lambdaQueryWrapper.orderByAsc(Dish::getSort).orderByAsc(Dish::getUpdateTime);
//
//        final List<Dish> list = dishService.list(lambdaQueryWrapper);
//
//        return R.success(list);
//    }
    @GetMapping("/list")
    public R<List<DishDto>> listR(Dish dish){
        //查询条件对象
        LambdaQueryWrapper<Dish> lambdaQueryWrapper
                = new LambdaQueryWrapper();
        lambdaQueryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
        //添加起售状态的条件
        lambdaQueryWrapper.eq(Dish::getStatus,1);
        //添加排序
        lambdaQueryWrapper.orderByAsc(Dish::getSort).orderByAsc(Dish::getUpdateTime);

        final List<Dish> list = dishService.list(lambdaQueryWrapper);
        List<DishDto>dtoList  = list.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);
            }
            final Long id = item.getId();//当前菜品id
            LambdaQueryWrapper<DishFlavor>lambdaQueryWrapper1
                    =new LambdaQueryWrapper<>();
            lambdaQueryWrapper1.eq(DishFlavor::getDishId,id);
            final List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper1);
            dishDto.setFlavors(dishFlavorList);

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

        return R.success(dtoList);
    }

SetmealController添加代码来展示套餐
请求 URL: http://localhost:8080/setmeal/list?categoryId=1413342269393674242&status=1
请求方法: GET

 /**
     * 根据条件查询套餐数据
     * @param setmeal
     * @return
     */
    @GetMapping("/list")
    public R<List<Setmeal>> listR(Setmeal setmeal){
        LambdaQueryWrapper<Setmeal> queryWrapper
                =new LambdaQueryWrapper<>();
        queryWrapper.eq(setmeal.getCategoryId()!=null,Setmeal::getCategoryId,setmeal.getCategoryId());
        queryWrapper.eq(setmeal.getStatus()!=null,Setmeal::getStatus,setmeal.getStatus());
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);
        final List<Setmeal> list = setmealService.list(queryWrapper);
        return R.success(list);
    }

3.请求 URL: http://localhost:8080/shoppingCart/list
请求方法: GET

/**
     * 查看购物车
     * @return
     */
    @GetMapping("/list")
    public R<List<ShoppingCart>> list(){
        log.info("查看购物车...");

        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
        queryWrapper.orderByAsc(ShoppingCart::getCreateTime);

        List<ShoppingCart> list = shoppingCartService.list(queryWrapper);

        return R.success(list);
    }

在这里插入图片描述

11.3加入购物车和从购物车减去

加入购物车
请求 URL: http://localhost:8080/shoppingCart/add
请求方法: POST
在这里插入图片描述

/**
     * 添加购物车
     * @param shoppingCart
     * @return
     */
    @PostMapping("/add")
    public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){
        log.info("购物车数据:{}",shoppingCart);

        //设置用户id,指定当前是哪个用户的购物车数据
        Long currentId = BaseContext.getCurrentId();
        shoppingCart.setUserId(currentId);

        Long dishId = shoppingCart.getDishId();

        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,currentId);

        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){
            //如果已经存在,就在原来数量基础上加一
            Integer number = cartServiceOne.getNumber();
            cartServiceOne.setNumber(number + 1);
            shoppingCartService.updateById(cartServiceOne);
        }else{
            //如果不存在,则添加到购物车,数量默认就是一
            shoppingCart.setNumber(1);
            shoppingCart.setCreateTime(LocalDateTime.now());
            shoppingCartService.save(shoppingCart);
            cartServiceOne = shoppingCart;
        }

        return R.success(cartServiceOne);
    }

从购物车减去一个产品数量
在这里插入图片描述
请求 URL: http://localhost:8080/shoppingCart/sub
请求方法: POST
提交json数据
在这里插入图片描述

/**
     * 当前商品从购物车删去
     * @param shoppingCart
     * @return
     */
    @PostMapping("sub")
    public R<ShoppingCart> sub(@RequestBody ShoppingCart shoppingCart){

        log.info("减去购物车{}",shoppingCart);
        //设置用户id,指定当前是哪个用户的购物车数据
        Long currentId = BaseContext.getCurrentId();
        shoppingCart.setUserId(currentId);

        Long dishId = shoppingCart.getDishId();

        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,currentId);

        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.getNumber() >= 1){
            //如果已经存在,就在原来数量基础上减一一
            Integer number = cartServiceOne.getNumber();
            cartServiceOne.setNumber(number - 1);
            shoppingCartService.updateById(cartServiceOne);
        }
        //如果小于1九清空当前商品
        if(cartServiceOne.getNumber()<1){
            shoppingCartService.removeById(cartServiceOne);
        }

        return R.success(cartServiceOne);
    }

11.4查询及清空购物车

查看购物车
请求 URL: http://localhost:8080/shoppingCart/list
请求方法: GET
提交json格式
在这里插入图片描述

/**
     * 查看购物车
     * @return
     */
    @GetMapping("/list")
    public R<List<ShoppingCart>> list(){

        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
        queryWrapper.orderByAsc(ShoppingCart::getCreateTime);

        List<ShoppingCart> list = shoppingCartService.list(queryWrapper);

        return R.success(list);
    }

清空购物车
请求 URL: http://localhost:8080/shoppingCart/clean
请求方法: DELETE
提交json数据
在这里插入图片描述

/**
     * 清空购物车
     * @return
     */
    @DeleteMapping("/clean")
    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("清空购物车成功");
    }

11.5用户下单

在这里插入图片描述

支付功能
请求 URL: http://localhost:8080/order/submit
请求方法: POST
提交json格式
在这里插入图片描述

控制几张表封装自己的方法

@Transactional
    public void submit(Orders orders) {
        //获得当前用户id
        Long userId = BaseContext.getCurrentId();

        //查询当前用户的购物车数据
        LambdaQueryWrapper<ShoppingCart> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ShoppingCart::getUserId,userId);
        List<ShoppingCart> shoppingCarts = shoppingCartService.list(wrapper);

        if(shoppingCarts == null || shoppingCarts.size() == 0){
            throw new CustomException("购物车为空,不能下单");
        }

        //查询用户数据
        User user = userService.getById(userId);

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

        long orderId = IdWorker.getId();//订单号

        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(wrapper);
    }
/**
     * 用户下单
     * @param orders
     * @return
     */
    @PostMapping("/submit")
    public R<String> submit(@RequestBody Orders orders){
        log.info("订单数据:{}",orders);
        orderService.submit(orders);
        return R.success("下单成功");
    }

11.6查看订单

请求 URL: http://localhost:8080/order/userPage?page=1&pageSize=1
请求方法: GET

 /**
     * 个人订单查询
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("/userPage")
    public R<Page> userPage(int page ,int pageSize){


        Page pageInfo = new Page(page, pageSize);//前端传过来分页的当前码和分页的每一页的大小
        LambdaQueryWrapper<Orders> queryWrapper
                =new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(Orders::getOrderTime);
        orderService.page(pageInfo,queryWrapper);
        return R.success(pageInfo);
    }

最后
基础部分就结束了,欢迎大家指正文中的不足

  • 欢迎关注本专栏《项目专栏》 后期更新进阶部分(redis、 nginx)。
  • 🔥如果感觉博主的文章还不错的话,👍点赞👍 + 👀关注👀 + 🤏收藏🤏

网站公告

今日签到

点亮在社区的每一天
去签到