Files
ccdi/docs/plans/backend/2026-03-19-results-overview-risk-api-backend-implementation.md

488 lines
15 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.
# Results Overview Risk API 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.
**Goal:** 为结果总览页面实现风险仪表盘、风险人员总览、中高风险人员 TOP10 三个后端接口,并在项目流水标签打标完成后回写项目高、中、低风险人数。
**Architecture:**`ccdi-project` 模块内新增结果总览专用 Controller、Service、Mapper 与 VO查询统一基于 `ccdi_project``ccdi_bank_statement_tag_result` 聚合,不新增兼容性补丁链路。员工风险等级按命中去重规则数分级,项目表高、中、低风险人数在标签任务成功结束后同步回写,保证项目列表与结果总览口径一致。
**Tech Stack:** Java 21, Spring Boot 3, MyBatis XML, MyBatis Plus, Maven, JUnit
---
### Task 1: 定义结果总览 VO 与服务接口骨架
**Files:**
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectOverviewDashboardVO.java`
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectOverviewStatVO.java`
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectRiskPeopleOverviewVO.java`
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectRiskPeopleOverviewItemVO.java`
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectTopRiskPeopleVO.java`
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectTopRiskPeopleItemVO.java`
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectEmployeeRiskAggregateVO.java`
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/CcdiProjectOverviewServiceStructureTest.java`
- [ ] **Step 1: Write the failing test**
新增结构测试,锁定服务接口与 VO 名称:
```java
@Test
void shouldExposeOverviewServiceMethods() throws Exception {
Class<?> clazz = Class.forName("com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService");
assertNotNull(clazz.getMethod("getDashboard", Long.class));
assertNotNull(clazz.getMethod("getRiskPeopleOverview", Long.class));
assertNotNull(clazz.getMethod("getTopRiskPeople", Long.class));
assertNotNull(clazz.getMethod("refreshProjectRiskCounts", Long.class, String.class));
}
```
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewServiceStructureTest
```
Expected:
- `FAIL`
- 原因是接口与 VO 尚未创建
- [ ] **Step 3: Write minimal implementation**
最小落地如下:
1. 为 3 个区块分别创建返回 VO。
2. 新建员工聚合中间 VO字段至少包括
- `staffIdCard`
- `staffName`
- `deptName`
- `ruleCount`
- `modelCount`
- `topRuleName`
- `riskLevelCode`
- `riskLevelName`
- `riskLevelSort`
3. 新建 `ICcdiProjectOverviewService`,声明以下方法:
```java
CcdiProjectOverviewDashboardVO getDashboard(Long projectId);
CcdiProjectRiskPeopleOverviewVO getRiskPeopleOverview(Long projectId);
CcdiProjectTopRiskPeopleVO getTopRiskPeople(Long projectId);
void refreshProjectRiskCounts(Long projectId, String operator);
```
- [ ] **Step 4: Run test to verify it passes**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewServiceStructureTest
```
Expected:
- `PASS`
- [ ] **Step 5: Commit**
```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectOverviewDashboardVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectOverviewStatVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectRiskPeopleOverviewVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectRiskPeopleOverviewItemVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectTopRiskPeopleVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectTopRiskPeopleItemVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectEmployeeRiskAggregateVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/CcdiProjectOverviewServiceStructureTest.java
git commit -m "定义结果总览风险接口基础结构"
```
### Task 2: 新增结果总览 Mapper 与员工归并聚合 SQL
**Files:**
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java`
- Create: `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: Write the failing test**
新增 SQL 结构测试,锁定 Mapper XML 中必须包含员工归并和风险等级分段逻辑:
```java
@Test
void shouldContainEmployeeRiskAggregationSql() throws Exception {
String xml = Files.readString(Path.of("ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
assertTrue(xml.contains("count(distinct base.rule_code)"));
assertTrue(xml.contains("count(distinct base.model_code)"));
assertTrue(xml.contains("when agg.rule_count >= 5 then 'HIGH'"));
assertTrue(xml.contains("when agg.rule_count between 2 and 4 then 'MEDIUM'"));
}
```
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewMapperSqlTest
```
Expected:
- `FAIL`
- 原因是 Mapper 文件还不存在
- [ ] **Step 3: Write minimal implementation**
创建 `CcdiProjectOverviewMapper` 与 XML至少提供以下查询
1. `selectDashboardBaseByProjectId(Long projectId)`
2. `selectRiskPeopleOverviewByProjectId(Long projectId)`
3. `selectTopRiskPeopleByProjectId(Long projectId)`
4. `selectRiskCountSummaryByProjectId(Long projectId)`
SQL 设计要求:
1. 先用公共子查询把标签结果归并到员工身份证:
- 本人命中:`object_type = 'STAFF_ID_CARD'`
- 流水本人命中:`bank_statement_id -> ccdi_bank_statement.cret_no`
- 亲属命中:`relation_cert_no -> person_id`
2. 外层按员工聚合:
```sql
count(distinct base.rule_code) as rule_count,
count(distinct base.model_code) as model_count
```
3. 风险等级按规则数分段:
```sql
case
when agg.rule_count >= 5 then 'HIGH'
when agg.rule_count between 2 and 4 then 'MEDIUM'
else 'LOW'
end
```
4. `riskPoint` 使用“规则命中次数倒序 + rule_code 升序”取第一条规则名。
- [ ] **Step 4: Run test to verify it passes**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewMapperSqlTest
```
Expected:
- `PASS`
- [ ] **Step 5: Commit**
```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: 实现结果总览 Service
**Files:**
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/CcdiProjectOverviewServiceImplTest.java`
- [ ] **Step 1: Write the failing test**
为服务层编写测试,锁定仪表盘与列表组装逻辑:
```java
@Test
void shouldBuildDashboardWithNoRiskCount() {
// mock project targetCount=100 high=5 medium=10 low=15
// assert noRiskCount == 70
}
@Test
void shouldMapRiskPeopleOverviewRows() {
// mock aggregate row
// assert riskCount/topRuleName/actionLabel mapping
}
@Test
void shouldMapTopRiskPeopleRows() {
// mock aggregate row with HIGH
// assert riskLevelType == "danger"
}
```
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest
```
Expected:
- `FAIL`
- [ ] **Step 3: Write minimal implementation**
`CcdiProjectOverviewServiceImpl` 中完成:
1. `getDashboard`
- 读项目基础数据
- 计算无风险人数,空值按 0 处理
2. `getRiskPeopleOverview`
- 调 Mapper 查询员工聚合结果
- 映射为 `overviewList`
- 固定补 `actionLabel = "查看详情"`
3. `getTopRiskPeople`
- 调 Mapper 查询 TOP10
-`HIGH/MEDIUM` 映射为 `高风险/中风险`
-`HIGH -> danger``MEDIUM -> warning`
4. 若项目不存在,抛出业务异常
如需项目存在性校验,直接复用现有项目查询能力,不新增兜底流程。
- [ ] **Step 4: Run test to verify it passes**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest
```
Expected:
- `PASS`
- [ ] **Step 5: Commit**
```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/CcdiProjectOverviewServiceImplTest.java
git commit -m "实现结果总览风险接口服务层"
```
### Task 4: 新增结果总览 Controller 接口
**Files:**
- Create: `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: Write the failing test**
为控制器编写测试,锁定 3 个 GET 接口:
```java
@Test
void shouldExposeDashboardEndpoint() {}
@Test
void shouldExposeRiskPeopleEndpoint() {}
@Test
void shouldExposeTopRiskPeopleEndpoint() {}
```
校验内容:
- 路径为 `/ccdi/project/overview/*`
- 权限为 `ccdi:project:query`
- 返回 `AjaxResult.success(...)`
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewControllerTest
```
Expected:
- `FAIL`
- [ ] **Step 3: Write minimal implementation**
控制器实现:
```java
@GetMapping("/dashboard")
public AjaxResult getDashboard(Long projectId) { ... }
@GetMapping("/risk-people")
public AjaxResult getRiskPeople(Long projectId) { ... }
@GetMapping("/top-risk-people")
public AjaxResult getTopRiskPeople(Long projectId) { ... }
```
要求:
- 添加 Swagger 注释
- 复用 `@PreAuthorize("@ss.hasPermi('ccdi:project:query')")`
- 仅接收 `projectId`
- [ ] **Step 4: Run test to verify it passes**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewControllerTest
```
Expected:
- `PASS`
- [ ] **Step 5: Commit**
```bash
git add 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 5: 在标签任务成功后回写项目风险人数
**Files:**
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectMapper.java`
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectMapper.xml`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/CcdiBankTagServiceRiskCountRefreshTest.java`
- [ ] **Step 1: Write the failing test**
新增测试,锁定打标成功后必须刷新项目风险人数:
```java
@Test
void shouldRefreshProjectRiskCountsAfterTagRebuildSuccess() {
// mock successful rule execution
// verify overviewService.refreshProjectRiskCounts(projectId, operator)
}
```
并新增一条失败场景:
```java
@Test
void shouldFailTaskWhenRiskCountRefreshFails() {
// mock refreshProjectRiskCounts throws exception
// assert task status becomes FAILED
}
```
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiBankTagServiceRiskCountRefreshTest
```
Expected:
- `FAIL`
- [ ] **Step 3: Write minimal implementation**
实施要求:
1.`CcdiBankTagServiceImpl.rebuildProject(...)` 中:
- 标签结果批量写入成功后
- 任务状态改成功前
- 调用 `projectOverviewService.refreshProjectRiskCounts(projectId, operator)`
2. 在项目 Mapper 中新增更新人数方法:
```java
int updateRiskCountsByProjectId(@Param("projectId") Long projectId,
@Param("highRiskCount") Integer highRiskCount,
@Param("mediumRiskCount") Integer mediumRiskCount,
@Param("lowRiskCount") Integer lowRiskCount,
@Param("updateBy") String updateBy);
```
3. `refreshProjectRiskCounts` 内部先查员工聚合统计,再更新项目表。
- [ ] **Step 4: Run test to verify it passes**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiBankTagServiceRiskCountRefreshTest
```
Expected:
- `PASS`
- [ ] **Step 5: Commit**
```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/CcdiBankTagServiceRiskCountRefreshTest.java
git commit -m "打标完成后回写项目风险人数"
```
### Task 6: 补充后端验证记录与实施记录
**Files:**
- Create: `docs/tests/records/2026-03-19-results-overview-risk-api-backend-verification.md`
- Create: `docs/reports/implementation/2026-03-19-results-overview-risk-api-backend-implementation.md`
- [ ] **Step 1: Write the failing record skeleton**
先创建验证记录模板:
```md
# 结果总览风险接口后端验证记录
## 验证范围
- 风险仪表盘接口
- 风险人员总览接口
- 中高风险人员 TOP10 接口
- 打标完成后项目风险人数回写
```
- [ ] **Step 2: Run backend verification commands**
Run:
```bash
mvn test -pl ccdi-project -Dtest=CcdiProjectOverviewServiceStructureTest,CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceImplTest,CcdiProjectOverviewControllerTest,CcdiBankTagServiceRiskCountRefreshTest
```
Expected:
- 相关测试全部 `PASS`
- [ ] **Step 3: Write minimal implementation record**
在实施记录中说明:
- 新增 3 个结果总览接口
- 员工风险等级按规则数分级
- 标签完成后回写项目风险人数
- 未扩展风险模型区和风险明细区接口
- [ ] **Step 4: Re-run record review**
Run:
```bash
sed -n '1,220p' docs/tests/records/2026-03-19-results-overview-risk-api-backend-verification.md
sed -n '1,220p' docs/reports/implementation/2026-03-19-results-overview-risk-api-backend-implementation.md
```
Expected:
- 两份文档都能覆盖本次后端改动和验证结果
- [ ] **Step 5: Commit**
```bash
git add docs/tests/records/2026-03-19-results-overview-risk-api-backend-verification.md docs/reports/implementation/2026-03-19-results-overview-risk-api-backend-implementation.md
git commit -m "补充结果总览风险接口后端记录"
```