init: 遗留订单系统初始版本

This commit is contained in:
micrograils
2026-06-13 16:53:08 +08:00
commit caffc89686
16 changed files with 1506 additions and 0 deletions

268
teacher-docs/README.md Normal file
View File

@@ -0,0 +1,268 @@
# 遗留订单系统 - 项目说明与学习指南
> 本项目是一个刻意构造的“遗留系统”代码练习场,供团队内部进行代码审查、重构、安全测试等技能训练使用。
---
## 一、项目概述
这是一个模拟真实环境的电商订单系统,代码风格、架构设计、命名规范均刻意模仿了多年维护、多人经手的遗留项目。系统本身**功能可用**,但代码质量和安全性存在大量隐患。
### 技术栈
| 组件 | 版本 |
|------|------|
| JDK | 1.8 |
| Spring Boot | 2.1.18.RELEASE |
| 数据库 | H2 (内存模式) |
| 数据库访问 | 原生 JDBC |
| 构建工具 | Maven 3.x |
### 快速启动
```bash
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` | 订单统计报表 |
### 创建订单示例
```bash
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 分钟)
**目标**:面对无注释、命名混乱的代码,快速理解业务逻辑。
**练习内容**
1. 打开 `OrderService.java`,阅读 `createOrder()` 方法
2. 尝试解释每个方法的业务含义
3. 为代码添加必要的中文注释
4. 绘制订单创建流程图
**关注点**
- 拼音 + 英文混合命名的识别dingdanBianhao = 订单编号kehuMingzi = 客户名字)
- 硬编码税率 0.17 的历史背景
- 多级 if-else 折扣逻辑的业务规则
---
### 场景 2代码重构建议 15-25 分钟)
**目标**:拆分上帝类和大方法,消除重复代码。
**练习内容**
1. 拆分 `createOrder()` 方法,提取为独立职责的方法:
- 请求校验 → `validateOrderRequest()`
- 金额计算 → `computePrices()`
- 折扣计算 → `applyDiscount()`
- 税额计算 → `calculateTax()`
2. 找出并消除 `OrderService` 中 3 处重复的价格计算逻辑
3. 将硬编码常量提取为配置项
**关注点**
- 提取方法时确保不改变原有业务逻辑
- 重复代码的识别标准(不仅仅是完全相同的代码,逻辑等价也要识别)
---
### 场景 3补充测试建议 15-20 分钟)
**目标**:为无测试覆盖的遗留系统建立测试保护网。
**练习内容**
1. 添加 `spring-boot-starter-test` 依赖
2. 为核心方法编写单元测试:
- `createOrder()` — 正常场景、边界值、异常场景
- `cancelOrder()` — 正常取消、不存在订单、已取消订单
- `calcDiscount()` — 各折扣阶梯
3.`OrderDao` 编写集成测试(使用 H2 内存数据库)
**关注点**
- 由于代码耦合度高,编写测试本身就会发现设计问题
- static 单例和共享状态使测试变得困难,说明可测试性对设计的重要性
---
### 场景 4修复缺陷建议 15-20 分钟)
**目标**:审查代码,发现并修复典型缺陷。
**练习内容**
1. 审查整个项目,找出所有潜在缺陷和安全漏洞
2. 逐一修复,每种修复完成后运行测试验证
3. **SQL 注入验证**:构造恶意请求确认漏洞存在,修复后验证已消除
**SQL 注入复现**
```bash
# 正常搜索
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。
**涉及变更**
1. `pom.xml`:升级版本号
2. 包名迁移:`javax.*``jakarta.*`
3. 配置迁移:`spring.datasource.initialization-mode``spring.sql.init.mode`
4. H2 版本兼容性处理
5. 已知 API 变更适配
**关注点**
- 升级后功能是否正常
- 哪些废弃 API 需要替换
---
### 场景 6添加新功能建议 20-30 分钟)
选择一个方向在现有代码基础上添加新功能。**建议先写测试再写代码**。
**方向 A退款功能**
1. 新增 `REFUNDED` 订单状态
2. 实现退款逻辑(已完成订单可退款,退款金额为原单 80%
3. 添加 `PUT /api/orders/{id}/refund` API
**方向 B优惠券功能**
1. 新增优惠券模型(支持满减券和折扣券)
2.`createOrder()` 中集成优惠券折扣
3. 处理优惠券与现有折扣的叠加规则
**关注点**
- 在不破坏原有逻辑的前提下扩展功能
- 是否应先重构再添加新功能
---
## 五、项目代码结构
```
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
```
---
## 六、使用建议
1. **代码审查优先**:在对代码进行任何修改之前,先完整阅读和理解现有代码。
2. **小步迭代**:每次只修改一个问题,修改后立即运行测试验证。
3. **测试先行**:在重构前先补上关键路径的单元测试,确保重构不破坏功能。
4. **记录修改**:每次修改后记录改动点、原因和影响范围。
5. **关注设计**:不仅修 Bug还要思考为什么会写出这样的 Bug以及如何在设计层面避免。