补充结果总览风险接口设计与实施计划

This commit is contained in:
wkc
2026-03-19 12:12:48 +08:00
parent 97bd3de299
commit f06ae4a9bf
4 changed files with 1316 additions and 0 deletions

View File

@@ -0,0 +1,483 @@
# 结果总览风险接口设计文档
**日期**: 2026-03-19
**模块**: 初核项目详情 - 结果总览
**作者**: Codex
**状态**: 已确认
## 一、概述
本文档用于沉淀结果总览页面中以下 3 个区块的后端设计方案:
- 风险仪表盘
- 风险人员总览
- 中高风险人员 TOP10
本次设计目标是为现有结果总览页面补齐真实后端接口,并把项目级高、中、低风险人数的统计口径收拢到流水标签打标链路中,保证项目列表、结果总览仪表盘、风险人员榜单三处口径一致。
## 二、设计范围
### 2.1 包含内容
- 新增结果总览 3 个独立后端查询接口
- 新增员工维度风险聚合查询
- 在项目流水标签打标完成后回写项目风险人数
- 输出对应后端实施计划与前端实施计划
### 2.2 不包含内容
- 不改造结果总览中的风险模型区、风险明细区
- 不新增设计需求之外的导出、弹窗、降级、补丁逻辑
- 不在查询接口中触发重新打标
- 不新增客户、中介等非员工维度榜单
## 三、当前上下文
### 3.1 前端现状
结果总览页面已经完成静态结构,相关文件如下:
- `ruoyi-ui/src/views/ccdiProject/components/detail/PreliminaryCheck.vue`
- `ruoyi-ui/src/views/ccdiProject/components/detail/OverviewStats.vue`
- `ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue`
- `ruoyi-ui/src/views/ccdiProject/components/detail/preliminaryCheck.mock.js`
其中当前页面仍由本地 mock 数据驱动,三块目标区块的字段结构已经稳定:
- 风险仪表盘:`title/subtitle/stats`
- 风险人员总览:`overviewList`
- 中高风险人员 TOP10`topRiskList`
### 3.2 后端现状
当前仓库中还没有结果总览专用后端接口,但已有以下可复用数据基础:
1. `ccdi_project`
- 已有 `target_count`
- 已有 `high_risk_count`
- 已有 `medium_risk_count`
- 已有 `low_risk_count`
2. `ccdi_bank_statement_tag_result`
- 已沉淀项目维度的标签命中结果
-`project_id/model_code/rule_code/risk_level/bank_statement_id/object_type/object_key`
3. `ccdi_base_staff`
- 员工主数据,含 `name/dept_id/id_card`
4. `ccdi_staff_fmy_relation`
- 员工亲属映射,含 `person_id/relation_cert_no/status`
5. `sys_dept`
- 用于补齐所属部门名称
### 3.3 已确认业务口径
用户已确认以下口径:
1. 接口粒度
- 采用 3 个独立接口,而非 1 个汇总接口
2. 风险仪表盘口径
- `总人数 = ccdi_project.target_count`
- `高风险/中风险/低风险 = ccdi_project.high_risk_count / medium_risk_count / low_risk_count`
- `无风险人员 = 总人数 - 高风险 - 中风险 - 低风险`
3. 榜单统计对象
- 统计“员工本人 + 员工亲属”
- 若命中亲属,则归并到所属员工名下
4. 风险人员总览中的 `疑似违规数`
- 统计员工命中的去重规则数
5. 员工风险等级口径
- 命中规则数 `>= 5`:高风险
- 命中规则数 `2-4`:中风险
- 命中规则数 `= 1`:低风险
6. TOP10 排序
- 按员工风险等级优先级排序
- 同等级按命中模型数倒序
- 再按命中规则数倒序
- 最后按员工身份证号升序
7. 项目表风险人数维护方式
- 在项目流水标签打标完成后回写高、中、低风险人数
## 四、方案对比与结论
### 4.1 方案一:新增结果总览专用控制器与 3 个独立接口
优点:
- 与前端 3 个区块一一对应
- 查询职责边界清晰
- 后续风险模型区、风险明细区继续扩展时可沿用同一入口
缺点:
- 需要新增一组 DTO/VO/Mapper 查询
### 4.2 方案二:把接口直接追加到 `CcdiProjectController`
优点:
- 表面改动点更少
缺点:
- 项目管理与结果总览查询混在同一控制器
- 后续结果总览继续扩展时边界会变乱
### 4.3 方案三:提供 1 个汇总接口
优点:
- 请求数更少
缺点:
- 与已确认的“3 个独立接口”目标不符
- 单个区块口径变化会牵动整个响应结构
### 4.4 最终结论
采用方案一:新增结果总览专用控制器,提供 3 个独立接口,并新增员工维度聚合查询与项目风险人数回写逻辑。
## 五、接口设计
### 5.1 控制器落点
建议新增:
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java`
接口统一挂载到:
- `/ccdi/project/overview`
### 5.2 接口清单
#### 接口一:查询风险仪表盘
- 方法:`GET`
- 路径:`/ccdi/project/overview/dashboard`
- 参数:`projectId`
- 返回:`AjaxResult.success(data)`
返回结构示例:
```json
{
"title": "风险仪表盘",
"subtitle": "风险仪表盘数据概览",
"stats": [
{ "key": "people", "label": "总人数", "value": 500 },
{ "key": "riskPeople", "label": "高风险", "value": 10 },
{ "key": "medium", "label": "中风险", "value": 20 },
{ "key": "low", "label": "低风险", "value": 38 },
{ "key": "count", "label": "无风险人员", "value": 432 }
]
}
```
#### 接口二:查询风险人员总览
- 方法:`GET`
- 路径:`/ccdi/project/overview/risk-people`
- 参数:`projectId`
- 返回:`AjaxResult.success(data)`
返回结构示例:
```json
{
"overviewList": [
{
"name": "李四",
"idNo": "330000000000000001",
"department": "信息二部",
"riskCount": 5,
"riskPoint": "大额单笔收入",
"actionLabel": "查看详情"
}
]
}
```
#### 接口三:查询中高风险人员 TOP10
- 方法:`GET`
- 路径:`/ccdi/project/overview/top-risk-people`
- 参数:`projectId`
- 返回:`AjaxResult.success(data)`
返回结构示例:
```json
{
"topRiskList": [
{
"name": "张三",
"idNo": "330000000000000002",
"department": "信贷部",
"riskLevel": "高风险",
"riskLevelType": "danger",
"modelCount": 8,
"actionLabel": "查看详情"
}
]
}
```
## 六、数据模型设计
### 6.1 新增 VO
建议新增以下 VO避免与现有 `CcdiProjectVO` 混用:
1. 仪表盘 VO
- `CcdiProjectOverviewDashboardVO`
- `CcdiProjectOverviewStatVO`
2. 风险人员总览 VO
- `CcdiProjectRiskPeopleOverviewVO`
- `CcdiProjectRiskPeopleOverviewItemVO`
3. 中高风险人员 TOP10 VO
- `CcdiProjectTopRiskPeopleVO`
- `CcdiProjectTopRiskPeopleItemVO`
4. 内部聚合中间 VO
- `CcdiProjectEmployeeRiskAggregateVO`
### 6.2 内部聚合字段
员工维度聚合中间结果至少包含:
- `staffIdCard`
- `staffName`
- `deptId`
- `deptName`
- `ruleCount`
- `modelCount`
- `topRuleCode`
- `topRuleName`
- `riskLevelCode`
- `riskLevelName`
- `riskLevelSort`
其中:
- `ruleCount = count(distinct rule_code)`
- `modelCount = count(distinct model_code)`
- `riskLevelCode` 根据 `ruleCount` 映射为 `HIGH/MEDIUM/LOW`
- `riskLevelName` 映射为 `高风险/中风险/低风险`
- `riskLevelSort` 仅供 SQL 排序使用,`HIGH=1``MEDIUM=2``LOW=3`
## 七、员工维度归并设计
### 7.1 归并目标
所有榜单都统一归并到员工维度,页面不直接展示亲属维度行。
### 7.2 归并规则
标签结果转员工归属时,按以下优先级处理:
1.`object_type = STAFF_ID_CARD``object_key` 命中员工身份证
- 直接归到该员工
2.`object_key` 为空,但该条结果关联到流水,且流水 `cret_no` 命中员工身份证
- 归到该员工
3.`object_key` 为空或为亲属证件号,且能通过 `ccdi_staff_fmy_relation.relation_cert_no` 关联到员工 `person_id`
- 归到该员工
4. 若无法归并到员工
- 本次查询中直接丢弃,不进入结果总览榜单
### 7.3 归并实现原则
- 不改造现有标签结果表结构
- 不为本次需求新增缓存表或汇总表
- 统一通过查询层完成员工归并
## 八、风险等级与项目人数回写设计
### 8.1 员工风险等级计算
按员工命中的去重规则数计算:
- `ruleCount >= 5` -> `HIGH` / `高风险`
- `ruleCount between 2 and 4` -> `MEDIUM` / `中风险`
- `ruleCount = 1` -> `LOW` / `低风险`
### 8.2 项目表风险人数统计
在员工维度聚合结果基础上统计:
- `highRiskCount = 员工风险等级为 HIGH 的人数`
- `mediumRiskCount = 员工风险等级为 MEDIUM 的人数`
- `lowRiskCount = 员工风险等级为 LOW 的人数`
### 8.3 回写时机
项目流水标签任务成功结束时执行回写,链路放在 `CcdiBankTagServiceImpl.rebuildProject(...)` 内:
1. 删除旧标签结果
2. 执行全部规则
3. 批量写入新标签结果
4. 重新按员工维度聚合高、中、低风险人数
5. 更新 `ccdi_project`
6. 更新任务状态为成功
这样可保证查询接口读到的项目风险人数与最新标签结果一致。
### 8.4 不采用的方案
不采用“查询时实时扫标签结果并顺手更新项目表”的做法,因为:
- 查询副作用过重
- 口径维护分散
- 项目列表与结果总览可能出现时间差
## 九、SQL 设计
### 9.1 Mapper 落点
建议新增:
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java`
- `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml`
### 9.2 查询职责拆分
Mapper 只负责以下查询:
1. 查询项目仪表盘基础数据
2. 查询员工维度风险总览列表
3. 查询员工维度中高风险 TOP10
4. 查询项目员工风险等级分布人数
### 9.3 员工聚合 SQL 结构
建议采用“公共子查询 + 外层聚合”的方式:
1. 先构造标签结果到员工身份证的归并明细
2. 再按员工身份证聚合规则数、模型数和代表性规则
3. 最后外层补部门名称、风险等级和排序字段
建议公共子查询输出字段:
- `project_id`
- `staff_id_card`
- `rule_code`
- `rule_name`
- `model_code`
### 9.4 代表性异常点选择
`风险人员总览.riskPoint` 采用以下稳定选择策略:
1. 先按员工 + 规则维度统计命中次数
2. 按命中次数倒序
3. 再按 `rule_code` 升序
4. 取第一条 `rule_name`
这样不依赖数据库非确定性行为,也不会因为同条规则多次命中而随机波动。
### 9.5 TOP10 筛选规则
TOP10 查询仅保留:
- `ruleCount >= 2`
即仅展示中风险和高风险员工。
## 十、服务层设计
### 10.1 Service 落点
建议新增:
- `ICcdiProjectOverviewService`
- `CcdiProjectOverviewServiceImpl`
### 10.2 服务职责
服务层负责:
1. 调用 Mapper 查询项目仪表盘基础数据
2. 组装前端需要的 `title/subtitle/stats`
3. 调用员工聚合查询并映射到列表 VO
4. 在打标完成后调用项目风险人数回写逻辑
5. 处理空数据场景
### 10.3 空数据处理
1. 仪表盘
- 若项目存在但风险人数为空,按 `0` 返回
2. 风险人员总览
- 返回 `overviewList: []`
3. TOP10
- 返回 `topRiskList: []`
## 十一、控制器与权限设计
控制器沿用现有项目模块风格:
- 使用 `@RestController`
- 使用 `@Tag`
- 使用 `@Operation`
- 使用 `@PreAuthorize("@ss.hasPermi('ccdi:project:query')")`
- 返回 `AjaxResult.success(...)`
本次不新增新的权限标识,直接复用项目查询权限。
## 十二、异常处理设计
### 12.1 查询接口
- 项目不存在:返回业务异常,提示“项目不存在”
- `projectId` 为空:走参数校验失败
### 12.2 回写链路
若项目风险人数回写失败:
- 视为本次打标任务失败
- 不允许出现“标签写入成功但项目人数未更新且任务仍成功”的状态
原因是本次需求要求三处口径一致,项目人数回写属于打标成功链路的一部分,而不是可选附加动作。
## 十三、测试设计
### 13.1 后端单元/集成测试重点
1. 风险仪表盘
- 校验 `无风险人员` 计算逻辑
2. 风险人员总览
- 校验员工本人命中可正常归并
- 校验亲属命中可正常归并到员工
- 校验 `riskCount = 去重规则数`
- 校验 `riskPoint` 选择稳定
3. TOP10
- 校验仅返回中高风险
- 校验排序规则正确
4. 打标回写
- 校验任务成功后项目表风险人数被更新
- 校验回写失败时任务整体失败
### 13.2 前端联调验证重点
1. mock 替换为 3 个真实接口
2. 字段命名与当前页面结构一致
3. 空列表时页面能正确展示空表格
## 十四、风险与约束
### 14.1 已知约束
- 当前标签结果表未直接存员工归属字段,需要通过查询归并
- 部分标签结果可能来自 `bank_statement_id` 关联的流水,而非直接对象命中
### 14.2 风险控制
- 统一通过员工聚合查询输出榜单,避免前后接口各自实现一套归并逻辑
- 项目风险人数只在打标成功后回写,避免查询阶段产生副作用
- 不引入额外缓存与汇总表,保持最短实现路径
## 十五、最终结论
本方案采用“结果总览专用接口 + 标签完成后回写项目风险人数”的最短路径实现:
1. 结果总览新增 3 个独立接口
2. 风险榜单统一按员工维度聚合
3. 员工风险等级按命中规则条数区间计算
4. 项目表高、中、低风险人数在打标完成后统一回写
5. 项目列表、风险仪表盘、风险人员榜单三处口径保持一致

