提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
目录
4.在java/com/itheima/reggie目录下创建springboot主配置文件ReggieApplication
5.在resources文件下创建application.yml配置文件
1.在java/com/itheima/reggie目录下创建项目中需要使用到的包:
注意:测试的时候我们发现出现了问题,就是我们修改员工的状态,提示信息显示修改成功,但是我们去数据库查验证的时候,发现员工的状态码压根就没有变化,这是为什么呢?
3.分类管理的新增菜品分类(菜品分类和套餐分类请求路径一样)
前言
瑞吉外卖后台管理系统主要基于springboot和mybtis-plus框架技术,用于实现后台的账号登录和退出,以及对员工添加,饭店菜品,菜品套餐的管理,下面就来详细的分享项目的编写过程。
一、开发环境的搭建
1.数据库环境的搭建
(1)创建数据库
字符集和排序规则可以不用选,直接确定
(2)运行sql语句
鼠标单击表,然后鼠标右键选择运行sql文件
这个sql文件在b站中的课程资料中下载,推荐使用百度网盘
2.maven项目搭建
说明:该项目是通过idea将maven项目转换成springboot项目,具体的转换步骤包括6步:
其中pom文件中有4步:
1、在pom文件中添加parent父级依赖
2、在pom文件中添加spring-boot-starter核心依赖和测试依赖
3、在pom文件中添加properties属性配置
4、在pom文件中添加build打包插件配置
其他两步也很重要:
5、在reggie文件下创建springboot启动类ReggieApplication,上面要有注解@ApplicationContext(springboot主配置文件)
6、在resources文件下创建application.yml配置文件
上面是maven转换springboot项目的讲解,接下来我们进行操作
1.创建一个maven项目
然后next就可以了
3.导入pom文件
pom文件是资料自带的,直接复制。上面的四个在pom文件导入的步骤也包含在这里面
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itheima</groupId>
<artifactId>reggie_take_out</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.23</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.6.6</version>
</plugin>
</plugins>
</build>
</project>
4.在java/com/itheima/reggie目录下创建springboot主配置文件ReggieApplication
package com.itheima.reggie;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author LJM
* @create 2022/4/14
*/
@Slf4j
@SpringBootApplication
@ServletComponentScan
public class ReggieApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieApplication.class,args);
log.info("项目启动成功...");
}
}
@ServletComponentScan跟后面的过滤器有关,用来扫描那些过滤器注解,从而把过滤器创建出来
5.在resources文件下创建application.yml配置文件
server:
port: 8080
spring:
application:
# 应用的名称,选择性配置
name: reggie_take_out
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
mybatis-plus:
configuration:
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
# 把SQL的查询的过程输出到控制台
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID
然后在ReggieApplication中运行springboot项目,看能否成功运行
开发环境准备完成。
6.导入前端文件
将这两个前端资源直接复制到resources文件下
前端资源导入之后, 因为在springboot项目中,网页默认就只能访问 resource目录下的static和template文件夹下的文件,无法访问到backend和front文件夹,可以自己去试一下,因此,我们需要加入一个配置类(也叫做静态资源映射):WebMvcConfig,大家在reggie目录下创建config文件,然后在资料中导入该配置类,该配置类的作用就是让网页访问到backend和front这两个前端文件夹;
开发环境搭建完成。
二.后台登录系统开发
登录页面 :瑞吉外卖管理端
按f12 打开控制台,再点击登录就可以看到登录请求信息:http://localhost:8080/employee/login
所以我们需要创建employee相关的Controller :EmployeeController,在controller中进行功能实现
在此之前,我们需要做好准备工作:
1.在java/com/itheima/reggie目录下创建项目中需要使用到的包:
common包(用来存放共同使用的类)
config包(放配置类)
mapper包
service包,service包中的放service实现类的Impl包
controller包
entity(放实体类)
filter(放过滤器)
dto(这个在菜品和套餐那块会用到,主要也是放实体类)
2. 实体类,Mapper,service,Impl的开发
(1)在entity导入实体类Employee类;
(2)使用mybatis-plus提供的功能,自动生成EmployeeMapper
package com.itheima.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Employee;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}
(3)EmployeeService
package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Employee;
public interface EmployeeService extends IService<Employee> {
}
(4)EmployeeServiceImpl
package com.itheima.reggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.Employee;
import com.itheima.reggie.mapper.EmployeeMapper;
import org.springframework.stereotype.Service;
/**
* @author LJM
* @create 2022/4/15
*/
@Service //这两个泛型一个是实体类对应的mapper,一个是实体类
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper,Employee> implements EmployeeService {
}
3.封装返回的结果类R
把这个返回结果类R放入common包中,服务端Controller响应的数据最终都会封装成此对象R
这个是资料自带的,直接复制到Common包下
package com.itheima.reggie.common;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* 通用返回结果类,服务端响应的数据最终都会封装成此对象
* @param <T>
*/
@Data
public class R<T> {
private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据
private Map map = new HashMap(); //动态数据
public static <T> R<T> success(T object) {
R<T> r = new R<T>();
r.data = object;
r.code = 1;
return r;
}
public static <T> R<T> error(String msg) {
R r = new R();
r.msg = msg;
r.code = 0;
return r;
}
public R<T> add(String key, Object value) {
this.map.put(key, value);
return this;
}
}
4.Controller
先把模块搭建好
package com.itheima.reggie.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.itheima.reggie.common.R;
import com.itheima.reggie.entity.Employee;
import com.itheima.reggie.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
@RequestMapping("/employee")
public class EmployeeController {
@PostMapping("/login") //使用restful风格开发
public R<Employee> login()
}
}
先处理业务逻辑,然后再编码
1、将页面提交的密码password进行md5加密处理
2、根据页面提交的用户名username查询数据库
3、如果没有查询到则返回登录失败结果
4、密码比对,如果不一致则返回登录失败结果
5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
6、登录成功,将员工id存入Session并返回登录成功结果
package com.itheima.reggie.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.reggie.common.R;
import com.itheima.reggie.entity.Employee;
import com.itheima.reggie.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){
//1、从前端用户登录拿到的用户密码,并且对用户密码password进行md5加密处理
String password = employee.getPassword();
password=DigestUtils.md5DigestAsHex(password.getBytes());
//2、创建条件构造器,根据页面提交的用户名username查询数据库,看数据库中的信息与用户输入的是否一致
LambdaQueryWrapper<Employee> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(Employee::getUsername,employee.getUsername());
//在设计数据库的时候我们对username使用了唯一索引,所以这里可以使用getOne方法对username设置唯一索引
Employee emp = employeeService.getOne(queryWrapper);
//3、如果没有查询到则返回登录失败结果
if(emp==null){
return R.error("用户不存在");
}
//4、密码比对,如果不一致则返回登录失败结果
if(!emp.getPassword().equals(password)){
return R.error("密码不正确");
}
//5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
if (emp.getStatus()==0){
return R.error("账号已禁用");
}
//6、登录成功,将员工id存入Session并返回登录成功结果
request.getSession().setAttribute("employee",emp.getId());
return R.success(emp);
}
三.退出功能开发
登录之后,按f12,点击右上角退出功能键,发现退出功能的请求路径是employee/logout,请求方法是Post
/**
* 退出功能
* 1.在controller中创建对应的处理方法来接受前端的请求,请求方式为post;
* 2.清理session中的用户id
* 3.返回结果(前端页面会进行跳转到登录页面)
*/
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){
request.getSession().removeAttribute("employee");
return R.success("退出成功");
}
四.完善登陆功能
问题分析:登录存在一个问题,就是在员工还没有进行登录的情况下,如果直接访问后台管理系统是可以直接访问到的,意思就是直接跳过了登录过程,
这样是不合理的。
解决思路:所以我们需要设置一个过滤器,过滤器来判断用户是否登录,没有登录的话就跳转到登录页面。
解决方法:1.在主配置文件上加入注解@ServletComponentScan
2.在filter包下创建过滤器LoginCheckFilter,就可以完成功能
package com.itheima.reggie.filter;
import com.alibaba.fastjson.JSON;
import com.itheima.reggie.common.BaseContext;
import com.itheima.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 检查用户是否已经完成登录
*/
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter{
//路径匹配器,支持通配符
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1、获取本次请求的URI
String requestURI = request.getRequestURI();// /backend/index.html
log.info("拦截到请求:{}",requestURI);
//定义不需要处理的请求路径
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
//2、判断本次请求是否需要处理
boolean check = check(urls, requestURI);
//3、如果不需要处理,则直接放行
if(check){
log.info("本次请求{}不需要处理",requestURI);
filterChain.doFilter(request,response);
return;
}
//4、判断登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("employee") != null){
log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);
filterChain.doFilter(request,response);
return;
}
log.info("用户未登录");
//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return;
}
/**
* 路径匹配,检查本次请求是否需要放行
* @param urls
* @param requestURI
* @return
*/
public boolean check(String[] urls,String requestURI){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if(match){
return true;
}
}
return false;
}
}
五.员工管理模块的开发
1.新增员工
业务逻辑:
1.对新增的员工设置初始化密码123456,需要进行md5加密处理,后续员工可以直接修改密码
2.设置员工的创造时间和更新时间(这是Employee实体类中的属性,需要设置)
3.获得当前登录用户的id
4.设置创造人和更新人(这个也是Employee中的属性,新增员工的话都要设置)
5.调用新增方法,返回封装类
/**
* 新增员工
* @return
*/
@PostMapping
public R<String> save(HttpServletRequest request, @RequestBody Employee employee){
//对新增的员工设置初始化密码123456,需要进行md5加密处理,后续员工可以直接修改密码
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//获得当前登录用户的id
Long empId = (Long) request.getSession().getAttribute("employee");
employee.setCreateUser(empId);
employee.setUpdateUser(empId);
//新增方法save
employeeService.save(employee);
return R.success("新增成功");
}
注意:但是因为我们把username设置为唯一索引,所以下次再新增用户名相同的时候,就会出现异常,这个异常是MySQL数据库抛出来的,所以我们需要对这个异常进行捕获处理
我们使用全局异常捕获的处理方式:
在common包中导入全局异常捕获的处理方法
package com.itheima.reggie.common;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.sql.SQLIntegrityConstraintViolationException;
/**
* 全局异常处理
* 用来处理:添加已经存在的账号名的情况,这种情况会抛出异常并显示:账号名已存在
*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.error(ex.getMessage());
if(ex.getMessage().contains("Duplicate entry")){
String[] split = ex.getMessage().split(" ");
String msg = split[2] + "账号名已存在";
return R.error(msg);
}
return R.error("未知错误");
}
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ex){
log.error(ex.getMessage());
return R.error(ex.getMessage());
}
}
2.员工信息分页展示和根据名字进行查询
开发分页查询功能之前,现在config包下导入mybatis-plus的分页插件,配置好分页插件后就可以开发分页功能了(资料中自带)
package com.itheima.reggie.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置mybatis-plus的分页插件
* 配置好分页插件后就可以去服务端开发分页功能了
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
信息分页展示和根据名字进行查询业务逻辑:
1.创建分页构造器对象
2.创建条件构造器
3.添加条件,根据员工name进行模糊查询
4.添加条件,根据员工信息更新时间进行降序排序
5.调用employeeService中的page方法,并且将pageInfo和queryWrapper传到page方法中
6.返回封装类
/**
* 员工分页展示和根据名字进行查询
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
//创建分页对象构造器
Page pageInfo=new Page(page,pageSize);
//创建条件构造器
LambdaQueryWrapper<Employee> queryWrapper=new LambdaQueryWrapper<>();
//根据条件name进行模糊查询,并且name不能为空
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
//根据更新时间的条件进行降序排序
queryWrapper.orderByDesc(Employee::getUpdateTime);
employeeService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
3.禁用和启动员工账号
业务逻辑:
1.设置员工信息的更新时间
2.通过request获取员工id
3.通过员工id设置更新人
4.调用employeeService中的更新方法,updateById进行更新(ememployeeService继承了Iservice,所以updateById方法是Iservice中的方法)
5.返回封装类
@PutMapping
public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
Long empId =(Long) request.getSession().getAttribute("employee");
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(empId);
employeeService.updateById(employee);
return R.success("修改成功");
}
注意:测试的时候我们发现出现了问题,就是我们修改员工的状态,提示信息显示修改成功,但是我们去数据库查验证的时候,发现员工的状态码压根就没有变化,这是为什么呢?
原因是:mybatis-plus对id使用了雪花算法,所以存入数据库中的id是19为长度,但是前端的js只能保证数据的前16位的数据的精度,对我们id后面三位数据进行了四舍五入,所以就出现了精度丢失;就会出现前端传过来的id和数据里面的id不匹配,就没办法正确的修改到我们想要的数据;
解决思路:既然js对long型的数据会进行精度丢失,那么我们就对数据进行转型,我们可以在服务端Controller给页面响应json格式的数据时进行处理,将long型的数据统一转换为string字符串;
解决办法:
步骤一:在common包下自定义消息转换类:JacksonObjectMapper(资料中有,直接复制)
步骤二:在前面的WebMvcConfig 配置类中扩展spring mvc 的消息转换器,在此消息转换器中使用spring提供的对象转换器进行Java对象到json数据的转换;
/**
* 扩展mvc框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter);
}
4.点击修改按钮的时候数据回显
业务逻辑:
点击修改的时候,前端页面会传过来的员工 id,我们通过这个员工id进行数据的回显
1.通过employeeService.getById(id).var,获得员工对象Employee
2.判断员工对象是否存在,如果存在,则返回封装类
/**
* 根据前端页面传过来的员工id查询数据库进行数据回显给前端
*/
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
Employee employee = employeeService.getById(id);
if (employee!=null){
return R.success(employee);
}
return R.error("没有查询到员工信息");
}
员工管理模块完成。
六.分类管理模块开发
1.公共字段填充
问题分析:
功能实现:
1.在Employee实体类的四个相关属性上加入@TableFiled注解,指定自动填充
2.按照框架要求编写对象处理器,在此类中统一为公共字段赋值
在common包下编写对象处理器MyMetaObjecthandler
package com.itheima.reggie.common;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 自定义元数据对象处理器
*/
@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {
/**
* 插入操作,自动填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充[insert]...");
log.info(metaObject.toString());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("createUser", BaseContext.getCurrentId());
metaObject.setValue("updateUser", BaseContext.getCurrentId());
}
/**
* 更新操作,自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[update]...");
log.info(metaObject.toString());
long id = Thread.currentThread().getId();
log.info("线程id为:{}",id);
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser", BaseContext.getCurrentId());
}
}
之后就不需要再手动设置这四个属性了,mybatis-plus会帮我们自动填充。
功能完善:
实现步骤:BaseContext工具类用于保存和获取当前登录用户id
2.分类管理的分页展示
1.在entity包中导入实体类Category
2.在mapper,service,service中的Impl,controller包中分别创建好相应内容
3.在CategoryController中@Autowired自动注入categoryService
@Slf4j
@RestController
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 分页功能的开发
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize){
//分页构造器
Page<Category> categoryPage=new Page<>(page,pageSize);
//条件构造器
LambdaQueryWrapper<Category> queryWrapper=new LambdaQueryWrapper<>();
//根据sort升序条件进行排序
queryWrapper.orderByAsc(Category::getSort);
categoryService.page(categoryPage,queryWrapper);
return R.success(categoryPage);
}
}
3.分类管理的新增菜品分类(菜品分类和套餐分类请求路径一样)
代码逻辑:
直接用Iservice中的save方法进行新增菜品分类
save是mybatis-plus自带的新增功能方法,也就是IService中新增功能的方法,save(实体类)
/**
* 新增菜品
* save是mybatis-plus自带的新增功能方法,也就是IService中新增功能的方法,save(实体类)
*/
@PostMapping
public R<String> save(@RequestBody Category category){
categoryService.save(category);
return R.success("新增成功");
}
4.根据ids删除菜品分类或者套餐分类
代码逻辑:
由请求路径可知:参数前面不需要加@PathVariable注解,因为ids在请求路径后面
分类管理中的删除直接通过IService中的removeById()的方法进行删除
@DeleteMapping()
public R<String> delete( Long ids){
categoryService.removeById(ids);
return R.success("删除成功");
}
5.修改菜品分类或者套餐分类
代码逻辑:
直接使用Iservice中的updateById进行修改
/**
* 修改菜品或套餐
*/
@PutMapping
public R<String> update(@RequestBody Category category){
categoryService.updateById(category);
return R.success("修改成功");
}
七.菜品管理模块的开发
1.菜品的分页展示和根据name进行查询
1.将Dish实体类导入entity中,并且创建好相对应的Mapper(@Mapper),Service,Impl(@Service),Controller(@RestController,@RequestMapping(“/dish”))
2.将实体类DishDto导入到entity包中,这个实体类继承了Dish实体类,并且包含了CategoryName这个属性,这个属性在菜品分页展示中的菜品分类这一栏数据会要用到
3.在DishController中注入DishService
@Autowired
private DishService dishService;
4.模块搭建好
@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {
@Autowired
private DishService dishService;
@GetMapping("/page")
public R<Page> page( int page, int pageSize, String name){
}
代码逻辑:
1.创建分页构造器
2.创建条件构造器
3.添加条件
4.调用dishService中的Iservice中的方法page,并且将分页构造器和条件构造器传到page方法的参数中去
5.返回封装类
@GetMapping("/page")
public R<Page> page( int page, int pageSize, String name){
//对象构造器
Page<Dish> dishPage= new Page<>(page,pageSize);
//条件构造器
LambdaQueryWrapper<Dish> queryWrapper=new LambdaQueryWrapper<>();
//添加条件
queryWrapper.like(StringUtils.isNotEmpty(name),Dish::getName,name);
queryWrapper.orderByDesc(Dish::getUpdateTime);
dishService.page(dishPage,queryWrapper);
return R.success(dishpage);
}
代码优化:
启动项目后可以发现,菜品管理分页中出现了菜品信息,但是菜品分类这一栏没有信息,原因是菜品分类这一栏需要的参数是CategoryName,
而Dish中传替的参数是CategoryId,所以菜品分类这一栏没有信息;
解决办法:
使用实体类DishDto
代码逻辑:
1.在分页构造器那里添加一个分页构造器,里面使用DishDto对象
2.使用BeanUtils进行对象拷贝,将dishPage中的信息拷贝到dishDtoPage,但是忽略records这个属性
3.通过 dishPage.getRecords获取到之前的records信息,并且返回的是一个list集合:List<Dish> records = dishPage.getRecords();
4.需要将List<Dish> Dish的list集合转换成 list<DishDto> DishDto的list集合,这里使用stream流的方式进行处理:
List<DishDto> list ==records.stream.map((item)->){ 其中的item就是Dish对象
}
5.处理完之后,使用dishdto分页构造器来注入records属性:dishDtoPage.setRecords(list);
/**
* 分页展示菜品管理
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page( int page, int pageSize, String name){
//对象构造器
Page<Dish> dishPage= new Page<>(page,pageSize);
Page<DishDto> dishDtoPage = new Page<>(page,pageSize);
//条件构造器
LambdaQueryWrapper<Dish> queryWrapper=new LambdaQueryWrapper<>();
//添加条件
queryWrapper.like(StringUtils.isNotEmpty(name),Dish::getName,name);
queryWrapper.orderByDesc(Dish::getUpdateTime);
dishService.page(dishPage,queryWrapper);
//对象拷贝
BeanUtils.copyProperties(dishPage,dishDtoPage,"records");
//
List<Dish> records = dishPage.getRecords();
List<DishDto> list = records.stream().map((item) -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item,dishDto);
Long categoryId = item.getCategoryId();//分类id
//根据id查询分类对象
Category category = categoryService.getById(categoryId);
if(category != null){
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}
return dishDto;
}).collect(Collectors.toList());
dishDtoPage.setRecords(list);
return R.success(dishDtoPage);
}
2.新增菜品
(1)菜品分类下拉框
新增菜品的请求路径是category/list
请求方式是Get
这里的type查数据库后可以知道type=1,意思是类型是菜品
代码实现:
/**
* 获得新增菜品中的菜品分类信息
*/
@GetMapping("/list")
private R<List<Category>> list( Category category){
//条件构造器
LambdaQueryWrapper<Category> queryWrapper=new LambdaQueryWrapper<>();
//添加条件
queryWrapper.eq(category.getType()!=null,Category::getType,category.getType());
queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);
List<Category> list = categoryService.list(queryWrapper);
return R.success(list);
}
(2)新增菜品
新增菜品的时候因为要上传图片,所以:
1.yml配置文件:配置上传图片的存储位置;
reggie:
path: D:\img\
2.在资料中导入一个负责图片文件上传和下载的controller(CommonController)
package com.itheima.reggie.controller;
import com.itheima.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;
/**
* 文件上传和下载
*/
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {
@Value("${reggie.path}")
private String basePath;
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
public R<String> upload(MultipartFile file){
//file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
log.info(file.toString());
//原始文件名
String originalFilename = file.getOriginalFilename();//abc.jpg
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
//使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
String fileName = UUID.randomUUID().toString() + suffix;//dfsdfdfd.jpg
//创建一个目录对象
File dir = new File(basePath);
//判断当前目录是否存在
if(!dir.exists()){
//目录不存在,需要创建
dir.mkdirs();
}
try {
//将临时文件转存到指定位置
file.transferTo(new File(basePath + fileName));
} catch (IOException e) {
e.printStackTrace();
}
return R.success(fileName);
}
/**
* 文件下载
* @param name
* @param response
*/
@GetMapping("/download")
public void download(String name, HttpServletResponse response){
try {
//输入流,通过输入流读取文件内容
FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
//输出流,通过输出流将文件写回浏览器
ServletOutputStream outputStream = response.getOutputStream();
response.setContentType("image/jpeg");
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fileInputStream.read(bytes)) != -1){
outputStream.write(bytes,0,len);
outputStream.flush();
}
//关闭资源
outputStream.close();
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
因为菜品的添加涉及到了菜品口味,所以菜品添加涉及到了dish和dish-flavor两张表
所以我们需要在Service中创建一个添加菜品的方法并且在Impl中进行实现,最后在Controller中直接调用这个方法。
service
package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.Dish;
public interface DishService extends IService<Dish> {
void saveWithFlavor(DishDto dishDto);
}
Impl
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
@Autowired
private DishFlavorService dishFlaovrService;
@Autowired
private DishService dishService;
@Override
@Transactional
public void saveWithFlavor(DishDto dishDto) {
//添加菜品的基本信息到菜品表dish
this.save(dishDto);
Long dishId = dishDto.getId();//菜品id
//绑定菜品setDishId,添加保存菜品口味
List<DishFlavor> flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item) -> {
item.setDishId(dishId);
return item;
}).collect(Collectors.toList());
//保存菜品口味数据到菜品口味表dish_flavor
dishFlaovrService.saveBatch(flavors);
}
controller
@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private CategoryService categoryService;
@Autowired
private DishFlavorService dishFlavorService;
/**
* 新增菜品,包括菜品和菜品口味
*/
@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
dishService.saveWithFlavor(dishDto);
return R.success("新增菜品成功");
}
}
3.起售和停售菜品
起售和停售在菜品管理中算是最简单的了
代码逻辑:
1.先根据ids获取对应的dish对象: dishService.getById(ids).var
2.然后在对dish做判断,判断是否为空
3.如果不为空,那么就更新status状态
/**
* 停售和起售
* @param status
* @param ids
* @return
*/
@PostMapping("/status/{status}")
public R<String> update(@PathVariable Integer status,Long ids){
//先根据ids获取对应的dish对象: dishService.getById(ids).var
Dish dish = dishService.getById(ids);
//然后在对dish做判断,判断是否为空
if (dish != null){
//如果不为空,那么就更新status状态
dish.setStatus(status);
dishService.updateById(dish);
return R.success("状态更新");
}
return R.error("菜品不存在");
}
4.菜品删除
先自定义一个业务异常类CustomException,因为这里的删除功能需要抛异常了(菜品管理和套餐管理功能模块的删除功能需要抛异常;因为删除之前需要进行判断,如果达到了删除条件,比如status==0,那么就可以成功删除,否则抛异常,并且抛出相应的中文提示信息)
package com.itheima.reggie.common;
/**
* 自定义业务异常类
*/
public class CustomException extends RuntimeException {
public CustomException(String message){
super(message);
}
}
然后在前面写的GlobalExceptionHandler全局异常捕获器中添加该异常CostomException,这样就可以把相关的异常信息显示给前端页面操作的人员看见(在捕获器中添加了该异常,那么就可以抛出相关的中文提示的异常信息)
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ex){
log.error(ex.getMessage());
return R.error(ex.getMessage());
}
代码逻辑:
1.在Service中创建一个删除的方法
2.在Impl中实现这个方法
3.在Controller中调用这个方法
Service
package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.Dish;
public interface DishService extends IService<Dish> {
void deleteWithFlavor(Long ids);
}
Impl
@Override
@Transactional
public void deleteWithFlavor(Long ids) {
//删除菜品
Dish dish = dishService.getById(ids);
Integer status = dish.getStatus();
if (status != 1) {
this.removeById(ids);
} else {
throw new CustomException("菜品正在售卖,不能删除");
}
}
Controller
/**
* 删除菜品
* @param ids
* @return
*/
@DeleteMapping
public R<String> delete(Long ids){
dishService.deleteWithFlavor(ids);
return R.success("删除成功");
}
总结一下(重要):我们在项目中有id和ids的请求就只有这三种情况,这三种情况对应的controller中的使用的注解和参数不一样,要学会区分
1.
/**
* 根据id进行回显
*/
@GetMapping("/{id}")
public R<DishDto> get(@PathVariable Long id){
}
2.
/**
* 停售和起售
* @param status
* @param ids
* @return
*/
@PostMapping("/status/{status}")
public R<String> update(@PathVariable Integer status,Long ids){
}
3.这一种因为ids在路径后面,所以都不需要@PathVariable注解或者@PathParam注解,参数
/**
* 删除菜品
* @param ids
* @return
*/
@DeleteMapping
public R<String> delete(Long ids){
}
5.菜品的修改
(1)根据id回显菜品和菜品口味信息
代码逻辑:
1.在Impl中,先用 this.getById(id).var 返回Dish,获取菜品信息(Dish dish = this.getById(id);)
2.接下来使用条件构造器来添加条件,获取菜品对应的菜品口味,并且将菜品口味返回成一个list集合
3.new DishDto对象,并且先把Dish对象拷贝到DishDto对象里面去:
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(dish, dishDto);
4.使用dishdto中的设置口味的方法,把菜品口味的list集合也放到dishdto对象里去
5.最后返回dishdto,在controller中调用方法
Service
package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.Dish;
public interface DishService extends IService<Dish> {
DishDto getByIdWithFlavor(Long id);
}
Impl
@Override
public DishDto getByIdWithFlavor(Long id) {
//获取菜品信息,根据id获取
Dish dish = this.getById(id);
//条件构造器
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
//添加条件获得菜品对应口味
queryWrapper.eq(DishFlavor::getDishId, dish.getId());
List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);
//把查到的数据放到DishDto对象里面去
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(dish, dishDto);
dishDto.setFlavors(flavors);
return dishDto;
}
Controller
/**
* 根据id进行回显
*/
@GetMapping("/{id}")
public R<DishDto> get(@PathVariable Long id){
DishDto dishDto = dishService.getByIdWithFlavor(id);
return R.success(dishDto);
}
(2)将修改数据进行保存
代码逻辑:
在Service中创建保存修改数据的方法,在Impl中实现
在Impl中:1.通过 this.updateById更新菜品的基本信息
2.创造菜品口味的条件构造器
3.添加条件,获取菜品对应的菜品口味
4.将菜品对应的菜品口味remove删除
5.添加用户提交的口味数据
6.使用stream流的方式保存口味
Service
package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.Dish;
public interface DishService extends IService<Dish> {
void updateWithFlavor(DishDto dishDto);
}
Impl
@Override
@Transactional
public void updateWithFlavor(DishDto dishDto) {
//更新菜品基本信息
this.updateById(dishDto);
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
//添加条件,先删除口味,再让用户重新添加口味数据
queryWrapper.eq(DishFlavor::getDishId, dishDto.getId());
dishFlavorService.remove(queryWrapper);
//添加当前用户提交的口味数据
List<DishFlavor> flavors = dishDto.getFlavors();
//口味保存操作
flavors = flavors.stream().map((item) -> {
item.setDishId(dishDto.getId());
return item;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(flavors);
}
Controller
/**
* 菜品更新后的保存功能
* @param dishDto
* @return
*/
@PutMapping
public R<String> update(@RequestBody DishDto dishDto){
dishService.updateWithFlavor(dishDto);
return R.success("菜品信息更新成功");
}
八.套餐管理模块的开发
1.分页展示套餐数据,并且根据名字查询
代码逻辑:
1.请求方式为Get,请求路径为setmeal/page
所以先在entity包中导入实体类Setmeal,然后在Mapper,Service,Impl,Controller中创建好相对应的数据
Controller
@RestController
@RequestMapping("/setmeal")
public class SetmealController {
@Autowired
private SetmealService setmealService;
/**
* 套餐信息分页查询
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){
}
}
2.创建好数据后,在controller中创建分页构造器
3.创建条件构造器
4.添加条件,根据name进行查询,并且name不为null;根据套餐的更新时间进行降序排序
5.调用Iservice中的Page方法,将分页构造器和条件构造器对象作为参数传进去
@Slf4j
@RestController
@RequestMapping("/setmeal")
public class SetmealController {
@Autowired
private SetmealService setmealService;
@Autowired
private CategoryService categoryService;
/**
* 套餐信息分页查询
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){
//分页对象构造器
Page<Setmeal> pageInfo=new Page<>(page,pageSize);
Page<SetmealDto> dtoPage = new Page<>();
//条件构造器
LambdaQueryWrapper<Setmeal> queryWrapper=new LambdaQueryWrapper<>();
//添加条件
queryWrapper.like(name!=null,Setmeal::getName,name);
queryWrapper.orderByDesc(Setmeal::getUpdateTime);
setmealService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
代码优化:
启动项目后,发现分页可以正常显示,但是套餐管理中的套餐分类这一栏没有数据,这是为什么呢?
因为套餐分类需要的参数是CategoryName,而传替的是Setmeal中的CategoryId参数,所以没有数据
解决方法:
1.在dto包中导入SetmealDto,这个是一个实体类,继承了Setmeal,并且有CategoryName属性
2.导入之后在分页构造器那里再创造一个SetmealDto的分页构造器
3.然后在Iservice调用page方法的后面,进行拷贝(BeanUtils.copyProperties),但是忽略records这个属性
4.使用 Setmeal的分页构造器对象 pageInfo ,pageInfo.getRecords().var 获取到records,返回的是一个list集合
(List<Setmeal> records=pageInfo.getRecords())
5.接着使用stream流的方式,将List<Setmeal> 转换成 List<SetmealDto> :
List<SetmealDto> list= records.stream.map((item)->{
})
6.在stream流中new一个setmealDto对象,将records拷贝到setmealDto中,并且根据Setmeal中的CategoryId,获取到套餐分类对象category,再根据category.getName获取套餐分类名字
7.在stream流里面处理完后,要set到setmeaDto的分页构造器中
8.最后返回setmealDto的分页构造器对象就可以了
@Slf4j
@RestController
@RequestMapping("/setmeal")
public class SetmealController {
@Autowired
private SetmealService setmealService;
@Autowired
private CategoryService categoryService;
/**
* 套餐信息分页查询
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){
//分页对象构造器
Page<Setmeal> pageInfo=new Page<>(page,pageSize);
Page<SetmealDto> dtoPage = new Page<>();
//条件构造器
LambdaQueryWrapper<Setmeal> queryWrapper=new LambdaQueryWrapper<>();
//添加条件
queryWrapper.like(name!=null,Setmeal::getName,name);
queryWrapper.orderByDesc(Setmeal::getUpdateTime);
setmealService.page(pageInfo,queryWrapper);
//对象拷贝,将对象构造器的dishPage中的数据拷贝到dishDtoPage中,除了"records"不拷贝
//BeanUtils.copyProperties(pageInfo,dtoPage,"records");
//获取records的list集合对象 (Page中的 protected List<T> records;)
// List<Setmeal> records = pageInfo.getRecords();
//但是上面的list集合对象的list泛型是 List<Setmeal> ,我们想要的泛型是List<SetmealDto>,
// 可以通过上面的泛型为<Setmeal>的records进行处理,处理完之后赋给泛型为<SetmealDto>的
// List<SetmealDto> list 就可以了
/* List<SetmealDto> list = records.stream().map((item) -> {
SetmealDto setmealDto = new SetmealDto();
//对象拷贝
BeanUtils.copyProperties(item,setmealDto);
//分类id
Long categoryId = item.getCategoryId();
//根据分类id查询分类对象
Category category = categoryService.getById(categoryId);
if(category != null){
//分类名称
String categoryName = category.getName();
setmealDto.setCategoryName(categoryName);
}
return setmealDto;
}).collect(Collectors.toList());
dtoPage.setRecords(list);*/
BeanUtils.copyProperties(pageInfo,dtoPage,"records");
List<Setmeal> records = pageInfo.getRecords();
List<SetmealDto> list=records.stream().map((item)->{
SetmealDto setmealDto=new SetmealDto();
BeanUtils.copyProperties(item,setmealDto);
Long categoryId = item.getCategoryId();
Category category = categoryService.getById(categoryId);
if (category !=null){
String categoryName = category.getName();
setmealDto.setCategoryName(categoryName);
}
return setmealDto;
}).collect(Collectors.toList());
dtoPage.setRecords(list);
return R.success(dtoPage);
}
2.新建套餐
业务逻辑:
新增套餐包含了套餐的基本信息,还有套餐的菜品信息,所以新建套餐涉及到了两个表的操作
代码逻辑:
跟之前一样,在Service中创建新增套餐的方法,然后在Impl中实现,最后在Controller中调用这个方法并且返回封装类
方法中的参数也为SetmealDto,因为SetmealDto继承了Setmeal,并且有SetmealDish(套餐的菜品)的信息
Service:
package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.Setmeal;
public interface SetmealService extends IService<Setmeal> {
void saveWithDish(SetmealDto setmealDto);
}
Impl:
@Service
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {
@Autowired
private SetmealDishService setmealDishService;
@Autowired
private SetmealService setmealService;
@Override
@Transactional
public void saveWithDish(SetmealDto setmealDto) {
//新增套餐的基本信息
this.save(setmealDto);
//新增套餐的菜品
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
setmealDishes=setmealDishes.stream().map((item)->{
//套餐菜品绑定设置对应的套餐id
item.setSetmealId(setmealDto.getId());
return item;
}).collect(Collectors.toList());
setmealDishService.saveBatch(setmealDishes);
/* //新增套餐的基本信息 :this.save
this.save(setmealDto);
//套餐中的菜品,通过stream流的方式来处理
List<SetmealDish> setmealDishes=setmealDto.getSetmealDishes();
//在保存setmealDishes 集合之前,该list集合中的 setmealId 还没有赋上值,所以需要遍历setmealDishes集合,然后将该属性赋上值
//这里可以使用stream流的方式来给该 setmealDishes集合中的 setmealId 赋上值
//或者说套餐菜品绑定对应的套餐id
setmealDishes=setmealDishes.stream().map((item) ->{
item.setSetmealId(setmealDto.getId()); // setmealDto.getId 是该新增套餐的id
return item;
}).collect(Collectors.toList());
//保存套餐中的菜品到 setmeal_dish表: setmealDishService.saveBatch
setmealDishService.saveBatch(setmealDishes);*/
}
Controller
/**
* 新增套餐
* @param setmealDto
* @return
*/
@PostMapping
public R<String> save(@RequestBody SetmealDto setmealDto){
setmealService.saveWithDish(setmealDto);
return R.success("新增成功");
}
3.套餐停售或者起售
因为ids是在请求路径外面,根据上面说过的三种情况,我们可以做出判断这是哪一种情况
/**
* 更改套餐状态
* @param status
* @param ids
* @return
*/
@PostMapping("/status/{status}")
public R<String> status(@PathVariable Integer status, Long ids){
//根据ids获取Setmeal对象
Setmeal setmeal = setmealService.getById(ids);
//进行判断
if (setmeal!=null){
setmeal.setStatus(status);
setmealService.updateById(setmeal);
return R.success("状态更新成功");
}
return R.error("对象不存在");
}
4.套餐删除
ids在请求路径外面,所以我们可以判断这是第三种情况
代码逻辑:
在Service中创建删除套餐的方法,在Impl中实现,最后在Controller中调用这个方法,并返回封装类
Impl中的代码逻辑:
先获取Setmeal对象,获取到对象后使用Setmeal中的属性status 用getStatus的方法获取到当前套餐的status信息(是起售或者停售)
如果是停售,那么可以删除,如果是起售中,那么就抛异常
Service
package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.Setmeal;
public interface SetmealService extends IService<Setmeal> {
void deleteWithDish(Long ids);
}
Impl
@Override
public void deleteWithDish(Long ids) {
Setmeal setmeal = setmealService.getById(ids);
Integer status = setmeal.getStatus();
if (status == 0) {
this.removeById(ids);
} else {
throw new CustomException("套餐还在售卖,无法删除");
}
}
Controller
@DeleteMapping
public R<String> delete( Long ids){
setmealService.deleteWithDish(ids);
return R.success("删除成功");
}
5.套餐的修改
(1)套餐信息的回显
这个id在请求路径里面
义务逻辑:
这个套餐的回显,回显了套餐的基本信息,而且回显了套餐的菜品,涉及到了两个表
所以还是在Service中创建回显套餐方法,在Impl中实现,最后在Controller中调用这个方法,并返回封装类
Service
public interface SetmealService extends IService<Setmeal> {
SetmealDto getWithDish(Long id);
}
Impl
@Override
public SetmealDto getWithDish(Long id) {
//通过id获取套餐基本信息
Setmeal setmeal = this.getById(id);
//获取跟套餐关联的菜品信息
//条件构造器
LambdaQueryWrapper<SetmealDish> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(SetmealDish::getSetmealId,setmeal.getId());
List<SetmealDish> setmealDishes = setmealDishService.list(queryWrapper);
//将Setmeal中获取到的信息复制到SetmealDto中
SetmealDto setmealDto=new SetmealDto();
BeanUtils.copyProperties(setmeal,setmealDto);
//将跟套餐关联的菜品信息set到setmealDto中
setmealDto.setSetmealDishes(setmealDishes);
return setmealDto;
}
Controller
/**
* 修改套餐->回显套餐信息
*/
@GetMapping("/{id}")
public R<SetmealDto> get(@PathVariable Long id){
SetmealDto setmealDto = setmealService.getWithDish(id);
return R.success(setmealDto);
}
(2)修改套餐的保存
在Service中创建套餐修改保存的方法,在Impl中实现,最后在Controller中调用这个方法,并返回封装类
代码逻辑:
更新修改的套餐基本信息(this.updateById())
创建条件构造器,先获取菜品对应的套餐id,在删除掉之前的菜品,删除掉之前的菜品后,获取用户新添加的套餐菜品
然后使用stream流的方式将新添加的套餐菜品绑定设置当前的套餐id,最后将套餐的菜品信息保存到套餐菜品sql表中
Service
package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.Setmeal;
public interface SetmealService extends IService<Setmeal> {
void saveWithDish(SetmealDto setmealDto);
SetmealDto getWithDish(Long id);
void updateWithDish(SetmealDto setmealDto);
void deleteWithDish(Long ids);
}
Impl
@Override
public void updateWithDish(SetmealDto setmealDto) {
//更新套餐基本信息
this.updateById(setmealDto);
//删除原来的菜品
LambdaQueryWrapper<SetmealDish> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(SetmealDish::getSetmealId,setmealDto.getId());
setmealDishService.remove(queryWrapper);
//获取更新后的套餐中的菜品集合
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
//通过stream流的方式来遍历setmealDishes集合并且获取setmealId
setmealDishes=setmealDishes.stream().map((item)->{
item.setSetmealId(setmealDto.getId());
return item;
}).collect(Collectors.toList());
setmealDishService.saveBatch(setmealDishes);
}
Controller
/**
* 修改套餐->保存修改的套餐信息
*/
@PutMapping
public R<String> update(@RequestBody SetmealDto setmealDto){
setmealService.updateWithDish(setmealDto);
return R.success("修改成功");
}