9.6 KiB
9.6 KiB
遗留订单系统 - 项目说明与学习指南
本项目是一个刻意构造的“遗留系统”代码练习场,供团队内部进行代码审查、重构、安全测试等技能训练使用。
一、项目概述
这是一个模拟真实环境的电商订单系统,代码风格、架构设计、命名规范均刻意模仿了多年维护、多人经手的遗留项目。系统本身功能可用,但代码质量和安全性存在大量隐患。
技术栈
| 组件 | 版本 |
|---|---|
| JDK | 1.8 |
| Spring Boot | 2.1.18.RELEASE |
| 数据库 | H2 (内存模式) |
| 数据库访问 | 原生 JDBC |
| 构建工具 | Maven 3.x |
快速启动
mvn spring-boot:run
启动后:
- API 地址:
http://localhost:8080/api/orders - H2 控制台:
http://localhost:8080/h2-console(JDBC URL:jdbc:h2:mem:legacydb,用户名sa,密码password123)
接口一览
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/orders |
创建订单 |
| GET | /api/orders |
查询所有订单 |
| GET | /api/orders/{id} |
按ID查询订单 |
| GET | /api/orders/search?name=xxx |
按商品名称搜索 |
| PUT | /api/orders/{id}/cancel |
取消订单 |
| GET | /api/orders/report |
订单统计报表 |
创建订单示例
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{
"kehuMingzi": "张三",
"items": [
{"shangpinMingcheng": "机械键盘", "danjia": 399, "shuliang": 2},
{"shangpinMingcheng": "鼠标垫", "danjia": 29.9, "shuliang": 5}
]
}'
二、已知问题清单
2.1 安全漏洞
| # | 问题 | 位置 | 严重程度 | 说明 |
|---|---|---|---|---|
| 1 | SQL 注入 | OrderDao.findByProductName() |
严重 | 使用 Statement + 字符串拼接构建 SQL,攻击者可注入任意 SQL 语句 |
2.2 并发缺陷
| # | 问题 | 位置 | 说明 |
|---|---|---|---|
| 2 | 订单号生成非线程安全 | OrderService.generateOrderNumber() 中 static orderNumberSequence++ |
多线程并发创建订单时可能产生重复订单号 |
2.3 空指针风险
| # | 问题 | 位置 | 说明 |
|---|---|---|---|
| 3 | 未判空直接调用 | OrderService.cancelOrder() 中 order.getZhuangtai() |
查询不存在的订单返回 null 后直接调用,抛出 NullPointerException |
2.4 数值处理缺陷
| # | 问题 | 位置 | 说明 |
|---|---|---|---|
| 4 | 整型溢出 | OrderService.createOrder() 中 int intDiscount = (int)discount |
大金额订单折扣计算可能导致溢出,结果不符合预期 |
2.5 异常处理不当
| # | 问题 | 位置 | 说明 |
|---|---|---|---|
| 5 | 异常被吞没 | 全局 catch(Exception e) { e.printStackTrace(); } |
数据库异常等关键错误被隐藏,上层无法感知 |
三、代码质量问题速查
| 问题类型 | 典型位置 | 说明 |
|---|---|---|
| 上帝类 | OrderService.java(350+ 行) |
单个类承载了订单创建、取消、查询、报表等多种职责 |
| 长方法 | createOrder() 方法(200+ 行) |
校验、金额计算、折扣、税额全部耦合在一个方法内 |
| 重复代码 | OrderService 中价格计算逻辑 |
至少 3 处完全相同的价格计算循环 |
| 硬编码 | 税率 0.17、折扣阶梯、数据库密码 | 业务常量散布在代码中,没有配置化 |
| 手动 JDBC | 整个 DAO 层 | 未使用 JPA/MyBatis 等 ORM 框架,手写 SQL 和管理连接 |
| 静态单例 | DbUtil.java |
绕过 Spring 容器,自行管理数据库连接池 |
| 线程不安全 | SimpleDateFormat、static orderNumberSequence |
多线程环境下共享可变状态 |
| System.out 日志 | 整个项目 | 没有使用 SLF4J/Logback 等日志框架 |
| 命名混乱 | dingdanBianhao、kehuMingzi 等字段名 |
拼音 + 英文混合命名,无统一规范 |
| 无单元测试 | 整个项目 | 测试覆盖率为零,重构风险极高 |
| 无 DTO 分层 | Controller 直接暴露实体类 | API 响应结构与数据库模型耦合 |
四、推荐练习场景
以下场景可按顺序逐步推进,也可根据时间选取重点练习。每个场景均适合使用 AI 辅助完成。
场景 1:阅读理解(建议 10-15 分钟)
目标:面对无注释、命名混乱的代码,快速理解业务逻辑。
练习内容:
- 打开
OrderService.java,阅读createOrder()方法 - 尝试解释每个方法的业务含义
- 为代码添加必要的中文注释
- 绘制订单创建流程图
关注点:
- 拼音 + 英文混合命名的识别(dingdanBianhao = 订单编号,kehuMingzi = 客户名字)
- 硬编码税率 0.17 的历史背景
- 多级 if-else 折扣逻辑的业务规则
场景 2:代码重构(建议 15-25 分钟)
目标:拆分上帝类和大方法,消除重复代码。
练习内容:
- 拆分
createOrder()方法,提取为独立职责的方法:- 请求校验 →
validateOrderRequest() - 金额计算 →
computePrices() - 折扣计算 →
applyDiscount() - 税额计算 →
calculateTax()
- 请求校验 →
- 找出并消除
OrderService中 3 处重复的价格计算逻辑 - 将硬编码常量提取为配置项
关注点:
- 提取方法时确保不改变原有业务逻辑
- 重复代码的识别标准(不仅仅是完全相同的代码,逻辑等价也要识别)
场景 3:补充测试(建议 15-20 分钟)
目标:为无测试覆盖的遗留系统建立测试保护网。
练习内容:
- 添加
spring-boot-starter-test依赖 - 为核心方法编写单元测试:
createOrder()— 正常场景、边界值、异常场景cancelOrder()— 正常取消、不存在订单、已取消订单calcDiscount()— 各折扣阶梯
- 为
OrderDao编写集成测试(使用 H2 内存数据库)
关注点:
- 由于代码耦合度高,编写测试本身就会发现设计问题
- static 单例和共享状态使测试变得困难,说明可测试性对设计的重要性
场景 4:修复缺陷(建议 15-20 分钟)
目标:审查代码,发现并修复典型缺陷。
练习内容:
- 审查整个项目,找出所有潜在缺陷和安全漏洞
- 逐一修复,每种修复完成后运行测试验证
- SQL 注入验证:构造恶意请求确认漏洞存在,修复后验证已消除
SQL 注入复现:
# 正常搜索
curl -G "http://localhost:8080/api/orders/search" --data-urlencode "name=键盘"
# 注入验证(返回所有订单,说明漏洞存在)
curl "http://localhost:8080/api/orders/search?name=' OR '1'='1'--"
修复要点:
- SQL 注入:
Statement→PreparedStatement,参数化查询 - 并发竞态:使用
AtomicLong或数据库序列 - 空指针:添加 null 检查或使用
Optional - 整型溢出:使用
BigDecimal处理金额 - 异常处理:区分可恢复和不可恢复异常,日志记录 + 业务处理
场景 5:技术栈升级(建议 15-20 分钟)
目标:将 Spring Boot 2.1.18 + Java 8 升级到 Spring Boot 3.x + Java 17/21。
涉及变更:
pom.xml:升级版本号- 包名迁移:
javax.*→jakarta.* - 配置迁移:
spring.datasource.initialization-mode→spring.sql.init.mode - H2 版本兼容性处理
- 已知 API 变更适配
关注点:
- 升级后功能是否正常
- 哪些废弃 API 需要替换
场景 6:添加新功能(建议 20-30 分钟)
选择一个方向在现有代码基础上添加新功能。建议先写测试再写代码。
方向 A:退款功能
- 新增
REFUNDED订单状态 - 实现退款逻辑(已完成订单可退款,退款金额为原单 80%)
- 添加
PUT /api/orders/{id}/refundAPI
方向 B:优惠券功能
- 新增优惠券模型(支持满减券和折扣券)
- 在
createOrder()中集成优惠券折扣 - 处理优惠券与现有折扣的叠加规则
关注点:
- 在不破坏原有逻辑的前提下扩展功能
- 是否应先重构再添加新功能
五、项目代码结构
legacy-order-system/
├── pom.xml
├── README.md
├── teacher-docs/ # 本目录
│ └── README.md
└── src/main/
├── java/com/legacy/order/
│ ├── LegacyOrderApplication.java # 启动类
│ ├── controller/
│ │ └── OrderController.java # REST 接口
│ ├── service/
│ │ └── OrderService.java # 业务逻辑
│ ├── dao/
│ │ └── OrderDao.java # 数据访问
│ ├── model/
│ │ ├── Order.java # 订单实体
│ │ ├── OrderItem.java # 订单项实体
│ │ └── OrderStatus.java # 订单状态枚举
│ └── util/
│ └── DbUtil.java # 数据库连接工具
└── resources/
├── application.properties # 配置文件
└── schema.sql # 数据库 DDL
六、使用建议
- 代码审查优先:在对代码进行任何修改之前,先完整阅读和理解现有代码。
- 小步迭代:每次只修改一个问题,修改后立即运行测试验证。
- 测试先行:在重构前先补上关键路径的单元测试,确保重构不破坏功能。
- 记录修改:每次修改后记录改动点、原因和影响范围。
- 关注设计:不仅修 Bug,还要思考为什么会写出这样的 Bug,以及如何在设计层面避免。