View File

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

View File

@@ -0,0 +1,311 @@
# Results Overview Risk API Frontend 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 从本地 mock 数据切换为真实后端接口数据。
**Architecture:** 保持 `PreliminaryCheck.vue` 作为结果总览页面入口,不改造当前 4 区块布局,只在前端增加结果总览 API 封装、页面数据拉取与 3 个区块的真实数据映射。风险模型区和风险明细区继续使用现有 mock 或原状,不额外扩展接口范围。
**Tech Stack:** Vue 2, Element UI, Axios (`@/utils/request`), Node.js, npm
---
### Task 1: 新增结果总览 API 封装
**Files:**
- Create: `ruoyi-ui/src/api/ccdi/projectOverview.js`
- Test: `ruoyi-ui/tests/unit/project-overview-api.test.js`
- [ ] **Step 1: Write the failing test**
新增静态断言脚本,锁定 3 个接口方法:
```js
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const source = fs.readFileSync(
path.resolve(__dirname, "../../src/api/ccdi/projectOverview.js"),
"utf8"
);
[
"getOverviewDashboard",
"getOverviewRiskPeople",
"getOverviewTopRiskPeople",
"/ccdi/project/overview/dashboard",
"/ccdi/project/overview/risk-people",
"/ccdi/project/overview/top-risk-people",
].forEach((token) => assert(source.includes(token), token));
```
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
cd ruoyi-ui
node tests/unit/project-overview-api.test.js
```
Expected:
- `FAIL`
- [ ] **Step 3: Write minimal implementation**
创建 API 文件:
```js
import request from "@/utils/request";
export function getOverviewDashboard(projectId) {
return request({ url: "/ccdi/project/overview/dashboard", method: "get", params: { projectId } });
}
```
同理补齐另外两个接口。
- [ ] **Step 4: Run test to verify it passes**
Run:
```bash
cd ruoyi-ui
node tests/unit/project-overview-api.test.js
```
Expected:
- `PASS`
- [ ] **Step 5: Commit**
```bash
git add ruoyi-ui/src/api/ccdi/projectOverview.js ruoyi-ui/tests/unit/project-overview-api.test.js
git commit -m "新增结果总览风险接口前端封装"
```
### Task 2: 在 PreliminaryCheck 页面接入 3 个真实接口
**Files:**
- Modify: `ruoyi-ui/src/views/ccdiProject/components/detail/PreliminaryCheck.vue`
- Modify: `ruoyi-ui/src/views/ccdiProject/components/detail/preliminaryCheck.mock.js`
- Test: `ruoyi-ui/tests/unit/preliminary-check-api-integration.test.js`
- [ ] **Step 1: Write the failing test**
新增静态断言脚本,锁定页面必须引入真实 API
```js
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const source = fs.readFileSync(
path.resolve(__dirname, "../../src/views/ccdiProject/components/detail/PreliminaryCheck.vue"),
"utf8"
);
[
"getOverviewDashboard",
"getOverviewRiskPeople",
"getOverviewTopRiskPeople",
"loadOverviewData",
"Promise.all",
].forEach((token) => assert(source.includes(token), token));
```
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
cd ruoyi-ui
node tests/unit/preliminary-check-api-integration.test.js
```
Expected:
- `FAIL`
- [ ] **Step 3: Write minimal implementation**
`PreliminaryCheck.vue` 中:
1. 引入 3 个 API 方法
2. 新增 `loadOverviewData`
3. 在页面进入或 `projectId` 变化时并发拉取 3 个接口
4. 将返回结果合并到页面数据:
```js
this.realData = {
...this.mockData,
summary: dashboardData,
riskPeople: {
overviewList: riskPeopleData.overviewList || [],
topRiskList: topRiskPeopleData.topRiskList || [],
},
};
```
5. 保留现有 `loading / empty / loaded` 状态
本任务只替换这 3 块数据,不动风险模型区和风险明细区。
- [ ] **Step 4: Run test to verify it passes**
Run:
```bash
cd ruoyi-ui
node tests/unit/preliminary-check-api-integration.test.js
```
Expected:
- `PASS`
- [ ] **Step 5: Commit**
```bash
git add ruoyi-ui/src/views/ccdiProject/components/detail/PreliminaryCheck.vue ruoyi-ui/src/views/ccdiProject/components/detail/preliminaryCheck.mock.js ruoyi-ui/tests/unit/preliminary-check-api-integration.test.js
git commit -m "接入结果总览风险真实接口"
```
### Task 3: 校准风险人员区字段映射与空态
**Files:**
- Modify: `ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue`
- Test: `ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js`
- [ ] **Step 1: Write the failing test**
新增静态断言脚本,锁定字段映射仍与当前页面结构兼容:
```js
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const source = fs.readFileSync(
path.resolve(__dirname, "../../src/views/ccdiProject/components/detail/RiskPeopleSection.vue"),
"utf8"
);
[
"sectionData.overviewList",
"sectionData.topRiskList",
"riskCount",
"riskPoint",
"modelCount",
"riskLevelType",
].forEach((token) => assert(source.includes(token), token));
```
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
cd ruoyi-ui
node tests/unit/preliminary-check-risk-people-binding.test.js
```
Expected:
- 若字段被改乱或误删则 `FAIL`
- [ ] **Step 3: Write minimal implementation**
实施要求:
1. 保持现有表格列不重排
2. 若接口返回空数组,表格自然展示 Element 空态
3. `riskLevelType` 不在组件内重新判断,直接使用后端返回值
4. `actionLabel` 缺失时仍回退为 `查看详情`
- [ ] **Step 4: Run test to verify it passes**
Run:
```bash
cd ruoyi-ui
node tests/unit/preliminary-check-risk-people-binding.test.js
```
Expected:
- `PASS`
- [ ] **Step 5: Commit**
```bash
git add ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js
git commit -m "校准结果总览风险人员区字段映射"
```
### Task 4: 补充前端验证记录与实施记录
**Files:**
- Create: `docs/tests/records/2026-03-19-results-overview-risk-api-frontend-verification.md`
- Create: `docs/reports/implementation/2026-03-19-results-overview-risk-api-frontend-implementation.md`
- [ ] **Step 1: Write the failing record skeleton**
先创建验证记录模板:
```md
# 结果总览风险接口前端验证记录
## 验证范围
- API 封装
- 结果总览页面并发取数
- 风险人员区字段映射
```
- [ ] **Step 2: Run frontend verification commands**
Run:
```bash
cd ruoyi-ui
node tests/unit/project-overview-api.test.js
node tests/unit/preliminary-check-api-integration.test.js
node tests/unit/preliminary-check-risk-people-binding.test.js
npm run build:prod
```
Expected:
- 3 个脚本 `PASS`
- 构建成功
- [ ] **Step 3: Write minimal implementation record**
实施记录至少包含:
- 新增结果总览 API 封装
- 页面接入 3 个真实接口
- 风险模型区和风险明细区本轮保持不变
- [ ] **Step 4: Re-run record review**
Run:
```bash
sed -n '1,220p' docs/tests/records/2026-03-19-results-overview-risk-api-frontend-verification.md
sed -n '1,220p' docs/reports/implementation/2026-03-19-results-overview-risk-api-frontend-implementation.md
```
Expected:
- 两份文档都能覆盖本次前端接入范围和验证方法
- [ ] **Step 5: Commit**
```bash
git add docs/tests/records/2026-03-19-results-overview-risk-api-frontend-verification.md docs/reports/implementation/2026-03-19-results-overview-risk-api-frontend-implementation.md
git commit -m "补充结果总览风险接口前端记录"
```

View File

@@ -0,0 +1,35 @@
# 结果总览风险接口计划记录
## 变更概述
- 新增结果总览风险接口设计文档 1 份。
- 新增结果总览风险接口后端实施计划 1 份。
- 新增结果总览风险接口前端实施计划 1 份。
- 本次仅完成设计与计划沉淀,尚未进入代码实现阶段。
## 新增文件
- `docs/design/2026-03-19-results-overview-risk-api-design.md`
- `docs/plans/backend/2026-03-19-results-overview-risk-api-backend-implementation.md`
- `docs/plans/frontend/2026-03-19-results-overview-risk-api-frontend-implementation.md`
## 设计结论
- 结果总览采用 3 个独立接口:风险仪表盘、风险人员总览、中高风险人员 TOP10。
- 风险人员榜单统一按员工维度聚合,员工亲属命中归并到所属员工。
- 员工风险等级按命中去重规则数分级:
- 5 条及以上:高风险
- 2 到 4 条:中风险
- 1 条:低风险
- 项目表高、中、低风险人数在项目流水标签打标成功后统一回写。
## 计划结论
- 后端计划聚焦结果总览 Controller、Service、Mapper、聚合 SQL 与打标后人数回写。
- 前端计划聚焦 3 个结果总览风险接口接入,不扩展风险模型区和风险明细区。
- 后续实施阶段需分别补充前后端验证记录与实施记录。
## 说明
- 本次按仓库规范将设计文档落在 `docs/design/`,将实施计划分别落在 `docs/plans/backend/``docs/plans/frontend/`
- 由于本轮用户明确要求直接完成设计与计划,不再分段等待确认,故本记录视为本次设计收口材料。