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

news/2024/5/17 15:45:01/文章来源:https://blog.csdn.net/weixin_52062043/article/details/126682528

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

瑞吉外卖项目学习笔记

  • 零、项目简介
  • 一、各种注解的解释
    • 1.1Lombok注解
      • @Slf4j
    • 1.2spring框架注解
      • @Controller, @Repository, @Service
      • @ResponseBody、@RestController
      • @RequestBody、@RequestParam、@PathVariable
      • @ServletComponentScan
      • @ControllerAdvice
      • @Value
    • 1.3javax.servlet注解
      • @WebFilter
    • 1.4MybatisPlus注解
      • @TableField
  • 二、环境搭建
    • 2.1配置application.yml文件
    • 2.2配置类config
      • WebMvcConfigurationSupport说明
  • 三、系统登陆功能
    • 3.1需求分析
    • 3.2后端实现
      • 3.2.1创建基本的结构
      • 3.2.2创建返回结果类 R
      • 3.2.3代码开发
      • 3.3.4登陆优化
  • 四、后台退出功能
    • 4.1需求分析
    • 4.2代码实现
  • 五、员工管理
    • 5.1新增员工
    • 5.2新增员工的优化
    • 5.3员工信息分页查询 🥇
    • 5.4禁用/启用/编辑员工账号
    • 5.5功能完善(公共字段填充)
  • 六、分类管理业务
    • 6.1搭建基础的环境
    • 6.2新增分类
    • 6.3分页查询
    • 6.4删除分类
    • 6.5删除功能完善 🥇
    • 6.6 修改分类
  • 七、菜品管理 🐉
    • 7.1文件上传/下载 🥇
    • 7.2新增菜品
      • 菜品分类请求
      • 提交添加菜品表单🉑
    • 7.3菜品信息分页查询🚹
    • 7.4修改菜品
    • 7.5删除和批量删除
    • 7.6停售和批量停售/起售
  • 八、套餐管理 🐉
    • 8.1新增套餐
    • 8.2 分页查询
    • 8.3(批量)删除套餐
    • 8.4修改套餐
    • 8.5(批量)停售和起售
  • 九、订单明细
    • 9.1查询功能
    • 9.2修改订单状态
  • 十 、手机验证码登陆
    • 10.1短信发送
  • 十一、用户首页
    • 11.1导入用户地址簿相关功能的代码
      • 11.1.1修改收获地址 🐉
    • 11.2菜品展示
    • 11.3加入购物车和从购物车减去
    • 11.4查询及清空购物车
    • 11.5用户下单
    • 11.6查看订单

零、项目简介

本项目可以练习自己的思想,是一个学习的好项目,因为老师有让学生自己动手的部分比如菜品管理和分类管理有一部分要求自己写,可以检验一下自己同东没有
项目视频教程搜索 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.Driverurl: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: 0615mybatis-plus:configuration:#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-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*/@Overrideprotected 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 EmployeeServiceImplextends 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 {@Autowiredprivate 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存入sessionrequest.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();@Overridepublic void doFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request=(HttpServletRequest)servletRequest;HttpServletResponse  response=(HttpServletResponse)servletResponse;//1.获取本次请求的urlfinal 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(\"/\")");//Redirectresponse.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的当前员工的idrequest.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*/@PostMappingpublic 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分页插件*/@Configurationpublic class MybatisPlusConfig {@Beanpublic 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的分页插件//分页构造器 PagePage pageInfo = new Page(page,pageSize);//条件构造器 lambdaQueryWrapperfinal 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);//处理完后会自己封装到pageInforeturn R.success(pageInfo);}}

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

需求分析

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

禁用/启用代码部分

