引言
欧克,接下来让我们步入正题
- 项目背景:黑马程序员《苍穹外卖》作为Java企业级开发实战项目,涵盖SpringBoot、MySQL、Redis等技术栈
- 学习目标:掌握外卖系统核心功能开发与全栈技术整合
技术选择
技术选型
技术栈概览
- 后端框架:SpringBoot + MyBatis-Plus + Spring MVC
- 数据库:MySQL主从复制 + Redis缓存
- 中间件:Nginx负载均衡 + Swagger接口文档
- 前端技术:H5 + Vue.js(可选扩展部分)
编译环境的配置
前端环境的搭建
在课程提供的资料中有Nginx负载均衡服务器,双击之后,等待屏幕一闪,即前端运行环境启动成功
注意:不要将上诉文件放在一个有中文的目录文件中,否则可能启动不了
后端环境的搭建
将课程提供的后端基本框架导入项目之中
制造git提交代码环境
注意:要选择正确的目录,不要选择错了
根据上面的操作将初始代码上传到本地仓库
接着,我们创建一个空的远程仓库,并且将本地仓库的代码文件推送到远程仓库之中
这样之后,我们就可以用git来提交代码啦!
远程仓库刷新后
后端数据库的准备
通过给的文件资料,阅读11个表相关结构,并且将sql语句完成
该项目所需要的11个表如图所示
简单的前后端联调测试
注意:你在开启springboot项目的时候要连接数据库,并且在配置文件中将密码改成自己的密码,否则报错:java.sql.SQLException
解决方法:
然后在浏览器访问刚刚搭建的前端环境,点击登录就可以啦!
Nginx反向代理
思考为什么这里访问路径不一致,却可以成功访问
原因如下:
用了Nginx反向代理
这里用了proxy_pass 配置进行了方向代理
负载均衡的配置如下:
注意:这里还是用的proxy_pass 指令,webservers是上面upstream自己定义的 服务器
导入接口文档
基于yapi导入客户端和管理端的接口
新建项目之后完成图示操作
完成之后接口就导入成功啦!
这里就展示一下管理端接口
Swagger
介绍:
可以在后端生成接口文档,进行在线接口测试
注意:Knife4j是Java mvc框架集成Swagger生成api文档的增强解决方案
使用步骤:
- 导入maven坐标:
注意:版本号在上面已经集中定义,方便管理(黑马提供的资料文件已导入)
配置类中配置:
设置映射:
注意注意! 如果你不配置这个映射,springboot mvc不会认为你访问的是一个静态资源,而是去访问一个动态的Controller
成果展示
你也可以添加一些日志来帮助你知道这个方法是否调用
常用命令
示例
成果展示
由于运用了注解所以不再是上面出现的login /logout
代码部分
完善登录代码
提高代码的安全性:
我们使用MD5加密方式对密码进行加密
注意:MD5只能加密,不能解密
数据库中对应的也要更改成:e10adc3949ba59abbe56e057f20f883e
更改Java中代码
这里运用spring提供的DigestUtils中提供的方法进行加密
那么到这一步重启服务,你的密码就加密成功啦!
新增员工
接口文档的分析与设计
代码
Controller层
Service层
注意:我写错了是TODO
更改如下:
Mapper层
测试
配置接口文档的全局设置(获取令牌)
完成测试
完善代码逻辑问题
处理新增重复异常问题
在全局异常中处理
处理刚刚TODO需优化的部分
我们资料中有BaseContext类里面定义了方法
理解:
我们每一次的请求都会在一个线程中完成,ThreadLocal是一个线程的局部变量,线程外的数据不会影响
我们可以通过这个当令牌解析出员工id时 利用set方法获取到id;
接着在线程到了service层时用get方法优化后端程序
步骤如下:
service层
员工的分页查询
接口的设计
实现代码
Controller层
Service层
实现类,接口类很简单,可以自己写
Mapper层
xml文件
测试
代码完善
解决方案
方法一:
方法二
启用和禁用账号功能
接口文档的设计
- 代码实现
Controller层
Service层实现类
mapper层
前后端联调
编辑员工
接口文档的分析
查询回显
编辑
代码部分
Controller层
Service层
实现类
Mapper层
测试
分类管理
新增分类
接口文档分析
代码:
Controller层
注意:这里接收前端的是Jason格式所以要注解,并且应该用PutMapping注解否则会报错
给前端相应的是请求错误,因为错用注解没有找到PutMapping
Service层
实现类
Mapper层
这里语句简单通过注解的方式写了
测试
分页查询
接口文档分析
代码
Controller层
Service层
实现类
Mapper层
xml映射文件
前后端联调
删除分类
接口文档分析
请求方式 Delete 请求数据query类型
代码:
Controller层
Service层
实现类
注意:因为这里关联了套餐和菜品所以不能随意删除需要判断该分类下是否有,有关联则不删除,反之则删除
Mapper层
统计菜品数量
套餐数量
CategoryMapper层
测试
通过类型查询分类
接口文档的分析
代码部分
Controller层
Service层
实现类
注意:这里是查询所以要用集合去接收你查询到的数据
Mapper层
xml文件
前后端联调测试
修改分类
接口文档的分析
代码
Conroller层
Service层
实现类
Mapper层
xml文件
前后端联调测试
分类的启用和禁用
接口文档的分析
代码
Controller层
Service层
实现类
Mapper层
注意:由于这里分类的禁用和启动都是修改,前面我们通过new对象已经把状态封装到了Category实体类中了,这里还是直接调用修改的语句就行!!!
前后端联调测试
自动补充公共字段
问题描述
注意:不可能写每一个公共字段的时候都在service实现类中写,这样代码冗余过多,不利于维护
代码实现
思路
自定义注解
定义切面类:
到这里,我们就通过反射的方法完成了,然后自己手动将我们之前写的补充代码注释一下就完成了
测试:
可以看到修改时间等四个公共字段已经填充
新增菜品
注意:这里根据类型查询分类已经写了,所以只需要完成 文件上传和新增菜品
文件上传
接口分析:
代码:
Controller层
1.获取原始文件名,利用前端传入的file.getOriginalname()
2.获取原始文件后缀,利用lastIndexof(".")获取后缀
3.利用UUID.randomUUID.toString()+你获取的文件后缀名
工具类:
注意:这里就是写upload方法 让Controller层获取路径
简单来说就是controller层获取图片url地址,然后生成一个新的地址(防止多用户传过来的地址重名),然后调用阿里oss工具类,然后阿里oss可以接收到这个文件并且生成一个url地址,返回给前端,前端自己去oss访问这个url然后获得图片
其中要保证accesskey等信息正确,且服务器端配置了正确的环境系统变量
测试:
前端显示了图片
新增菜品
文档接口分析:
代码:
Controller层:
Service层实现类:
Mapper层:
dishMapper:
注意:这里使用了自动填充
DishFlavorMapper:
测试:
注意:这里我已经写了分页查询所以展示出来了!但是这里的阿里云bucket我是用的自己的所以照片没有展示出来(要收费)
分页查询
接口文档:
代码:
Controller层:
Service层实现类:
Mapper层:
测试:
删除菜单
接口文档分析:
代码部分:
Controller层:
Service层实现类:
Mapper
删除口味
修改菜单
接口文档分析:
代码部分:
Controlller层:
Service层实现类
Mapper层
营业状态设置
代码部分:
由于营业状态就一个字段所以这里使用redis
首先我们引入redis的相关依赖
接着我们要创建redis配置类,创建
redisTemplate对象,并且解决一下序列化的问题
接下来就可以使用redis通过内存来存储营业状态
admin端代码:
user端代码:
微信登录接口
小程序与后端服务器是如何联系在一起的?
可以看到是用户在小程序登录,然后生成code,微信小程序在发送请求并且把code携带给java后端,后端通过@Requestbody注解接收这个小程序传过来的Jason数据(code) 后端这时候通过调用httpclient这个api发送请求给微信官方服务器(携带appid等信息),这个时候微信官方服务器处理这些信息并且返回一个openid ,session_key
Controller层
Service层
这个 是获取openid的方法
注意:
这里Controller层为什么要返回openid ,token
后端将这些信息发送给小程序,小程序把这些信息缓存下来,之后的每次请求都是把这个信息带给后端,这时候后端要创建拦截器拦截,验证并且解析token;
拦截器
写拦截器并且注册
将商品浏览代码导入
缓存菜品数据
由于菜品数据访问次数多,如果多人同时访问,数据库压力大,会导致响应很慢等用户体验不好的情况
所以通过redis缓存数据;
代码部分:
![]()
这里将信息存到redis String类型的键值对中,将查询过来的数组序列化成redis中的String类型
清理缓存
当管理员在修改删除,增加菜品之后微信小程序展示的菜品都会变,所以要清除缓存
代码部分:
写一个方法直接调用,减少代码冗余
后面修改菜品删除菜品直接调用这个方法就可以
套餐缓存
原生操作 Redis,我们要写大量重复代码:redisTemplate.opsForValue().get()、set()、delete(),每个查询方法都要写一遍,代码冗余。SpringCache 是 Spring 提供的缓存抽象框架,基于注解,不用手动写 Redis 操作代码。
Spring Cache 的方法
代码部分:
添加购物车
基本思路:
代码部分:
controller层:
service层:
你要判断添加的是菜品还是套餐,分别通过id去查询,然后每个属性自己设置就好了
Mapper层:
判断是否有时,你不知道添加的时套餐还是菜品,不知道添加的菜品有没有口味,所以用动态sql去查询;
查询购物车:
代码部分:
controller层:
service层:
清除购物车
代码:
导入地址薄相关代码
用户订单接口:
接口文档分析
:
代码部分:
controller层:
service实现类:
mapper层:
思路:
第一步用户提交的订单你要确保购物车不为空,地址不为空所以处理异常
第二步 插入订单表
我们每个表结构对应的实体类,所以我们要先把前端收到的dto属性值赋值给实体类,并且补全相关属性值(这里还要返回主键值给订单明细表用)
第三步插入订单明细表,因为订单明细是通过购物车来写的,所以我们通过查询购物车,将信息赋值给orderdetail
第四步清除购物车调用clean方法
第五步返回vo;wei
微信支付:
由于我们不是商户,这里我们模拟微信支付即可
我这里添加了模拟定单支付的流程
代码部分:
这里将微信预支付订单给小程序,小程序在给微信服务端
实现类调用wechatpayutils这个工具类里面的pay方法,然后pay方法里面调用了httpclient这个api 给微信服务器发送一个post请求,并且返回微信预支付交易单标识,也将所需信息返回一起封装在了orderPaymentVo这个实体类中
返回给小程序中后,小程序通过wx.requestPayment拉起微信支付弹窗
Websocket
WebSocket 是全双工长连接协议,先通过 HTTP 完成握手,协议升级成功后,服务端、客户端可以互相主动发消息(苍穹外卖用来做新订单推送、催单提醒)
apache poi
ava 用来读写 Office 文件的开源库,苍穹外卖导出 Excel 报表就是用它!
核心板块
| 模块 | 作用 |
|---|---|
| HSSF | 读写.xls(Excel 97-2003,老版) |
| XSSF | 读写.xlsx(Excel2007+,常用) |
| SXSSF | 超大 Excel 导出,防止内存溢出(大数据推荐) |
| HWPF | Word(.doc) |
| XWPF | Word(.docx) |
代码部分:
依赖:
<!-- POI 核心 导出xlsx --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.4</version> </dependency>public void export(HttpServletResponse response) {
//先查询数据库,近30天的营业数据
LocalDate beginTime = LocalDate.now().minusDays(30);
LocalDate endTime = LocalDate.now().minusDays(1);
BusinessDataVO businessDataVO = workspaceService.getBusinessData(LocalDateTime.of(beginTime, LocalTime.MIN), LocalDateTime.of(endTime, LocalTime.MAX));
//通过poi将数据写入excel中
try {
//设置response响应头,浏览器下载文件
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("运营数据报表", StandardCharsets.UTF_8).toString();
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
//基于模板文件创建一个新的Excel文件
XSSFWorkbook excel = new XSSFWorkbook(in);
//获取表格文件的Sheet页
XSSFSheet sheet = excel.getSheet("Sheet1");
//填充数据--时间
sheet.getRow(1).getCell(1).setCellValue("时间:" + beginTime + "至" + endTime);
//获得第4行(索引从0开始)
XSSFRow row = sheet.getRow(3);
row.getCell(2).setCellValue(businessDataVO.getTurnover());
row.getCell(4).setCellValue(businessDataVO.getOrderCompletionRate());
row.getCell(6).setCellValue(businessDataVO.getNewUsers());
//获得第5行
row = sheet.getRow(4);
row.getCell(2).setCellValue(businessDataVO.getValidOrderCount());
row.getCell(4).setCellValue(businessDataVO.getUnitPrice());
//填充明细数据 循环30天
for (int i = 0; i < 30; i++) {
LocalDate date = beginTime.plusDays(i);
//查询某一天的营业数据
BusinessDataVO businessData = workspaceService.getBusinessData(
LocalDateTime.of(date, LocalTime.MIN),
LocalDateTime.of(date, LocalTime.MAX)
);
//获得某一行,从第8行开始(下标7)
row = sheet.getRow(7 + i);
row.getCell(1).setCellValue(date.toString());
row.getCell(2).setCellValue(businessData.getTurnover());
row.getCell(3).setCellValue(businessData.getValidOrderCount());
row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
row.getCell(5).setCellValue(businessData.getUnitPrice());
row.getCell(6).setCellValue(businessData.getNewUsers());
}
//3. 通过输出流将Excel文件下载到客户端浏览器
ServletOutputStream out = response.getOutputStream();
excel.write(out);
//关闭资源
out.flush();
out.close();
excel.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
其他的都是跟之前一样的crud这里就不重复了