diff --git a/docs/reports/implementation/2026-03-31-project-detail-risk-details-abnormal-account-backend-implementation.md b/docs/reports/implementation/2026-03-31-project-detail-risk-details-abnormal-account-backend-implementation.md new file mode 100644 index 00000000..825b10d9 --- /dev/null +++ b/docs/reports/implementation/2026-03-31-project-detail-risk-details-abnormal-account-backend-implementation.md @@ -0,0 +1,136 @@ +# 项目详情风险明细异常账户人员信息后端实施记录 + +## 1. 实施概述 + +- 实施日期:2026-03-31 +- 实施目标:为项目详情风险明细补齐“异常账户人员信息”的真实后端分页查询与统一导出能力 +- 实施范围:`ccdi-project` 模块结果总览控制器、服务层、Mapper SQL、统一工作簿导出器及对应测试 + +## 2. 新增接口与对象 + +### 2.1 新增接口 + +- `GET /ccdi/project/overview/abnormal-account-people` + - 入参:`projectId`、`pageNum`、`pageSize` + - 返回:`rows`、`total` + - 权限:`ccdi:project:query` + +### 2.2 新增 DTO / VO / Excel 对象 + +- `CcdiProjectAbnormalAccountQueryDTO` + - 承载异常账户分页查询入参 +- `CcdiProjectAbnormalAccountItemVO` + - 承载单条异常账户明细 +- `CcdiProjectAbnormalAccountPageVO` + - 承载分页查询结果 `rows/total` +- `CcdiProjectAbnormalAccountExcel` + - 承载统一导出第 3 个 sheet 的行数据 + +## 3. Mapper SQL 口径 + +异常账户分页与导出统一复用同一套基础查询口径: + +- 仅查询当前项目:`tr.project_id = projectId` +- 仅查询异常账户模型:`tr.model_code = 'ABNORMAL_ACCOUNT'` +- 仅查询对象型结果:`tr.bank_statement_id is null` +- 仅查询员工本人账户:`account.owner_type = 'EMPLOYEE'` 且 `account.owner_id = tr.object_key` +- 仅在 `reason_detail` 中命中具体账号时返回:`instr(tr.reason_detail, account.account_no) > 0` +- 排序统一为:`异常发生时间 desc -> 账号 asc -> 规则编码 asc` + +字段映射如下: + +- `accountNo`:`ccdi_account_info.account_no` +- `accountName`:优先 `ccdi_account_info.account_name`,为空回退 `ccdi_base_staff.name` +- `bankName`:`ccdi_account_info.bank` +- `abnormalType`:`ccdi_bank_statement_tag_result.rule_name` +- `abnormalTime` + - `SUDDEN_ACCOUNT_CLOSURE` 取 `invalid_date` + - `DORMANT_ACCOUNT_LARGE_ACTIVATION` 从 `reason_detail` 提取首次交易日期 +- `status` + - `1 -> 正常` + - `2 -> 已销户` + +## 4. 服务层与统一导出改动 + +### 4.1 服务层 + +- 在 `ICcdiProjectOverviewService` 中新增: + - `getAbnormalAccountPeople(queryDTO)` + - `exportAbnormalAccountPeople(projectId)` +- 在 `CcdiProjectOverviewServiceImpl` 中实现: + - 项目存在性校验 + - 分页默认值 `pageNum=1`、`pageSize=5` + - 分页结果直接映射为 `CcdiProjectAbnormalAccountPageVO` + - 导出结果映射为 `CcdiProjectAbnormalAccountExcel` + +### 4.2 统一导出 + +- `exportRiskDetails(...)` 现在会同时查询: + - 涉疑交易明细 + - 员工负面征信信息 + - 异常账户人员信息 +- `CcdiProjectRiskDetailWorkbookExporter.export(...)` 方法签名扩展为接收异常账户列表 +- 第 3 个 sheet `异常账户人员信息` 从“仅表头”改为“表头 + 真实数据行” +- 第 3 个 sheet 列顺序固定为: + - `账号` + - `开户人` + - `银行` + - `异常类型` + - `异常发生时间` + - `状态` + +## 5. 自动化验证 + +### 5.1 基线验证 + +执行命令: + +```bash +mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiProjectOverviewControllerContractTest,CcdiProjectOverviewControllerTest,CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceImplTest,CcdiProjectRiskDetailWorkbookExporterTest test +``` + +验证结果: + +- 40 个相关既有测试通过 + +### 5.2 任务内 TDD 验证 + +按计划分别执行并通过: + +```bash +mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiProjectOverviewControllerContractTest,CcdiProjectOverviewControllerTest test +mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiProjectOverviewMapperSqlTest test +mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiProjectOverviewServiceAbnormalAccountTest test +mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiProjectOverviewServiceImplTest,CcdiProjectRiskDetailWorkbookExporterTest test +``` + +### 5.3 最终回归 + +执行命令: + +```bash +mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiProjectOverviewControllerContractTest,CcdiProjectOverviewControllerTest,CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceAbnormalAccountTest,CcdiProjectOverviewServiceImplTest,CcdiProjectRiskDetailWorkbookExporterTest test +``` + +结果: + +- 47 个测试全部通过 +- `BUILD SUCCESS` + +## 6. 手工联调与进程处理 + +- 本次未执行手工联调 +- 未启动新的后端 `java -jar ruoyi-admin.jar` 进程 +- 因未启动额外前后端进程,无额外进程需要关闭 + +## 7. 结果结论 + +- 异常账户人员信息分页接口已具备真实查询能力 +- 页面查询与统一导出第 3 个 sheet 已复用同一套异常账户明细口径 +- 返回字段已覆盖: + - `accountNo` + - `accountName` + - `bankName` + - `abnormalType` + - `abnormalTime` + - `status`