@PutMappingpublic 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*/@Overrideprotected 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*/@Overridepublic void insertFill(MetaObject metaObject) {//threadLocal并不是一个线程 jdk提供 具有线程隔离效果//常用方法 set   getmetaObject.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*/@Overridepublic 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*/@PostMappingpublic 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*/@DeleteMappingpublic 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 CategoryServiceImplextends ServiceImpl<CategoryMapper, Category>implements CategoryService {@Autowiredprivate DishService dishService;@AutowiredSetmealService 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*/@PutMappingpublic 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 {@Autowiredprivate DishService dishService;@Autowiredprivate 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//接受页面传来的flavorsprivate 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 {@AutowiredDishFlavorService dishFlavorService;/*** 新增菜品的同时保存口味的数据* 同时操作多张表 所以需要事务控制* 要是事务 @Transactional生效得在启动类开启事务的支持* @EnableTransactionManagement* @param dishDto*/@Override@Transactionalpublic void saveWithFlavor(DishDto dishDto) {//先保存菜品的基本信息到菜品表this.save(dishDto);//菜品idfinal Long id = dishDto.getId();//保存菜品口味表List<DishFlavor> flavors=dishDto.getFlavors();flavors=flavors.stream().map(item->{item.setDishId(id);return item;}).collect(Collectors.toList());//保存菜品口味数据到菜品口味表dish_favordishFlavorService.saveBatch(dishDto.getFlavors());}
}

然后controller代码如下

