Files
ccdi/docs/design/2026-03-19-results-overview-risk-api-design.md

13 KiB
Raw Blame History

结果总览风险接口设计文档

日期: 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
  • 中高风险人员 TOP10topRiskList

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)

返回结构示例:

{
  "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)

返回结构示例:

{
  "overviewList": [
    {
      "name": "李四",
      "idNo": "330000000000000001",
      "department": "信息二部",
      "riskCount": 5,
      "riskPoint": "大额单笔收入",
      "actionLabel": "查看详情"
    }
  ]
}

接口三:查询中高风险人员 TOP10

  • 方法:GET
  • 路径:/ccdi/project/overview/top-risk-people
  • 参数:projectId
  • 返回:AjaxResult.success(data)

返回结构示例:

{
  "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=1MEDIUM=2LOW=3

七、员工维度归并设计

7.1 归并目标

所有榜单都统一归并到员工维度,页面不直接展示亲属维度行。

7.2 归并规则

标签结果转员工归属时,按以下优先级处理:

  1. object_type = STAFF_ID_CARDobject_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. 项目列表、风险仪表盘、风险人员榜单三处口径保持一致