Java后端实战:耗时两周肝完《苍穹外卖》
前言
最近终于把黑马的《苍穹外卖》项目完整地过了一遍。作为 Java 后端学习路上的第一个企业级实战项目,做完的感觉和只看视频完全不一样。从最基础的 CRUD,到 Redis 缓存,再到最后的图形报表导出,这一路走来真的是“步步惊心”。
今天这篇博客主要想记录一下在做**“数据统计报表”**模块时遇到的几个经典 Bug,以及我对后端开发的一些新理解。希望对正在做这个项目的同学有所帮助!
项目技术栈
在开始吐槽 Bug 之前,先简单回顾一下这个项目的核心技术:
- 核心框架:Spring Boot + MyBatis
- 数据库:MySQL + Redis
- 工具库:Lombok, Swagger, POI (Excel处理)
- 前端:Nginx 反向代理 + Vue (虽然我是写后端的,但也得懂点部署)
实战“踩坑”记录 (干货部分)
在做最后几章的“运营数据报表”时,我遇到了两个让我怀疑人生的 Bug,排查了很久才明白其中的原理。
1. 关于钱的计算,别再用 double 了!
在计算“平均客单价”时,我一开始想当然地用了普通的除法,结果报错了。
错误示范:
Java
// 营业额(BigDecimal) / 订单数(Integer)
// 报错:Non-terminating decimal expansion; no exact representable decimal result.
unitPrice = turnover.divide(BigDecimal.valueOf(validOrderCount));
原因分析: 当营业额是 10,订单数是 3 时,结果是 3.3333… 无限循环小数。BigDecimal 如果不指定精度,就会直接抛出 ArithmeticException 异常。
正确写法: 必须在 .divide() 方法中同时指定保留小数位数和舍入模式。
Java
if (validOrderCount != 0) {
unitPrice = turnover.divide(
BigDecimal.valueOf(validOrderCount),
2, // 保留2位小数
RoundingMode.HALF_UP // 四舍五入
).doubleValue();
}
心得:涉及金额的计算,BigDecimal.ZERO 初始化和 RoundingMode 是标配,千万别偷懒!
2. Excel 模板文件读取不到 (InputStream 为 null)
在做导出功能时,我把 Excel 模板放在了 src/main/resources/template 下,代码也没写错:
Java
InputStream in = this.getClass().getClassLoader()
.getResourceAsStream("template/运营数据报表模板.xlsx");
但是 Debug 时 in 永远是 null,导致报空指针异常。
原因分析: 这就是 Java 项目的 target 目录机制! IDEA 运行程序时,读取的是编译后的 target/classes 目录,而不是我们的 src 目录。如果 Maven 没有及时把资源文件“搬运”过去,程序就找不到文件。
解决方案:
- 检查
src/main/resources是否被标记为 Resources Root。 - 只要运行 Maven 的
clean然后compile,强制重新编译,问题解决!
核心功能复盘:Excel 报表导出
这个功能是我觉得最像“大佬”写出来的代码。以前写接口都是返回 JSON,这次是直接返回二进制文件流。
实现思路:
- 查询数据:先从数据库把营业额、订单数查出来,封装成 VO 对象。
- 加载模板:读取现成的 Excel 模板文件。
- 写入数据:利用 Apache POI,把查到的数据填入 Excel 的指定单元格(Row 和 Cell)。
- 输出流下载:这是最关键的一步!
Java
// 不需要返回值,而是直接操作 HttpServletResponse
public void export(HttpServletResponse response) {
// ... POI 操作省略 ...
// 通过 Response 获取输出流
ServletOutputStream out = response.getOutputStream();
// 把 Excel 写入流,发给浏览器
excel.write(out);
out.close();
excel.close();
}
这里让我明白了,HttpServletResponse 就像一根管道,我们可以直接往里面灌二进制数据,浏览器接收到后就会自动触发下载。
总结
苍穹外卖这个项目确实让我对 Spring Boot 的理解加深了很多。
- 以前只知道
@Autowired,现在知道了什么是 IOC。 - 以前只会写 CRUD,现在知道了 Redis 缓存穿透、ThreadLocal 上下文这些骚操作
更多推荐


所有评论(0)