461 lines
12 KiB
Markdown
461 lines
12 KiB
Markdown
# HealthLink-HIS 代码库优化实施计划
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use compose:subagent (recommended) or compose:execute to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** 修复健康检查发现的 Critical/High 级别问题,提升代码质量和可维护性
|
||
|
||
**Architecture:** 保持现有分层架构(Controller → AppService → Service → Mapper → Entity),重点解决 God Classes、重复代码、测试覆盖等结构性问题
|
||
|
||
**Tech Stack:** Java 25, Spring Boot 4.0.6, MyBatis-Plus 3.5.16, JUnit 5, Mockito
|
||
|
||
---
|
||
|
||
## 任务概览
|
||
|
||
| 优先级 | 任务 | 预计时间 | 影响范围 |
|
||
|:------:|------|:--------:|----------|
|
||
| P0 | 删除重复文件 | 30分钟 | 2个文件 |
|
||
| P0 | 修复脆弱断言 | 1小时 | 8个测试文件 |
|
||
| P1 | 提取测试基类 | 2小时 | 新建1个基类 |
|
||
| P1 | 清理过期TODO | 1小时 | ~20个文件 |
|
||
| P2 | 拆分IChargeBillServiceImpl | 8小时 | 1个God Class |
|
||
| P2 | 添加单元测试框架 | 4小时 | 新建测试结构 |
|
||
|
||
---
|
||
|
||
## Task 1: 删除重复文件(消除classpath冲突风险)
|
||
|
||
**Covers:** 架构维度 Finding 2
|
||
|
||
**Files:**
|
||
- Delete: `healthlink-his-yb/src/main/java/com/healthlink/his/yb/util/YbParamBuilderUtil.java`
|
||
- Delete: `healthlink-his-yb/src/main/java/com/healthlink/his/yb/dto/Yb4401InputBaseInfoDto.java`
|
||
- Modify: `healthlink-his-yb/pom.xml` (确认依赖)
|
||
|
||
- [ ] **Step 1: 确认重复文件存在**
|
||
|
||
```bash
|
||
# 验证两个文件内容相同
|
||
diff healthlink-his-domain/src/main/java/com/healthlink/his/yb/util/YbParamBuilderUtil.java healthlink-his-yb/src/main/java/com/healthlink/his/yb/util/YbParamBuilderUtil.java
|
||
```
|
||
|
||
- [ ] **Step 2: 检查yb模块是否直接使用这些文件**
|
||
|
||
```bash
|
||
# 搜索yb模块中的引用
|
||
rg "YbParamBuilderUtil" healthlink-his-yb/src --include="*.java" | grep -v "^.*YbParamBuilderUtil.java:"
|
||
rg "Yb4401InputBaseInfoDto" healthlink-his-yb/src --include="*.java" | grep -v "^.*Yb4401InputBaseInfoDto.java:"
|
||
```
|
||
|
||
- [ ] **Step 3: 删除重复文件**
|
||
|
||
```bash
|
||
rm healthlink-his-yb/src/main/java/com/healthlink/his/yb/util/YbParamBuilderUtil.java
|
||
rm healthlink-his-yb/src/main/java/com/healthlink/his/yb/dto/Yb4401InputBaseInfoDto.java
|
||
```
|
||
|
||
- [ ] **Step 4: 验证编译通过**
|
||
|
||
```bash
|
||
mvn clean compile -DskipTests
|
||
```
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add -A
|
||
git commit -m "fix: remove duplicate files to prevent classpath conflicts"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 2: 修复脆弱断言(提高测试可信度)
|
||
|
||
**Covers:** 测试维度 Finding 5A
|
||
|
||
**Files:**
|
||
- Modify: `healthlink-his-application/src/test/java/com/healthlink/his/web/doctorstation/DoctorWorkstationTest.java`
|
||
- Modify: `healthlink-his-application/src/test/java/com/healthlink/his/web/registration/RegistrationApiTest.java`
|
||
- Modify: `healthlink-his-application/src/test/java/com/healthlink/his/web/report/ReportApiTest.java`
|
||
|
||
- [ ] **Step 1: 修复DoctorWorkstationTest中的脆弱断言**
|
||
|
||
```java
|
||
// 修改前 (line 221-226):
|
||
assertTrue("未授权应返回401/403", code == 401 || code == 403 || code == 200);
|
||
|
||
// 修改后:
|
||
assertTrue("未授权应返回401或403", code == 401 || code == 403);
|
||
assertFalse("未授权不应返回200", code == 200);
|
||
```
|
||
|
||
- [ ] **Step 2: 修复RegistrationApiTest中的空断言**
|
||
|
||
```java
|
||
// 修改前 (line 221-229):
|
||
if (result.path("code").asInt() == 200) {
|
||
// If 200, check msg
|
||
}
|
||
|
||
// 修改后:
|
||
int code = result.path("code").asInt();
|
||
assertTrue("退号失败应返回错误码", code != 200 || result.path("msg").asText().contains("失败"));
|
||
```
|
||
|
||
- [ ] **Step 3: 修复ReportApiTest中的永真断言**
|
||
|
||
```java
|
||
// 修改前 (line 126-129):
|
||
assertTrue("...", result.path("code").asInt() != 500 || result.path("code").asInt() == 500);
|
||
|
||
// 修改后:
|
||
int code = result.path("code").asInt();
|
||
assertTrue("应返回成功或业务错误", code == 200 || code == 500 || (code >= 400 && code < 500));
|
||
```
|
||
|
||
- [ ] **Step 4: 运行测试验证**
|
||
|
||
```bash
|
||
cd healthlink-his-application && mvn test -Dtest="DoctorWorkstationTest,RegistrationApiTest,ReportApiTest"
|
||
```
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add -A
|
||
git commit -m "fix(test): replace fragile assertions with meaningful validations"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 3: 提取测试基类(消除重复代码)
|
||
|
||
**Covers:** 测试维度 Finding 5D
|
||
|
||
**Files:**
|
||
- Create: `healthlink-his-application/src/test/java/com/healthlink/his/web/BaseApiTest.java`
|
||
- Modify: 8个测试文件(继承基类)
|
||
|
||
- [ ] **Step 1: 创建BaseApiTest基类**
|
||
|
||
```java
|
||
package com.healthlink.his.web;
|
||
|
||
import io.restassured.RestAssured;
|
||
import io.restassured.response.Response;
|
||
import org.junit.jupiter.api.BeforeAll;
|
||
import org.junit.jupiter.api.TestInstance;
|
||
|
||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||
public abstract class BaseApiTest {
|
||
|
||
protected static String token;
|
||
|
||
@BeforeAll
|
||
void setUp() {
|
||
// 登录获取token
|
||
Response loginResponse = RestAssured.given()
|
||
.contentType("application/json")
|
||
.body("{\"username\":\"admin\",\"password\":\"admin123\"}")
|
||
.post("/auth/login");
|
||
|
||
token = loginResponse.jsonPath().getString("token");
|
||
}
|
||
|
||
protected Response get(String path) {
|
||
return RestAssured.given()
|
||
.header("Authorization", "Bearer " + token)
|
||
.get(path);
|
||
}
|
||
|
||
protected Response post(String path, Object body) {
|
||
return RestAssured.given()
|
||
.header("Authorization", "Bearer " + token)
|
||
.contentType("application/json")
|
||
.body(body)
|
||
.post(path);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 修改DoctorWorkstationTest继承基类**
|
||
|
||
```java
|
||
// 修改前:
|
||
public class DoctorWorkstationTest {
|
||
// ... 重复的登录代码
|
||
|
||
// 修改后:
|
||
public class DoctorWorkstationTest extends BaseApiTest {
|
||
// 删除重复的登录代码
|
||
```
|
||
|
||
- [ ] **Step 3: 对其他7个测试文件执行相同修改**
|
||
|
||
```bash
|
||
# 批量替换(示例)
|
||
sed -i 's/public class RegistrationApiTest {/public class RegistrationApiTest extends BaseApiTest {/' RegistrationApiTest.java
|
||
```
|
||
|
||
- [ ] **Step 4: 运行所有测试验证**
|
||
|
||
```bash
|
||
mvn test -pl healthlink-his-application
|
||
```
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add -A
|
||
git commit -m "refactor(test): extract BaseApiTest to eliminate login duplication"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 4: 清理过期TODO(消除技术债务标记)
|
||
|
||
**Covers:** 技术债务维度 Finding 3
|
||
|
||
**Files:**
|
||
- Modify: `healthlink-his-domain/src/main/java/com/healthlink/his/yb/util/TenantOptionUtil.java`
|
||
- Modify: 其他过期TODO文件
|
||
|
||
- [ ] **Step 1: 搜索所有过期TODO**
|
||
|
||
```bash
|
||
rg "TODO.*2025|FIXME|HACK" healthlink-his-domain/src healthlink-his-application/src --include="*.java" -l
|
||
```
|
||
|
||
- [ ] **Step 2: 修复TenantOptionUtil中的过期TODO**
|
||
|
||
```java
|
||
// 修改前 (line 36):
|
||
// TODO:2025/10/17 李永兴提出的sys_option切换TenantOption临时防止报错方案,最晚2025年11月底删除
|
||
|
||
// 修改后: 直接删除这行注释(代码逻辑已正确)
|
||
```
|
||
|
||
- [ ] **Step 3: 评估其他TODO并分类**
|
||
|
||
```bash
|
||
# 统计TODO数量
|
||
rg "TODO" healthlink-his-domain/src healthlink-his-application/src --include="*.java" -c | awk -F: '{sum+=$2} END {print sum}'
|
||
```
|
||
|
||
- [ ] **Step 4: 为高风险TODO创建issue跟踪**
|
||
|
||
```bash
|
||
# 示例:为YbServiceImpl中的TODO创建备忘
|
||
echo "TODO:YbServiceImpl:274-后续处理需等待门诊住院开发完全后" >> docs/TODO_TRACKING.md
|
||
```
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add -A
|
||
git commit -m "chore: clean up expired TODOs and create tracking document"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 5: 拆分IChargeBillServiceImpl(解决God Class问题)
|
||
|
||
**Covers:** 架构维度 Finding 1, 技术债务维度 Finding 1
|
||
|
||
**Files:**
|
||
- Split: `IChargeBillServiceImpl.java` (2764行) → 多个服务类
|
||
- Create: `ChargeBillQueryService.java`
|
||
- Create: `ChargeBillCalculationService.java`
|
||
- Create: `ChargeBillStatisticsService.java`
|
||
|
||
- [ ] **Step 1: 分析IChargeBillServiceImpl的方法职责**
|
||
|
||
```bash
|
||
# 列出所有public方法
|
||
rg "public .* \w+\(" healthlink-his-application/src/main/java/com/healthlink/his/web/paymentmanage/appservice/impl/IChargeBillServiceImpl.java | head -20
|
||
```
|
||
|
||
- [ ] **Step 2: 创建ChargeBillQueryService(查询相关)**
|
||
|
||
```java
|
||
package com.healthlink.his.web.paymentmanage.appservice;
|
||
|
||
import org.springframework.stereotype.Service;
|
||
|
||
@Service
|
||
public class ChargeBillQueryService {
|
||
|
||
public Page<ChargeBillDto> getChargeBills(ChargeBillQueryDto query) {
|
||
// 从IChargeBillServiceImpl迁移查询逻辑
|
||
}
|
||
|
||
public ChargeBillDetailDto getChargeBillDetail(Long billId) {
|
||
// 从getDetail()方法迁移
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: 创建ChargeBillCalculationService(计算相关)**
|
||
|
||
```java
|
||
@Service
|
||
public class ChargeBillCalculationService {
|
||
|
||
public ChargeBillSummary calculateSummary(List<ChargeItem> items) {
|
||
// 从getTotal()方法迁移
|
||
}
|
||
|
||
public BigDecimal calculateInsurance(ChargeBillSummary summary, Contract contract) {
|
||
// 从getTotalCommen()方法迁移
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: 创建ChargeBillStatisticsService(统计相关)**
|
||
|
||
```java
|
||
@Service
|
||
public class ChargeBillStatisticsService {
|
||
|
||
public StatisticsDto getStatistics(DateRange range) {
|
||
// 从getTotalCcu()方法迁移
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 5: 重构IChargeBillServiceImpl使用新服务**
|
||
|
||
```java
|
||
@Service
|
||
public class ChargeBillAppServiceImpl implements IChargeBillAppService {
|
||
|
||
@Autowired
|
||
private ChargeBillQueryService queryService;
|
||
|
||
@Autowired
|
||
private ChargeBillCalculationService calculationService;
|
||
|
||
@Autowired
|
||
private ChargeBillStatisticsService statisticsService;
|
||
|
||
@Override
|
||
public Page<ChargeBillDto> getChargeBills(ChargeBillQueryDto query) {
|
||
return queryService.getChargeBills(query);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 6: 运行测试验证功能不变**
|
||
|
||
```bash
|
||
mvn test -pl healthlink-his-application -Dtest="BillingApiTest,PaymentApiTest"
|
||
```
|
||
|
||
- [ ] **Step 7: Commit**
|
||
|
||
```bash
|
||
git add -A
|
||
git commit -m "refactor: split IChargeBillServiceImpl into query/calculation/statistics services"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 6: 添加单元测试框架(建立测试基础设施)
|
||
|
||
**Covers:** 测试维度 Finding 4
|
||
|
||
**Files:**
|
||
- Create: `healthlink-his-domain/src/test/java/com/healthlink/his/BaseUnitTest.java`
|
||
- Create: `healthlink-his-domain/src/test/java/com/healthlink/his/payment/ChargeBillCalculationServiceTest.java`
|
||
|
||
- [ ] **Step 1: 创建BaseUnitTest基类**
|
||
|
||
```java
|
||
package com.healthlink.his;
|
||
|
||
import org.junit.jupiter.api.extension.ExtendWith;
|
||
import org.mockito.InjectMocks;
|
||
import org.mockito.junit.jupiter.MockitoExtension;
|
||
|
||
@ExtendWith(MockitoExtension.class)
|
||
public abstract class BaseUnitTest {
|
||
// Mockito配置
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 创建ChargeBillCalculationService的单元测试**
|
||
|
||
```java
|
||
package com.healthlink.his.payment;
|
||
|
||
import org.junit.jupiter.api.Test;
|
||
import static org.junit.jupiter.api.Assertions.*;
|
||
|
||
class ChargeBillCalculationServiceTest extends BaseUnitTest {
|
||
|
||
@InjectMocks
|
||
private ChargeBillCalculationService service;
|
||
|
||
@Test
|
||
void calculateSummary_withValidItems_returnsCorrectTotal() {
|
||
// Given
|
||
List<ChargeItem> items = Arrays.asList(
|
||
new ChargeItem("药品A", new BigDecimal("100.00")),
|
||
new ChargeItem("药品B", new BigDecimal("200.00"))
|
||
);
|
||
|
||
// When
|
||
ChargeBillSummary summary = service.calculateSummary(items);
|
||
|
||
// Then
|
||
assertEquals(new BigDecimal("300.00"), summary.getTotalAmount());
|
||
}
|
||
|
||
@Test
|
||
void calculateSummary_withEmptyItems_returnsZero() {
|
||
// Given
|
||
List<ChargeItem> items = Collections.emptyList();
|
||
|
||
// When
|
||
ChargeBillSummary summary = service.calculateSummary(items);
|
||
|
||
// Then
|
||
assertEquals(BigDecimal.ZERO, summary.getTotalAmount());
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: 运行单元测试**
|
||
|
||
```bash
|
||
mvn test -pl healthlink-his-domain -Dtest="ChargeBillCalculationServiceTest"
|
||
```
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add -A
|
||
git commit -m "test: add unit test framework and calculation service tests"
|
||
```
|
||
|
||
---
|
||
|
||
## 执行顺序
|
||
|
||
1. Task 1(删除重复文件)- 立即执行,风险最低
|
||
2. Task 2(修复脆弱断言)- 立即执行,提高测试可信度
|
||
3. Task 3(提取测试基类)- 短期执行,消除重复
|
||
4. Task 4(清理过期TODO)- 短期执行,减少噪音
|
||
5. Task 5(拆分God Class)- 中期执行,需要仔细设计
|
||
6. Task 6(添加单元测试)- 长期执行,建立测试文化
|
||
|
||
---
|
||
|
||
## 验证标准
|
||
|
||
每个Task完成后必须验证:
|
||
- [ ] `mvn clean compile -DskipTests` 编译通过
|
||
- [ ] `mvn test` 测试通过
|
||
- [ ] 无新增编译警告
|
||
- [ ] git commit 包含清晰的变更说明
|