Files
ccdi/docs/plans/backend/2026-03-30-project-detail-risk-people-export-backend-implementation.md

356 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Project Detail Risk People Export Backend Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
>
> **Repo note:** 本仓库 `AGENTS.md` 明确禁止开启 subagent执行本计划时请在当前会话使用 `superpowers:executing-plans`。
**Goal:** 为项目详情“结果总览 -> 风险总览”人员列表补齐后端 Excel 导出能力,导出当前项目全部风险人员,并保证导出口径与页面表格字段一致。
**Architecture:** 后端继续沿用 `CcdiProjectOverviewController -> ICcdiProjectOverviewService -> CcdiProjectOverviewServiceImpl -> CcdiProjectOverviewMapper.xml` 这条结果总览链路,不新增平行模块或补丁接口。分页查询与导出查询共用同一组风险人员列映射与排序口径,服务层继续复用 `buildRiskPeopleItem(...)` 统一组装页面字段,再映射为导出 Excel 对象,避免页面与导出出现两套业务口径。
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MyBatis XML, ExcelUtil, JUnit 5, Mockito, Maven, Swagger/OpenAPI
---
### Task 1: 建立风险人员导出契约
**Files:**
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiProjectRiskPeopleOverviewExcel.java`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java`
- [ ] **Step 1: 先写失败测试锁定导出接口路径、Service 新签名和 Excel 对象列**
```java
Method method = CcdiProjectOverviewController.class.getMethod(
"exportRiskPeople",
HttpServletResponse.class,
Long.class
);
assertEquals("/risk-people/export", method.getAnnotation(PostMapping.class).value()[0]);
```
```java
assertNotNull(ICcdiProjectOverviewService.class.getMethod("exportRiskPeopleOverview", Long.class));
```
```java
assertNotNull(CcdiProjectRiskPeopleOverviewExcel.class.getDeclaredField("name"));
assertNotNull(CcdiProjectRiskPeopleOverviewExcel.class.getDeclaredField("riskPoint"));
```
- [ ] **Step 2: 运行控制器结构测试,确认当前仓库还没有风险人员导出入口**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewControllerTest test
```
Expected:
- FAIL提示 `exportRiskPeople``exportRiskPeopleOverview` 尚不存在
- [ ] **Step 3: 写最小导出契约**
```java
@PostMapping("/risk-people/export")
@Operation(summary = "导出风险人员总览")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportRiskPeople(HttpServletResponse response, Long projectId) {
List<CcdiProjectRiskPeopleOverviewExcel> rows = overviewService.exportRiskPeopleOverview(projectId);
ExcelUtil<CcdiProjectRiskPeopleOverviewExcel> util =
new ExcelUtil<>(CcdiProjectRiskPeopleOverviewExcel.class);
util.exportExcel(response, rows, "风险人员总览");
}
```
```java
default List<CcdiProjectRiskPeopleOverviewExcel> exportRiskPeopleOverview(Long projectId) {
return List.of();
}
```
```java
@Excel(name = "姓名")
private String name;
```
- [ ] **Step 4: 回跑控制器结构测试,确认导出外壳建立完成**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewControllerTest test
```
Expected:
- PASS
- 控制器测试能断言 `/risk-people/export`
- 权限仍为 `ccdi:project:query`
- [ ] **Step 5: 提交本任务**
```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiProjectRiskPeopleOverviewExcel.java \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java
git commit -m "补充风险总览人员导出接口契约"
```
### Task 2: 让分页查询与导出查询共用同一套风险人员口径
**Files:**
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java`
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapperSqlTest.java`
- [ ] **Step 1: 先写失败测试,锁定导出查询、排序和共用 SQL 片段**
```java
String xml = readMapperXml();
assertTrue(xml.contains("<sql id=\"riskPeopleOverviewSelectColumns\">"), xml);
assertTrue(xml.contains("<sql id=\"riskPeopleOverviewOrderBy\">"), xml);
assertTrue(xml.contains("selectRiskPeopleOverviewPage"), xml);
assertTrue(xml.contains("selectRiskPeopleOverviewList"), xml);
assertTrue(xml.contains("order by risk_level_sort asc, result.model_count desc, result.rule_count desc, result.staff_id_card asc"), xml);
```
- [ ] **Step 2: 运行 SQL 测试,确认当前 Mapper 还没有导出列表查询**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewMapperSqlTest test
```
Expected:
- FAIL提示缺少 `selectRiskPeopleOverviewList`
- FAIL提示未抽出共用 SQL 片段
- [ ] **Step 3: 写最小共用查询结构**
```java
List<CcdiProjectEmployeeRiskAggregateVO> selectRiskPeopleOverviewList(@Param("projectId") Long projectId);
```
```xml
<sql id="riskPeopleOverviewSelectColumns">
result.staff_id_card,
result.staff_name,
result.dept_id,
result.dept_name,
result.rule_count,
result.model_count,
result.hit_count,
null as top_rule_code,
null as top_rule_name,
result.risk_point,
result.risk_level_code,
case
when result.risk_level_code = 'HIGH' then '高风险'
when result.risk_level_code = 'MEDIUM' then '中风险'
else '低风险'
end as risk_level_name,
case
when result.risk_level_code = 'HIGH' then 1
when result.risk_level_code = 'MEDIUM' then 2
else 3
end as risk_level_sort
</sql>
```
```xml
<sql id="riskPeopleOverviewOrderBy">
order by risk_level_sort asc, result.model_count desc, result.rule_count desc, result.staff_id_card asc
</sql>
```
- [ ] **Step 4: 回跑 SQL 测试,确认分页与导出将使用同一套排序和列映射**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewMapperSqlTest test
```
Expected:
- PASS
- `selectRiskPeopleOverviewPage``selectRiskPeopleOverviewList` 共用列与排序片段
- [ ] **Step 5: 提交本任务**
```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java \
ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapperSqlTest.java
git commit -m "统一风险总览人员查询与导出口径"
```
### Task 3: 实现服务层导出映射并复用现有字段组装逻辑
**Files:**
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java`
- [ ] **Step 1: 先写失败测试,锁定“导出全部人员”和字段映射**
```java
when(projectMapper.selectById(40L)).thenReturn(project);
when(overviewMapper.selectRiskPeopleOverviewList(40L)).thenReturn(List.of(aggregate));
when(overviewMapper.selectRiskHitTagsByScope(40L, "330000000000000001", null)).thenReturn(List.of(
buildHitTag("LARGE_TRANSACTION", "大额交易模型", "RULE_A", "大额单笔收入", "HIGH"),
buildHitTag("PART_TIME", "兼职取酬模型", "RULE_B", "疑似兼职", "MEDIUM")
));
List<CcdiProjectRiskPeopleOverviewExcel> rows = service.exportRiskPeopleOverview(40L);
assertEquals(1, rows.size());
assertEquals("李四", rows.getFirst().getName());
assertEquals("高风险", rows.getFirst().getRiskLevel());
assertEquals("大额单笔收入、疑似兼职", rows.getFirst().getRiskPoint());
verify(overviewMapper).selectRiskPeopleOverviewList(40L);
```
- [ ] **Step 2: 运行 Service 测试,确认当前实现还没有导出映射**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest test
```
Expected:
- FAIL提示 `exportRiskPeopleOverview` 尚未实现
- [ ] **Step 3: 写最小服务实现**
```java
@Override
public List<CcdiProjectRiskPeopleOverviewExcel> exportRiskPeopleOverview(Long projectId) {
ensureProjectExists(projectId);
return defaultList(overviewMapper.selectRiskPeopleOverviewList(projectId)).stream()
.map(aggregate -> buildRiskPeopleItem(projectId, aggregate))
.map(this::buildRiskPeopleExcelRow)
.toList();
}
```
```java
private CcdiProjectRiskPeopleOverviewExcel buildRiskPeopleExcelRow(CcdiProjectRiskPeopleOverviewItemVO item) {
CcdiProjectRiskPeopleOverviewExcel row = new CcdiProjectRiskPeopleOverviewExcel();
row.setName(item.getName());
row.setIdNo(item.getIdNo());
row.setDepartment(item.getDepartment());
row.setRiskCount(item.getRiskCount());
row.setRiskLevel(item.getRiskLevel());
row.setModelCount(item.getModelCount());
row.setRiskPoint(item.getRiskPoint());
return row;
}
```
- [ ] **Step 4: 回跑 Service 测试,确认导出复用了页面口径**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest test
```
Expected:
- PASS
- 测试明确断言导出走 `selectRiskPeopleOverviewList`
- 导出字段与页面表格字段一致
- 项目不存在时继续抛 `ServiceException`
- [ ] **Step 5: 提交本任务**
```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java
git commit -m "实现风险总览人员列表后端导出"
```
### Task 4: 补齐导出控制器回归与文档留痕
**Files:**
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java`
- Create: `docs/reports/implementation/2026-03-30-project-detail-risk-people-export-backend-record.md`
- Create: `docs/tests/records/2026-03-30-project-detail-risk-people-export-backend-verification.md`
- Verify: `docs/design/2026-03-30-project-detail-risk-people-export-design.md`
- [ ] **Step 1: 补一条控制器导出回归测试**
```java
MockHttpServletResponse response = new MockHttpServletResponse();
when(overviewService.exportRiskPeopleOverview(40L)).thenReturn(List.of(new CcdiProjectRiskPeopleOverviewExcel()));
controller.exportRiskPeople(response, 40L);
verify(overviewService).exportRiskPeopleOverview(40L);
```
- [ ] **Step 2: 跑本次后端最小回归集**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewControllerTest,CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceImplTest test
```
Expected:
- PASS
- [ ] **Step 3: 记录测试结果**
`docs/tests/records/2026-03-30-project-detail-risk-people-export-backend-verification.md` 记录:
- 执行命令
- 通过情况
- 导出接口路径
- 字段口径与页面一致的核对结论
- [ ] **Step 4: 记录实施内容**
`docs/reports/implementation/2026-03-30-project-detail-risk-people-export-backend-record.md` 记录:
- 新增导出接口与 Excel 对象
- 复用 `buildRiskPeopleItem(...)` 的实现方式
- 分页查询与导出查询共用排序/字段 SQL 片段
- 未引入新的筛选、菜单或权限
- [ ] **Step 5: 提交本任务**
```bash
git add ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java \
docs/reports/implementation/2026-03-30-project-detail-risk-people-export-backend-record.md \
docs/tests/records/2026-03-30-project-detail-risk-people-export-backend-verification.md
git commit -m "补充风险总览人员导出后端记录"
```
### 最终验证
- [ ] 运行后端回归:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewControllerTest,CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceImplTest test
```
- [ ] 确认以下结果:
- `POST /ccdi/project/overview/risk-people/export` 已接通
- 导出范围为当前项目全部风险人员
- 导出列仅包含页面展示列,不包含操作列
- 导出字段顺序与设计文档一致
- [ ] 准备执行时,只暂存本次任务相关后端文件后再提交