# 遗留订单系统 - 项目说明与学习指南 > 本项目是一个刻意构造的“遗留系统”代码练习场,供团队内部进行代码审查、重构、安全测试等技能训练使用。 --- ## 一、项目概述 这是一个模拟真实环境的电商订单系统,代码风格、架构设计、命名规范均刻意模仿了多年维护、多人经手的遗留项目。系统本身**功能可用**,但代码质量和安全性存在大量隐患。 ### 技术栈 | 组件 | 版本 | |------|------| | 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,以及如何在设计层面避免。