# 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 "补充结果总览风险接口后端记录" ```