目录
项目搭建
一苍穹外卖项目介绍
开发苍穹外卖,需要全方位的来介绍一下这个项目。将从项目简介、产品原型、技术选型三个方面来介绍苍穹外卖这个项目。
1 项目介绍
本项目(苍穹外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括 :系统管理后台、程序端应用两部分。
-
系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的分类、菜品、套餐、订单、员工等进行管理维护,对餐厅的各类数据进行统计,同时也可进行来单语音播报功能。
-
小程序端主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单、支付、催单等。
接下来,通过功能架构图来展示管理端和用户端的具体业务功能模块。
1). 管理端功能
员工登录/退出 , 员工信息管理 , 分类管理 , 菜品管理 , 套餐管理 , 菜品口味管理 , 订单管理 ,数据统计,来单提醒。
2). 用户端功能
微信登录 , 收件人地址管理 , 用户历史订单查询 , 菜品规格查询 , 购物车功能 , 下单 , 支付、分类及菜品浏览。
2 技术选型
关于本项目的技术选型, 我们将会从 用户层、网关层、应用层、数据层 这几个方面进行介绍,主要用于展示项目中使用到的技术框架和中间件等。
1). 用户层
本项目中在构建系统管理后台的前端页面,我们会用到H5、Vue.js、ElementUI、apache echarts(展示图表)等技术。而在构建移动端应用时,我们会使用到微信小程序。
2). 网关层
Nginx是一个服务器,主要用来作为Http服务器,部署静态资源,访问性能高。在Nginx中还有两个比较重要的作用: 反向代理和负载均衡, 在进行项目部署时,要实现Tomcat的负载均衡,就可以通过Nginx来实现。
3). 应用层
SpringBoot: 快速构建Spring项目, 采用 "约定优于配置" 的思想, 简化Spring项目的配置开发。
SpringMVC:SpringMVC是spring框架的一个模块,springmvc和spring无需通过中间整合层进行整合,可以无缝集成。
Spring Task: 由Spring提供的定时任务框架。
http Client: 主要实现了对http请求的发送。
Spring Cache: 由Spring提供的数据缓存框架
JWT: 用于对应用程序上的用户进行身份验证的标记。
阿里云OSS: 对象存储服务,在项目中主要存储文件,如图片等。
Swagger: 可以自动的帮助开发人员生成接口文档,并对接口进行测试。
POI: 封装了对Excel表格的常用操作。
WebSocket: 一种通信网络协议,使客户端和服务器之间的数据交换更加简单,用于项目的来单、催单功能实现。
4). 数据层
MySQL: 关系型数据库, 本项目的核心业务数据都会采用MySQL进行存储。
Redis: 基于key-value格式存储的内存数据库, 访问速度快, 经常使用它做缓存。
Mybatis: 本项目持久层将会使用Mybatis开发。
Pagehelper: 分页插件。
Spring Data rRdis: 简化java代码操作Redis的API。
5). 工具
git: 版本控制工具, 在团队协作中, 使用该工具对项目中的代码进行管理。
maven: 项目构建工具。
junit:单元测试工具,开发人员功能实现完毕后,需要通过junit对功能进行单元测试。
postman: 接口测工具,模拟用户发起的各类HTTP请求,获取对应的响应结果。
3 项目收获
当我们完成该项目的学习,可以培养以下能力:
二环境搭建
开发环境搭建主要包含前端环境和后端环境两部分。
-
作为服务端开发工程师, 我们课程学习的重心应该放在后端的业务代码上
-
前端的页面我们只需要导入资料中的nginx, 前端页面的代码我们只需要能看懂即可。
1前端环境搭建
前端工程基于 nginx
从资料中找到前端运行环境的nginx,移动到非中文目录下。
sky目录中存放了管理端的前端资源,具体如下:
启动nginx,访问测试
双击 nginx.exe 即可启动 nginx 服务,访问端口号为 80
2数据库环境搭建
从资料中找到sky.sql
执行sky.sql文件
直接打开sky.sql文件,通过该sql文件直接可创建数据库,所以不需要提前创建数据库,直接导入该文件执行即可。
执行完成后,共创建出11张表
3后端环境搭建
将初始代码导入idea
熟悉项目结构
序号 | 名称 | 说明 |
---|---|---|
1 | sky-take-out | maven父工程,统一管理依赖版本,聚合其他子模块 |
2 | sky-common | 子模块,存放公共类,例如:工具类、常量类、异常类等 |
3 | sky-pojo | 子模块,存放实体类、VO、DTO等 |
4 | sky-server | 子模块,后端服务,存放配置文件、Controller、Service、Mapper等 |
启动测试
4补充知识
请求响应流程
DTO VO PO
DTO(Data Transfer Object):数据传输对象,用于接收数据和传输数据,属性和请求参数对应
VO(View Object):视图对象,返回给客户端展示用的数据,例如分页对象 PageResult{total,List}
PO(Persistant Object):持久化对象,对象属性和数据库表中的字段一一对应,一张表对应一个PO
POJO(Plain Ordinary Java Object): 简单无规则Java对象,PO、VO、DTO都是典型的POJO。
员工管理
零 接口文档
开发的时候需要先确定接口文档
前端,后端都需要按照接口文档进行开发
作用
确定请求内容
请求路径 请求方式 请求参数 请求头
确定响应内容
服务器要返回给客户端的数据
注意:
一员工登录
1 需求
餐饮员工访问http://localhost/#/login,实现后台登录
2 token
背景:
若是一个后台需要验证身份的网站,客户端会频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生
作用
token可以对标识用户身份信息,返回给客户端自己保存
客户端可以携带token访问业务相关的其他系统,而不用重新登录
注意事项
-
我们项目中使用的jwt技术生成的token
-
保证加密解密使用的是同一个密钥
3 数据模型
create table employee
(
id bigint auto_increment comment '主键'
primary key,
name varchar(32) not null comment '姓名',
username varchar(32) not null comment '用户名',
password varchar(64) not null comment '密码',
phone varchar(11) not null comment '手机号',
sex varchar(2) not null comment '性别 0:女 1:男 ',
id_number varchar(18) not null comment '身份证号',
status int default 1 not null comment '状态 0:禁用,1:启用',
create_time datetime null comment '创建时间',
update_time datetime null comment '更新时间',
create_user bigint null comment '创建人',
update_user bigint null comment '修改人',
constraint idx_username unique (username)
) comment '员工信息' collate = utf8mb3_bin;
4 思路分析
5代码实现
EmployeeController
package com.sky.web.admin;
import com.sky.dto.EmployeeDTO;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.dto.EmployeePageQueryDTO;
import com.sky.entity.Employee;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.EmployeeService;
import com.sky.vo.EmployeeLoginVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 员工管理
*/
@Slf4j
@RestController
@RequestMapping("/admin/employee")
public class EmployeeController {
@Autowired//依赖注入
private EmployeeService employeeService;
@PostMapping("login")
public Result login(@RequestBody EmployeeLoginDTO employeeLoginDTO){
//1.调用service完成登陆操作,返回VO
EmployeeLoginVO vo = employeeService.login(employeeLoginDTO);
//2.将vo封装成result且返回
return Result.success(vo);
}
}
EmployeeService
package com.sky.service;
import com.sky.dto.EmployeeDTO;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.dto.EmployeePageQueryDTO;
import com.sky.entity.Employee;
import com.sky.result.PageResult;
import com.sky.vo.EmployeeLoginVO;
public interface EmployeeService {
EmployeeLoginVO login(EmployeeLoginDTO employeeLoginDTO);
}
EmployeeServicelmpl
package com.sky.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.JwtClaimsConstant;
import com.sky.constant.PasswordConstant;
import com.sky.constant.StatusConstant;
import com.sky.context.ThreadLocalUtil;
import com.sky.dto.EmployeeDTO;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.dto.EmployeePageQueryDTO;
import com.sky.entity.Employee;
import com.sky.exception.BusinessException;
import com.sky.mapper.EmployeeMapper;
import com.sky.properties.JwtProperties;
import com.sky.result.PageResult;
import com.sky.service.EmployeeService;
import com.sky.utils.JwtUtil;
import com.sky.vo.EmployeeLoginVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
@Autowired
private JwtProperties jwtProperties;
@Override
public EmployeeLoginVO login(EmployeeLoginDTO employeeLoginDTO) {
//1.对数据先进行非空校验,若有问题抛异常
String username = employeeLoginDTO.getUsername();
String password = employeeLoginDTO.getPassword();
//使用hutool工具对字符串进行校验
if (StrUtil.isBlank(username) || StrUtil.isBlank(password)) {
throw new BusinessException("用户名密码不能为空");
}
//2.调用mapper.findByUsername(un),返回Employee对象
Employee employee = employeeMapper.findByUsername(username);
//3.判断employee是否为空(找到),若为空抛异常
if (employee == null) {
throw new BusinessException("用户名错误");
}
//4.判断密码是否一致,若不一致抛异常
//注意:要用md5将用户输入的密码和数据库中的密码进行比较
//使用hutool中的工具类进行加密
String md5Password = SecureUtil.md5(password);
if (!md5Password.equals(employee.getPassword())) {
throw new BusinessException("密码错误");
}
//5.判断状态是否为禁用,若为禁用抛异常
if (employee.getStatus().equals(0)) {
throw new BusinessException("用户被禁用,请联系店长");
}
//6.生成token(将员工id放入载荷中)
Map<String,Object> claim = new HashMap<>();
//claim.put("empId",employee.getId());
claim.put(JwtClaimsConstant.EMP_ID,employee.getId());
//加密的秘钥为了防止写错,将密码放入了yml文件中
String token = JwtUtil.createJWT(jwtProperties.getAdminSecret(), 1000 * 60 * 60 * 24 * 7, claim);
//7.封装vo且返回
EmployeeLoginVO vo = EmployeeLoginVO.builder()
.id(employee.getId())
.userName(employee.getUsername())
.name(employee.getName())
.token(token)
.build();
return vo;
}
}
EmployeeMapper
package com.sky.mapper;
import com.sky.anno.AutoFill;
import com.sky.entity.Employee;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface EmployeeMapper {
@Select("select * from employee where username = #{username}")
Employee findByUsername(String username);
}
登陆的成功后,每次请求的时候都会以请求头的方式携带过来token,我们在拦截器中配置了解析token的操作
可以参考AdminLoginInterceptor类
6 登录不成功
点登录不成功,看看maven是否有报错
若有错进行以下操作
![](https://i-blog.csdnimg.cn/blog_migrate/58eea650605cc7f0b91e305c43295689.png)
二退出登录
1 需求
登录后点击【退出登录】,前端重新回到登录页面
2 思路分析
3 代码实现
EmployeeController
@PostMapping("logout")
public Result logout(){
return Result.success();
}
三员工分页
1需求
可以根据姓名搜索员工分页查询
![](https://i-blog.csdnimg.cn/blog_migrate/e591cd2ea44f526420ca1c03db37cee1.png)
2思路分析
![](https://i-blog.csdnimg.cn/blog_migrate/5fdee4017864005ea1efcc9eb15c740f.png)
3代码实现
EmployeeController
@GetMapping("page")
public Result findByPage(EmployeePageQueryDTO employeePageQueryDTO){
//1.调用service查询当前页的数据,返回值PageResult
PageResult pageResult = employeeService.findByPage(employeePageQueryDTO);
//2.将pageResult封装成result对象且返回
return Result.success(pageResult);
}
EmployeeService
PageResult findByPage(EmployeePageQueryDTO employeePageQueryDTO);
EmployeeServicelmpl
@Override
public PageResult findByPage(EmployeePageQueryDTO employeePageQueryDTO) {
//1.获取DTO中page和pageSize,参数校验,若没有值则设置默认值
int page = employeePageQueryDTO.getPage();
int pageSize = employeePageQueryDTO.getPageSize();
if (page==0) {
page=1;
}
if (pageSize==0) {
pageSize=10;
}
//2.设置分页参数
PageHelper.startPage(page,pageSize);
//3.调用mapper.findList方法,参数为name,返回List<Emp>
List<Employee> list = employeeMapper.findList(employeePageQueryDTO.getName());
//4.将List转成Page,就可以获取total
Page p = (Page) list;
long total = p.getTotal();
//5.封装成PageResult且返回
return new PageResult(total,list);
}
EmployeeMapper
List<Employee> findList(String name);
EmployeeMapper.xml
<select id="findList" resultType="com.sky.entity.Employee">
select * from employee
<where>
<if test="name != null and name != ''">
name like concat('%',#{name},'%')
</if>
</where>
</select>
四 新增员工
1 需求
添加员工账号时,要求:账号、手机号、身份证号不能被注册过
2 思路分析
3 ThreadLocal
ThreadLocal是jdk提供的一个工具类,将某个值和当前线程绑定.
共享用户信息
EmployeeController
@PostMapping
public Result save(@RequestBody EmployeeDTO employeeDTO){
//调用service完成保存操作
employeeService.save(employeeDTO);
//返回成功的result
return Result.success();
}
EmployeeService
void save(EmployeeDTO employeeDTO);
EmployeeServicelmpl
@Override
public void save(EmployeeDTO employeeDTO) {
//1.业务校验(用户名不能重复)
Employee employee = employeeMapper.findByUsername(employeeDTO.getUsername());
if (employee!=null) {
throw new BusinessException("用户名已存在");
}
//2.将dto转成po对象(Employee)
employee = new Employee();
/*employee.setName(employeeDTO.getName());
employee.setUsername(employeeDTO.getUsername());*/
//建议使用hutool中的工具类去拷贝同名属性,参数1:数据来源,参数2:数据目的地
BeanUtil.copyProperties(employeeDTO,employee);
//3.完善数据(密码,状态,俩时间,俩员工id)
employee.setPassword(SecureUtil.md5("123456"));
employee.setStatus(1);
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//从当前线程中获取当前登陆者的id
employee.setCreateUser(ThreadLocalUtil.getCurrentId());
employee.setUpdateUser(ThreadLocalUtil.getCurrentId());
//4.调用mapper.save(emp)
employeeMapper.save(employee);
}
EmployeeMapper
@Insert("insert into employee values (null,#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})")
void save(Employee employee);
五回显员工
1需求
根据ID查询信息回显
2思路分析
![](https://i-blog.csdnimg.cn/blog_migrate/0d509ccc39047a71ea804d6539f37df9.png)
3代码实现
EmployeeController
@GetMapping("{id}")
public Result findById(@PathVariable Long id){
//调用service,返回Employee
Employee employee = employeeService.findById(id);
//将employee封装成result且返回
return Result.success(employee);
}
EmployeeService
Employee findById(Long id);
EmployeeServicelmpl
@Override
public Employee findById(Long id) {
Employee employee = employeeMapper.findById(id);
return employee;
}
EmployeeMapper
@Select("select * from employee where id = #{id}")
Employee findById(Long id);
六修改员工
1需求
根据ID完成修改
![](https://i-blog.csdnimg.cn/blog_migrate/f6d0b1daf2c57cd5b1d526380fdf5904.png)
2思路分析
3代码实现
EmployeeController
@PutMapping
public Result update(@RequestBody EmployeeDTO employeeDTO){
//调用service完成更新操作
employeeService.update(employeeDTO);
//返回成功的result
return Result.success();
}
EmployeeService
void update(EmployeeDTO employeeDTO);
EmployeeServicelmpl
@Override
public void update(EmployeeDTO employeeDTO) {
//1.业务校验(用户名不能重复,记住排除自己)
Employee employee = employeeMapper.findByUsername(employeeDTO.getUsername());
if (employee!=null && !employeeDTO.getId().equals(employee.getId())) {
throw new BusinessException("用户名不能重复");
}
//2.将dto转成po对象(Employee)
employee = new Employee();
BeanUtil.copyProperties(employeeDTO,employee);
//3.完善数据(更新时间和更新人)
employee.setUpdateUser(ThreadLocalUtil.getCurrentId());
employee.setUpdateTime(LocalDateTime.now());
//4.调用mapper.update(emp)
employeeMapper.update(employee);
}
EmployeeMapper
void update(Employee employee);
EmployeeMapper.xml
<update id="update">
update employee
<!-- 动态sql中set可以将最后一个提交后面的","去掉 -->
<set>
<if test="name != null and name!= ''">name=#{name},</if>
<if test="username != null and username!= ''">username=#{username},</if>
<if test="password != null and password!= ''">password=#{password},</if>
<if test="phone != null and phone!= ''">phone=#{phone},</if>
<if test="sex != null and sex!= ''">sex=#{sex},</if>
<if test="idNumber != null and idNumber!= ''">id_number=#{idNumber},</if>
<if test="status != null">status=#{status},</if>
<if test="updateTime != null">update_time=#{updateTime},</if>
<if test="updateUser != null">update_user=#{updateUser},</if>
</set>
where id = #{id}
</update>
七 启用禁用账号
1需求
实现对员工账号的启动/禁用
![](https://i-blog.csdnimg.cn/blog_migrate/97af3bd96d748e7953a86a95c67e0232.png)
2思路分析
![](https://i-blog.csdnimg.cn/blog_migrate/e906a8e2277c6e91f92d825464b16f04.png)
3代码实现
EmployeeController
@PostMapping("/status/{status}")
public Result updateStatus(@PathVariable Integer status,Long id){
//调用service完成更新状态
employeeService.updateStatus(status,id);
//返回成功的result
return Result.success();
}
EmployeeService
void updateStatus(Integer status, Long id);
EmployeeServicelmpl
@Override
public void updateStatus(Integer status, Long id) {
//创建employee,设置id和status
Employee employee = Employee.builder()
.id(id)
.status(status)
.build();
//调用mapper.update(emp)
employeeMapper.update(employee);
}
菜品管理
零五张表关系
![](https://i-blog.csdnimg.cn/blog_migrate/98a9009cbc520d3fa41cd1ad60ccecb7.png)
一新增菜品
1需求
-
添加菜品之前,需要查询菜品分类列表(已完成)
-
完成图片上传功能
-
口味列表是前端固定给出的选项,不用后台查询
2数据模型![](https://i-blog.csdnimg.cn/blog_migrate/6b2b02dd0f1658428b7dcc5a1ba0df53.png)
create table dish
(
id bigint auto_increment comment '主键' primary key,
name varchar(32) not null comment '菜品名称',
category_id bigint not null comment '菜品分类id',
price decimal(10, 2) null comment '菜品价格',
image varchar(255) null comment '图片',
description varchar(255) null comment '描述信息',
status int default 1 null comment '0 停售 1 起售',
create_time datetime null comment '创建时间',
update_time datetime null comment '更新时间',
create_user bigint null comment '创建人',
update_user bigint null comment '修改人',
constraint idx_dish_name unique (name)
)comment '菜品';
create table dish_flavor
(
id bigint auto_increment comment '主键' primary key,
dish_id bigint not null comment '菜品',
name varchar(32) null comment '口味名称',
value varchar(255) null comment '口味数据list'
)comment '菜品口味关系表';
3图片上传
(1)代码流程
(2)阿里云操作
![](https://i-blog.csdnimg.cn/blog_migrate/d76b8624bf0281babec30462d4ca4759.png)
新建桶
若没有完成实名认证,就去认证下
实名认证完毕之后,再次搜索OSS,开通服务,然后创建桶
记住桶名和地域节点地址,放入application-dev.yml中
新建阿里云用户和获取秘钥
申请oss权限
![](https://i-blog.csdnimg.cn/blog_migrate/d1ebe415b4d281f730ac45c7f9693ac0.png)
(3)定义OSS相关配置
application-dev.yml
sky:
alioss:
endpoint: oss-cn-beijing.aliyuncs.com
bucket-name: sky-520
access-key-id: LTAI5tRRmFhhw8zrhnukC11g
access-key-secret: 9vmcoSc1PHzRUS1ktXTJA9xauRFjVx
application.yml
sky:
alioss:
endpoint: ${sky.alioss.endpoint}
access-key-id: ${sky.alioss.access-key-id}
access-key-secret: ${sky.alioss.access-key-secret}
bucket-name: ${sky.alioss.bucket-name}
(4)工具类(已提供)
在sky-server下新建测试类,测试代码
package com.sky.test;
import com.sky.utils.AliOssUtil;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@SpringBootTest
public class OssTest {
@Autowired
private AliOssUtil aliOssUtil;
@Test
public void testUpload() throws FileNotFoundException {
String url = aliOssUtil.upload("21.jpg", new FileInputStream("D:\\pic\\21.jpg"));
System.out.println(url);
}
}
CommonController(已提供)
package com.sky.web.admin;
import com.sky.exception.BusinessException;
import com.sky.result.Result;
import com.sky.service.CommonService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.IOException;
@Slf4j
@RestController
@RequestMapping("/admin/common")
public class CommonController {
@Autowired
private CommonService commonService;
@PostMapping("upload")
//文件上传方法参数类型必须是MultipartFile类型,且方法参数名称要和请求体中文件名称一致
public Result upload(MultipartFile file){
String url = null;
try {
url = commonService.upload(file);
} catch (IOException e) {
e.printStackTrace();
log.error("文件上传失败:{}",e);
throw new BusinessException("文件上传失败");
}
return Result.success(url);
}
}
CommonServivce(已提供)
package com.sky.service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
public interface CommonService {
String upload(MultipartFile file) throws IOException;
}
CommonServicelmpl(已提供)
package com.sky.service.impl;
import com.sky.service.CommonService;
import com.sky.utils.AliOssUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@Service
public class CommonServiceImpl implements CommonService {
@Autowired
private AliOssUtil aliOssUtil;
@Override
public String upload(MultipartFile file) throws IOException {
String url = aliOssUtil.upload(file.getOriginalFilename(), file.getInputStream());
return url;
}
}
4思路分析
![](https://i-blog.csdnimg.cn/blog_migrate/ab651759d2d20895c86bbd882b785906.png)
5代码实现
DishController
package com.sky.web.admin;
import com.sky.dto.DishDTO;
import com.sky.dto.DishPageDTO;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/admin/dish")
public class DishController {
@Autowired
private DishService dishService;
@PostMapping
public Result save(@RequestBody DishDTO dishDTO){
//1.调用service完成新增操作
dishService.save(dishDTO);
//2.返回成功的result
return Result.success();
}
}
DishService
void save(DishDTO dishDTO);
DishServicelmpl
@Override
public void save(DishDTO dishDTO) {
//1.业务校验(菜品名称不能重复)
Dish dish = dishMapper.findByName(dishDTO.getName());
if (dish!=null) {
throw new BusinessException("菜品名称不能重复");
}
//2.将dto转成po(dish)
dish = new Dish();
BeanUtil.copyProperties(dishDTO,dish);
//3.完善dish数据
dish.setStatus(StatusConstant.DISABLE);//0
dish.setCreateTime(LocalDateTime.now());
dish.setUpdateTime(LocalDateTime.now());
dish.setCreateUser(ThreadLocalUtil.getCurrentId());
dish.setUpdateUser(ThreadLocalUtil.getCurrentId());
//4.调用dishMapper往dish表中保存数据(返回新增记录的主键)
dishMapper.save(dish);
//5.从dto中获取口味集合
List<DishFlavor> flavors = dishDTO.getFlavors();
//6.遍历集合,获取每个口味,给口味设置菜品id
//使用hutool工具类判断集合是否为空
if (CollUtil.isNotEmpty(flavors)) {
for (DishFlavor flavor : flavors) {
flavor.setDishId(dish.getId());
}
//7.调用dishFlavorMapper的批量保存方法
dishFlavorMapper.batchSave(flavors);
}
}
DishMapper
@Select("select * from dish where name = #{name}")
Dish findByName(String name);
@Insert("insert into dish values(null,#{name},#{categoryId},#{price},#{image},#{description},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})")
//返回新增记录的主键值,设置给dish的id
@Options(useGeneratedKeys = true,keyProperty = "id")
void save(Dish dish);
DishFlavorMapper(已提供)
void batchSave(List<DishFlavor> flavors);
DishFlavorMapper.xml
<insert id="batchSave">
insert into dish_flavor values
<!--
动态sql:foreach
collection属性:用来配置要遍历的内容,
若是list就写list或者colletion,若是数组就写array
iten属性:用来给遍历到的变量起个名字
separator属性:指定分隔符
-->
<foreach collection="list" item="df" separator=",">
(null,#{df.dishId},#{df.name},#{df.value})
</foreach>
</insert>
二菜品分页
1 需求
分页查询时可以根据需要输入菜品名称、菜品分类、菜品状态进行查询
展示的数据中包含分类名称
![](https://i-blog.csdnimg.cn/blog_migrate/8b5ddafadb74bb74ee2cfdcb2047d702.png)
2 思路分析
![](https://i-blog.csdnimg.cn/blog_migrate/610db1092d1cc87b6b24af5be5b16740.png)
3 代码实现
DishController
@GetMapping("page")
public Result findByPage(DishPageDTO dishPageDTO){
//1.调用service完成分页查询,返回PageResult
PageResult pageResult = dishService.findByPage(dishPageDTO);
//2.将pageResult封装成Result且返回
return Result.success(pageResult);
}
DishService
PageResult findByPage(DishPageDTO dishPageDTO);
DishServicelmpl
@Override
public PageResult findByPage(DishPageDTO dishPageDTO) {
//1.设置分页参数
PageHelper.startPage(dishPageDTO.getPage(),dishPageDTO.getPageSize());
//2.调用mapper完成查询列表,List<DishVo>
List<DishVO> list = dishMapper.findList(dishPageDTO);
//3.将list转成page,目标获取total
Page p = (Page) list;
//4.封装pageResult且返回
return new PageResult(p.getTotal(),list);
}
DishMapper
List<DishVO> findList(DishPageDTO dishPageDTO);
DishMapper.xml
<select id="findList" resultType="com.sky.vo.DishVO">
select d.*,c.name categoryName
from dish d
join category c
on d.category_id = c.id
<where>
<if test="name != null and name != ''">
and d.name like concat('%',#{name},'%')
</if>
<if test="categoryId != null">
and d.category_id = #{categoryId}
</if>
<if test="status != null">
and d.status = #{status}
</if>
</where>
</select>
三删除菜品
1 需求
业务规则:
-
可以一次删除一个菜品,也可以批量删除菜品
-
起售中的菜品不能删除
-
被套餐关联的菜品不能删除
-
删除菜品后,关联的口味数据也需要删除掉
2 思路分析
在进行删除菜品操作时,会涉及到以下三张表
![](https://i-blog.csdnimg.cn/blog_migrate/467a08e6f07b275c8aae8adbfcd68152.png)
3 代码实现
DishController
@DeleteMapping
public Result deleteByIds(Long[] ids){
//1.调用service完成删除操作
dishService.deleteByIds(ids);
//2.返回成功的result
return Result.success();
}
DishService
void deleteByIds(Long[] ids);
DishServicelmpl
@Override
public void deleteByIds(Long[] ids) {
//1.若数组为空,抛异常
//采用hutool中工具类对数组进行操作
if (ArrayUtil.isEmpty(ids)) {
throw new BusinessException("请选择要删除的菜品");
}
//2.调用mapper查询这些菜品有无起售的,若有抛异常
int count = dishMapper.findEnableCountByIds(ids);
if (count>0) {
throw new BusinessException("起售的菜品不允许删除");
}
//3.调用菜品套餐中间表对应的mapper查询有无关联的套餐,若有抛异常
count = setmealDishMapper.findCountByDishIds(ids);
if (count>0) {
throw new BusinessException("被套餐关联的菜品不允许删除");
}
//4.调用dishMapper.deleteByIds(ids)
dishMapper.deleteByIds(ids);
//5.调用dishFlavorMapper.deleteByDishIds(ids)
dishFlavorMapper.deleteByDishIds(ids);
}
DishMapper
int findEnableCountByIds(Long[] ids);
void deleteByIds(Long[] ids);
DishMapper.xml
<select id="findEnableCountByIds" resultType="java.lang.Integer">
select count(*) from dish where status = 1 and id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
<delete id="deleteByIds">
delete from dish where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
SetmealDishMapper(已提供)
SetmealDishMapper.xml(已提供)
DishFlavorMapper
void deleteByDishIds(Long[] ids);
DishFlavorMapper.xml
<delete id="deleteByDishIds">
delete from dish_flavor where dish_id in
<foreach collection="array" separator="," item="id" open="(" close=")">
#{id}
</foreach>
</delete>
四启售停售菜品
1 需求
菜品起售表示该菜品可以对外售卖,在用户端可以点餐,菜品停售表示此菜品下架,用户端无法点餐。
-
菜品停售:菜品需要停售(status=0),关联菜品的套餐也需要停售
-
菜品起售:菜品需要启售(status=1),不影响套餐
2 需求分析
![](https://i-blog.csdnimg.cn/blog_migrate/fa1c6f7e6d5b9d4c4392edb66431f65f.png)
3 代码实现
DishController
@PostMapping("status/{status}")
public Result updateStatus(@PathVariable Integer status,Long id){
//1.调用service完成修改操作
dishService.updateStatus(id,status);
//2.返回成功的result
return Result.success();
}
DishService
void updateStatus(Long id, Integer status);
DishServicelmpl
@Override
public void updateStatus(Long id, Integer status) {
//1.将id和status封装dish,修改菜品的状态
Dish dish = Dish.builder()
.id(id)
.status(status)
.updateTime(LocalDateTime.now())
.updateUser(ThreadLocalUtil.getCurrentId())
.build();
dishMapper.update(dish);
//2.判断status是否为停售,若是停售,先查询出来关联的套餐id,若有套餐的话,需要将这些套餐的状态改成停售
if (StatusConstant.DISABLE.equals(status)) {
//2.1先查询出来关联的套餐id
List<Long> setmealIds = setmealDishMapper.findSetmealIdsByDishId(id);
if (CollUtil.isNotEmpty(setmealIds)) {
//需要将这些套餐的状态改成停售
//2.2 修改这些套餐的状态,修改时间,修改人
setmealMapper.updateStatus(setmealIds,status,LocalDateTime.now(),ThreadLocalUtil.getCurrentId());
}
}
}
DishMapper
void update(Dish dish);
DishMapper.xml
<update id="update">
update dish
<set>
<if test="name != null and name!= ''">name=#{name},</if>
<if test="categoryId != null ">category_id=#{categoryId},</if>
<if test="price != null">price=#{price},</if>
<if test="image != null and image!= ''">image=#{image},</if>
<if test="description != null and description!= ''">description=#{description},</if>
<if test="status != null">status=#{status},</if>
<if test="updateTime != null">update_time=#{updateTime},</if>
<if test="updateUser != null">update_user=#{updateUser},</if>
</set>
where id = #{id}
</update>
SetmealDishMapper
@Select("select setmeal_id from setmeal_dish where dish_id = #{dishId}")
List<Long> findSetmealIdsByDishId(Long dishId);
SetmealMapper
void updateStatus(List<Long> ids, Integer status, LocalDateTime updateTime, Long updateUser);
SetmealMapper.xml
<update id="updateStatus">
update setmeal set status=#{status},update_time=#{updateTime},update_user=#{updateUser}
where id in
<!--若参数列表一个参数,且类型为list或者array的时候,遵守上午的写法
若有多个参数的话,collection属性就需要编写属性名
-->
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</update>
五回显菜品(选做)
1 需求
根据菜品id查询菜品信息+口味信息
![](https://i-blog.csdnimg.cn/blog_migrate/a1203b7f8885a699213e29036728c1fe.png)
2 思路分析
3 代码实现
DishController
DishService
DishServicelmpl
DishMapper
DishFlavorMapper
六修改菜品(选做)
需求
修改的菜品信息
菜品口味列表数量改变:先删后加
![](https://i-blog.csdnimg.cn/blog_migrate/8689088ff2e0e4421e326686843f9d55.png)
思路分析
代码实现
DishController
DishService
DishServicelmpl
DishMapper
DishMapper.xml
DishFlavorMapper