/*** 添加菜品* @param dishDto* @return*/@PostMappingpublic 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@Transactionalpublic 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@Transactionalpublic void updateWithFlavor(DishDto dishDto) {//更新dishthis.updateById(dishDto);//先清理dish_flavorLambdaQueryWrapper<DishFlavor> LambdaQueryWrapper = new LambdaQueryWrapper<>();LambdaQueryWrapper.eq(DishFlavor::getDishId,dishDto.getId());dishFlavorService.remove(LambdaQueryWrapper);//添加dish_flavorList<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*/@PutMappingpublic 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@Transactionalpublic void deleteWithFlavor(List<Long> ids){//删除菜品信息this.removeByIds(ids);//查询对应的口味信息的idLambdaQueryWrapper<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*/@DeleteMappingpublic 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 {@Autowiredprivate SetmealService setmealService;@Autowiredprivate 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 {@Autowiredprivate SetmealDishService setmealDishService;/*** 新增套餐 同时 保存套餐和菜品的关联关系* @param setmealDto*/@Override@Transactionalpublic 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*/@PostMappingpublic 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);//分类idfinal 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@Overridepublic 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*/@DeleteMappingpublic 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*/@Overridepublic 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*/@Overridepublic void updateWithDish(SetmealDto setmealDto) {//setmealDtothis.updateById(setmealDto);//先清理setmealDishLambdaQueryWrapper<SetmealDish> LambdaQueryWrapper= new LambdaQueryWrapper<>();LambdaQueryWrapper.eq(SetmealDish::getSetmealId,setmealDto.getId());setmealDishService.remove(LambdaQueryWrapper);//setmealDishList<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*/@PutMappingpublic 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*/@PutMappingpublic R<String> update(@RequestBody Orders orders){//查看idLambdaQueryWrapper<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) {// 如有需要,请打印 errorcom.aliyun.teautil.Common.assertAsString(error.message);} catch (Exception _error) {TeaException error = new TeaException(_error.getMessage(), _error);// 如有需要,请打印 errorcom.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);//生成随机数,最大为9999if(code < 1000){code = code + 1000;//保证随机数为4位数字}}else if(length == 6){code = new Random().nextInt(999999);//生成随机数,最大为999999if(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 {@Autowiredprivate AddressBookService addressBookService;/*** 新增地址簿*/@PostMappingpublic 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 = 1AddressBook 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 descreturn 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();//当前菜品idLambdaQueryWrapper<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格式
在这里插入图片描述

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

@Transactionalpublic void submit(Orders orders) {//获得当前用户idLong 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)。
  • 🔥如果感觉博主的文章还不错的话,👍点赞👍 + 👀关注👀 + 🤏收藏🤏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_6974.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android ActionBar

android的ActionBar是3.0才推出的,3.0之前称之为AppBar。为了向后兼容,ActionBar位于Android的支持库AppCompat中,所以要使用ActionBar先必须依赖AppCompat库(现在新建的工程默认都依赖此库了)implementation androidx.appcompat:appcompat:1.3.0如果没有在主题Theme中或Ac…

【小月电子】安路国产FPGA开发板系统学习教程-LESSON6按键消抖

按键消抖例程讲解若要观看该博客配套的视频教程&#xff0c;可点击此链接根据多年工作经验&#xff0c;总结出的FPGA的设计流程&#xff0c;概括起来总共有以上12步&#xff0c;其中根据项目难易度可省去其中一些步骤。比如非常简单的项目&#xff0c;我们可以省去虚线框里面的…

java基于微信小程序的电影院购票选座 uniapp 小程序

随着移动应用技术的发展,越来越多的用户借助于移动手机、电脑完成生活中的事务,许多的传统行业也更加重视与互联网的结合,由于城镇人口的增加,人们去电影院总是排着长长的队伍,对于时间紧的人是一个非常头痛的事情,有的人可能就是排队也要用去半天时间,人们为了缓解排队就购票的…

TFT-eSPI入门使用教程

一、准备资料开发板:ESP32-S3 屏驱动是:ST7789_DRIVER 开发环境:VS Code + PlatformIO注意:以上是我使用的环境,不一定需要和是使用的东西一样,这里主要是学习TFT-eSPI开源驱 二、获取TFT-eSPI GitHub:https://github.com/Bodmer/TFT_eSPI 三、配置User_Setup.h文件 在路…

【软件与系统安全笔记】二、软件与系统安全基础

【软件与系统安全】二、软件与系统安全基础 这是《【软件与系统安全】笔记与期末复习》系列中的一篇 2022-01-17 第二次课 2022-02-21 第三次课前部分 计算机安全的目标&#xff1a; 防止信息“遭遇不测事件”, 但不能阻止“好的事情”发生&#xff08;“好的事情”包括功能性…

基于Android studio+SSH的单词记忆(背单词)APP设计

目录 引言 3 1.1. 项目介绍 3 课程设计选题《单词记忆APP》 3 1.2. 项目的目的和意义 3 1.3. 相关技术介绍 5 1.3.1. ionic angular cordova混合框架 5 1.4. 后端SSH框架 6系统需求分析 8 2.1. 软件功能 8 2.1.1. 需求分析 8 2.2. 功能性需求 9项目介绍 10 3.1. 系统的开发环…

手机上有没有跨平台轻量级的备忘录?

当你在读书时,如果想要随手记录读书笔记,那你会采取什么方式做读书笔记呢?当你在工作时,如果想要随后记录工作注意事项或常用的一些工作资料,那你会如何记录呢?相信有不少网友都会使用手机上的备忘录软件来做读书笔记,随手记事;而在电脑上会直接使用TXT或Word来记录工作…

Apple Xcode 14 (14A309) 正式版发布(含下载)

请访问原文链接&#xff1a;Apple Xcode 14&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;www.sysin.org Xcode 14 包含了在所有 Apple 平台上开发、测试和分发 App 所需的一切资源。利用 Swift 和 SwiftUI 的易用性与强大能力以及全新…

微信抖音快手三合一壁纸小程序源码_后端管理设置功能丰富

介绍&#xff1a; 这是一款支持快手端,微信端,抖音三端的一个壁纸类型的小程序 一个后台同时管理三端,内有丰富的后端设置 安装也是特别的简单(压缩包里面也有文本安装教程) 另外支持静态壁纸显示,动态壁纸显示或者头像表情包等等 前端自适应识别所属内容然后根据内容来自…

Win11右键显示更多选项设置教程

Win11如何设置右键显示更多选项?如果你觉得每次右键菜单,都是需要点击“显示更多选项”十分麻烦,那么可以通过设置,让其直接显示出现。那么应该如何操作呢?下面小编就为大家带来具体的操作步骤,我们一起来学习下吧。Win11右键显示更多选项设置方法:1、首先用鼠标右键点击…

使用位移基本场方法对空间扩展光源进行建模

1. 摘要 利用VirtualLab Fusion的参数耦合功能可在光学设置中耦合参数。耦合的参数可重新计算系统的其他参数&#xff0c;进而自动保持系统参数间的关系。因此&#xff0c;参数耦合功能使用户可以参数设置复杂的依存关系。例如&#xff0c;在此示例中&#xff0c;我们使用参数…

234.回文链表

题目来源&#xff1a;力扣https://leetcode.cn/problems/palindrome-linked-list/solution/hui-wen-lian-biao-by-leetcode-solution/ 题目简介&#xff1a;简单的就是判断一个链表是否为回文链表 双指针 思路&#xff1a;首先就是可以想到用个双指针一个指头&#xff0c;一…

河北稳控科技DLS11 网关中继器(LTE-LoRA) 数据发送机制

河北稳控科技DLS11 网关中继器(LTE-LoRA) 数据发送机制 DLS11 是 LoRA-LTE 网关设备,专用于接收其它 LoRA 设备发来的数据包存储并在预定的时间间隔后统一发送(目前支持 VSxxx、NLM3、NLM5、NLM6 的 LoRA 数据包格式)。发送的方式有:UART、TCP、EMAIL、FTP、RF,通过设置…

高效掌握JDBC技术(三)| 三层架构理念 | 书写符合事务特性的工具类 | JUnit测试框架 | JDBC项目开发步骤

✅作者简介&#xff1a;热爱后端语言的大学生&#xff0c;CSDN内容合伙人 ✨精品专栏&#xff1a;C面向对象 &#x1f525;系列专栏&#xff1a;JDBC快速入门 文章目录1、三层架构1.1、数据访问层1.2、业务逻辑层1.2.1、组成1.3、表示层1.3.1、实现1.4、完整实现步骤2、事务及J…

Vue通知提醒框(Notification)

可自定义设置以下属性&#xff1a; 自动关闭的延时时长&#xff08;duration&#xff09;&#xff0c;单位ms&#xff0c;默认4500ms消息从顶部弹出时&#xff0c;距离顶部的位置&#xff08;top&#xff09;&#xff0c;单位像素px&#xff0c;默认24px消息从底部弹出时&…

04 访问 /staticTryFiles 或者 /staticTryFiles/ 的一些具体行为体现

前言 之前曾经做过一个测试, 测试结果如下 nginx 访问文件如果文件存在, 获取文件如果文件不存在, 文件夹存在, 获取文件夹的 index如果都不存在 响应 403/404 然后 后面更加详细的测试了一下, 梳理了一下 结论 请求 匹配到 /staticTryFiles 的时候, 查找是否有文件, 如果…

房地产进入供应链时代,供应商协同系统助力企业构建供应商全生命周期管理机制

近几年来&#xff0c;房地产企业融资环境收紧&#xff0c;行业利润下滑&#xff0c;且开发成本提升&#xff0c;全国房地产市场快速降温&#xff0c;部分开发企业出现严重困难&#xff0c;甚至信用违约&#xff0c;房产行业出现裁员、关店等现象&#xff0c;行业发展正在经受史…

玩转代码|逆向分析一下4399小游戏绕过实名认证

4399的实名认证真是越来越恶心了&#xff0c;本以为只是响应国家号召做点表面功夫&#xff0c;没想到现在他们又在网页上加了反调试。看来是4399是认真的。目录 0X00实名认证提醒 0X01分析 0X03断点发现关键元素 0X04遮罩消失 0X05快去试试吧 0X00实名认证提醒 先看一下&am…

2.java-数据背后的二进制

目录1.负数用二进制如何表示(1) 什么是补码表示法(2) 为什么用补码表示法(3) 正数相加的结果为负数2.小数的二进制(1) 小数相乘结果出错?(2) 小数二进制如何表示 1.负数用二进制如何表示 首先整数类型有 byte、short、int、long,分别占1、2、4、8个字节,表示范围如下(1) 什么…

1.Java-编程基础

目录0.前言1.数据类型和变量2.赋值3.基本运算4.条件执行5.循环6.函数的用法7.函数调用的基本原理 0.前言 什么是程序:计算机能够执行的预先写好的指令。 1.数据类型和变量 (1) java8大基本数据类型整数类型:byte/short/int/long,对应不同的取值范围,如int占4个字节,取值范…