# 异常账户模型接入银行流水打标后端实施记录

**日期**: 2026-03-31  
**类型**: 后端实施记录  
**范围**: 银行流水打标 - 异常账户模型

## 1. 已完成实施内容

### 1.1 规则与元数据

- 新增异常账户模型迁移脚本：`sql/migration/2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql`
- 新增模型编码：`ABNORMAL_ACCOUNT`
- 新增规则编码：
  - `SUDDEN_ACCOUNT_CLOSURE`
  - `DORMANT_ACCOUNT_LARGE_ACTIVATION`
- 两条规则均按 `OBJECT` 结果写入现有结果表 `ccdi_bank_statement_tag_result`

### 1.2 服务与 SQL

- `CcdiBankTagServiceImpl` 已补充两条对象型规则分发
- `CcdiBankTagAnalysisMapper` 已补充两条 Mapper 方法签名
- `CcdiBankTagAnalysisMapper.xml` 已补充：
  - `selectSuddenAccountClosureObjects`
  - `selectDormantAccountLargeActivationObjects`

### 1.3 自动化测试

- 已新增 SQL 元数据测试：
  - `CcdiAbnormalAccountRuleSqlMetadataTest`
- 已补充服务分发与对象结果断言：
  - `CcdiBankTagServiceImplTest`
- 已补充员工聚合承接断言：
  - `CcdiProjectOverviewEmployeeResultBuilderTest`

## 2. 测试数据准备

### 2.1 样本设计

- 员工 A：命中 `SUDDEN_ACCOUNT_CLOSURE`
- 员工 B：命中 `DORMANT_ACCOUNT_LARGE_ACTIVATION`
- 员工 C：休眠不足 6 个月，不命中
- 员工 D：销户前 30 天无流水，不命中

### 2.2 导入脚本

```bash
bin/mysql_utf8_exec.sh sql/migration/2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql
bin/mysql_utf8_exec.sh sql/migration/2026-03-31-add-abnormal-account-rule-test-data.sql
```

### 2.3 导入结果

- 已使用 `bin/mysql_utf8_exec.sh` 成功执行两份 SQL 脚本
- 远端业务库已写入：
  - 项目：`90331 / 异常账户规则测试项目`
  - 员工：A、B、C、D 四个最小样本
  - 账户：4 个员工本人账户
  - 项目流水：7 笔

## 3. 过程说明

- 本轮实现保持最短路径，未新增平行结果表或独立查询链路
- 异常账户结果仍复用既有项目打标主链路与员工风险聚合
- 为保证 `ccdi-project` 模块测试可执行，补充了缺失的 `easyexcel` 依赖声明
- `mvn` 定向测试统一使用 `-am`，确保 `ccdi-lsfx` 依赖以当前源码参与 reactor 构建，避免使用陈旧本地产物

## 4. SQL 校验结果

### 4.1 环境说明

- 项目导入脚本读取的数据库配置为：`jdbc:mysql://116.62.17.81:3307/ccdi`
- 当前 MySQL MCP 会话实际连接为：`ccdi@ca446c6169d2:3306`
- 由于 MySQL MCP 与项目配置数据库不是同一实例，直接在 MCP 中查询不到刚导入的样本数据
- 因此本次“真实 SQL 命中校验”实际使用项目配置对应库的只读 `mysql` 查询执行 Mapper 等价 SQL；MySQL MCP 仅用于确认环境差异，而未直接承载最终命中校验

### 4.2 `SUDDEN_ACCOUNT_CLOSURE`

- 执行 SQL 摘要：
  - 关联 `ccdi_account_info`、`ccdi_base_staff` 与项目内 `ccdi_bank_statement`
  - 过滤 `owner_type = 'EMPLOYEE'`、`status = 2`、`invalid_date is not null`
  - 统计窗口为 `[invalid_date - 30天, invalid_date)`
- 命中结果：
  - 员工 A `330101199001010001`
- `reasonDetail` 快照：
  - `账户6222000000000001于2026-03-20销户，销户前30天内最后交易日2026-03-18，累计交易金额180000.00元，单笔最大金额70000.00元`
- 反样本校验：
  - 员工 D `330101199001010004` 命中数为 `0`

### 4.3 `DORMANT_ACCOUNT_LARGE_ACTIVATION`

- 执行 SQL 摘要：
  - 关联 `ccdi_account_info`、`ccdi_base_staff` 与项目内 `ccdi_bank_statement`
  - 过滤 `owner_type = 'EMPLOYEE'`、`status = 1`、`effective_date is not null`
  - 要求 `first_tx_date >= effective_date + 6个月`
  - 要求 `windowTotalAmount >= 500000` 或 `windowMaxSingleAmount >= 100000`
- 命中结果：
  - 员工 B `330101199001010002`
- `reasonDetail` 快照：
  - `账户6222000000000002开户于2025-01-01，首次交易日期2025-08-01，沉睡时长7个月，启用后累计交易金额550000.00元，单笔最大金额300000.00元`
- 反样本校验：
  - 员工 C `330101199001010003` 命中数为 `0`

### 4.4 口径结论

- 两条规则均只命中预期正样本：
  - `SUDDEN_ACCOUNT_CLOSURE` 仅命中员工 A
  - `DORMANT_ACCOUNT_LARGE_ACTIVATION` 仅命中员工 B
- 反样本满足预期：
  - 员工 C 因沉睡期不足 6 个月未命中
  - 员工 D 因销户前 30 天无流水未命中

## 5. 最终验证汇总

### 5.1 Java 定向测试

- 执行命令：

```bash
mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false \
  -Dtest=CcdiAbnormalAccountRuleSqlMetadataTest,CcdiBankTagRuleSqlMetadataTest,CcdiBankTagServiceImplTest,CcdiProjectOverviewEmployeeResultBuilderTest \
  test
```

- 执行结果：
  - `Tests run: 25, Failures: 0, Errors: 0, Skipped: 0`
  - `BUILD SUCCESS`

### 5.2 端到端链路验证

- 已重新执行后端打包：

```bash
mvn -pl ruoyi-admin -am package -DskipTests
```

- 已启动 `ruoyi-admin/target/ruoyi-admin.jar`，并使用测试登录接口获取 token
- 已调用手工重建接口：

```http
POST /ccdi/project/tags/rebuild
{
  "projectId": 90331,
  "modelCode": "ABNORMAL_ACCOUNT"
}
```

- 后端执行日志确认：
  - 异常账户模型规则数为 `2`
  - 实际命中数为 `2`
  - 员工风险聚合已刷新
- 结果表校验确认：
  - `SUDDEN_ACCOUNT_CLOSURE` 写入员工 `330101199001010001`
  - `DORMANT_ACCOUNT_LARGE_ACTIVATION` 写入员工 `330101199001010002`
- 员工总览聚合表校验确认：
  - 员工 A 聚合命中 `ABNORMAL_ACCOUNT / SUDDEN_ACCOUNT_CLOSURE`
  - 员工 B 聚合命中 `ABNORMAL_ACCOUNT / DORMANT_ACCOUNT_LARGE_ACTIVATION`

### 5.3 进程关闭

- 端到端验证完成后，已主动关闭本轮启动的后端 `java -jar ruoyi-admin.jar` 进程
- 关闭日志可见 Quartz 调度器与 Druid 数据源正常释放，无残留后端进程

## 6. 最终改动文件清单

- `ccdi-project/pom.xml`
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java`
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java`
- `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml`
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiAbnormalAccountRuleSqlMetadataTest.java`
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiBankTagRuleSqlMetadataTest.java`
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java`
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewEmployeeResultBuilderTest.java`
- `sql/migration/2026-03-31-create-ccdi-account-info-and-abnormal-account-rules.sql`
- `sql/migration/2026-03-31-add-abnormal-account-rule-test-data.sql`
- `docs/reports/implementation/2026-03-31-abnormal-account-bank-tag-backend-implementation.md`
