Files
ccdi/docs/design/2026-03-23-credit-info-maintenance-design.md

463 lines
13 KiB
Markdown
Raw Normal View History

2026-03-23 21:24:09 +08:00
# 征信维护设计文档
## 1. 背景
当前仓库已经具备两项征信解析基础能力:
- `ccdi-lsfx` 已提供 `POST /lsfx/credit/parse` 征信解析联调接口,可接收 HTML 并返回 `lx_header``lx_debt``lx_publictype`
- `lsfx-mock-server` 已支持从征信 HTML 中解析员工姓名和身份证号,能够稳定返回本地联调 payload
本次需求是在“信息维护”菜单下新增“征信维护”菜单,支持批量导入员工征信 HTML 文件并提交上传。系统需要调用现有征信解析接口完成数据解析,并把返回结果批量落库。页面同时要展示当前已维护的员工征信信息,并支持删除现有征信数据。
## 2. 已确认约束
- 菜单挂在“信息维护”下,页面为独立菜单,不挂到员工维护弹窗内
- 页面主列表按员工维度展示摘要,一行一个员工
- 批量上传按员工维度原子化处理,一个员工失败不影响其他员工
- 负债明细表名固定为 `ccdi_debts_info`
- 负债信息按照 `assets/征信解析/ccdi_debts_info.xlsx` 设计
- 负面信息按照接口字段单独设计一张表
- 每个员工只保留最新的征信信息
- 列表操作列支持删除现有征信信息
- 不新增征信主表,主列表通过明细表实时聚合
## 3. 目标
- 在“信息维护”下新增“征信维护”菜单与页面
- 支持一次上传多个 `.html/.htm` 征信文件
- 上传后逐文件调用征信解析接口
- 按员工身份证号归户,并按“最新征信优先”规则覆盖写入
- 在页面展示员工维度的征信摘要列表
- 在详情中展示负债明细和负面信息
- 支持按员工删除当前已维护的征信数据
## 4. 非目标
- 不改造现有 `POST /lsfx/credit/parse` 的协议语义
- 不新增异步任务、轮询状态页或上传历史模块
- 不保存多版本征信记录
- 不保存原始 HTML 文件或原始 payload JSON
- 不增加说明书之外的补丁性兜底方案
## 5. 方案对比
### 5.1 方案 A三张业务表增加征信主表
新增征信主表保存员工最新摘要,再配合负债表和负面信息表。
优点:
- 列表查询简单
- 聚合逻辑更少
缺点:
- 引入额外主表,超出本次最短路径
- 同一份摘要数据会与明细形成重复维护
### 5.2 方案 B仅保留两张业务表列表实时聚合
只建设 `ccdi_debts_info` 与负面信息表,主列表按员工维度实时聚合展示。
优点:
- 满足“负债表固定命名”和“负面信息单独建表”
- 不引入额外主表,路径最短
- 删除和覆盖逻辑仍然清晰,按员工清空两张表即可
缺点:
- 列表查询需要做员工维度聚合
### 5.3 方案 C两张业务表加原始 payload 存档
在方案 B 基础上额外保存原始 payload 或原始 HTML。
优点:
- 排查解析问题时信息更全
缺点:
- 增加冗余与维护成本
- 超出当前需求
## 6. 最终方案
采用方案 B仅新增 `ccdi_debts_info``ccdi_credit_negative_info` 两张业务表,列表按员工维度实时聚合,不新增征信主表。
选择原因:
- 满足“每个员工只保留最新征信信息”的要求
- 满足“负债信息按 Excel 设计、负面信息单独建表”的要求
- 不额外增加主表,保持最短路径实现
- 删除逻辑直接按员工清理两张业务表,语义明确
## 7. 总体架构
业务链路如下:
`征信维护页面 -> 征信维护业务接口 -> 征信维护服务 -> /lsfx/credit/parse -> 征信解析服务`
职责拆分如下:
- 征信维护页面
- 选择多个 HTML 文件并提交上传
- 展示员工维度征信摘要列表
- 打开详情弹窗查看负债明细与负面信息
- 删除员工当前征信数据
- 征信维护业务接口
- 对外提供上传、列表、详情、删除接口
- 不直接把前端耦合到 `ccdi-lsfx`
- 征信维护服务
- 逐文件调用征信解析接口
- 根据员工身份证号归户
- 校验“是否为最新征信”
- 单员工事务覆盖写入两张业务表
- `ccdi-lsfx`
- 保持现有征信解析客户端能力
- 继续作为外部征信解析接入边界
## 8. 数据模型设计
### 8.1 负债明细表 `ccdi_debts_info`
一条记录表示一条负债明细,字段按 `assets/征信解析/ccdi_debts_info.xlsx` 设计:
- `debt_id``BIGINT` 主键,自增
- `person_id``VARCHAR(18)`,员工身份证号
- `person_name``VARCHAR(100)`,员工姓名
- `query_date``DATE`,征信查询日期
- `debt_main_type``VARCHAR(50)`,负债大类
- `debt_sub_type``VARCHAR(50)`,负债小类
- `creditor_type``VARCHAR(50)`,债权人类型
- `debt_name``VARCHAR(100)`,负债名称
- `principal_balance``DECIMAL(18,2)`,负债本金余额
- `debt_total_amount``DECIMAL(18,2)`,负债总额
- `debt_status``VARCHAR(20)`,负债状态
- `create_by``VARCHAR(64)`
- `create_time``DATETIME`
- `update_by``VARCHAR(64)`
- `update_time``DATETIME`
建议索引:
- `idx_person_id(person_id)`
- `idx_query_date(query_date)`
- `idx_person_query_date(person_id, query_date)`
### 8.2 负面信息表 `ccdi_credit_negative_info`
一条记录表示一个员工当前最新征信对应的负面信息汇总:
- `negative_id``BIGINT` 主键,自增
- `person_id``VARCHAR(18)`,员工身份证号
- `person_name``VARCHAR(100)`,员工姓名
- `query_date``DATE`,征信查询日期
- `civil_cnt``INT`,民事案件笔数
- `enforce_cnt``INT`,强制执行笔数
- `adm_cnt``INT`,行政处罚笔数
- `civil_lmt``DECIMAL(18,2)`,民事案件金额
- `enforce_lmt``DECIMAL(18,2)`,强制执行金额
- `adm_lmt``DECIMAL(18,2)`,行政处罚金额
- `create_by``VARCHAR(64)`
- `create_time``DATETIME`
- `update_by``VARCHAR(64)`
- `update_time``DATETIME`
建议索引:
- `uk_person_id(person_id)` 唯一索引
## 9. 解析字段映射设计
### 9.1 归户字段
从解析结果 `lx_header` 中读取:
- `query_cert_no` -> 员工身份证号 `person_id`
- `query_cust_name` -> 员工姓名 `person_name`
- `report_time` -> 征信查询日期 `query_date`
`query_cert_no` 作为员工唯一归户标识,并校验该身份证号必须存在于 `ccdi_base_staff`
### 9.2 `lx_debt` 到 `ccdi_debts_info` 的映射
`lx_debt` 当前为 7 组固定字段,每组映射成一条负债明细:
| 接口前缀 | 负债大类 | 负债小类 | 债权人类型 | 负债名称 |
| --- | --- | --- | --- | --- |
| `uncle_bank_house` | 银行 | 住房贷款 | 银行 | 未结清银行住房贷款 |
| `uncle_bank_car` | 银行 | 汽车贷款 | 银行 | 未结清银行汽车贷款 |
| `uncle_bank_manage` | 银行 | 经营贷款 | 银行 | 未结清银行经营贷款 |
| `uncle_bank_consume` | 银行 | 消费贷款 | 银行 | 未结清银行消费贷款 |
| `uncle_bank_other` | 银行 | 其他贷款 | 银行 | 未结清银行其他贷款 |
| `uncle_not_bank` | 非银 | 非银行贷款 | 非银 | 未结清非银行贷款 |
| `uncle_credit_cart` | 银行 | 信用卡 | 银行 | 未结清信用卡 |
字段映射规则:
- `*_bal` -> `principal_balance`
- `*_lmt` -> `debt_total_amount`
- `*_state` -> `debt_status`
落库过滤规则:
- 当某组 `principal_balance``debt_total_amount` 都为空或 `0`,且 `debt_status` 为空时,不生成明细记录
- 其余情况生成一条明细
### 9.3 `lx_publictype` 到 `ccdi_credit_negative_info` 的映射
按接口字段直接映射:
- `civil_cnt` -> `civil_cnt`
- `enforce_cnt` -> `enforce_cnt`
- `adm_cnt` -> `adm_cnt`
- `civil_lmt` -> `civil_lmt`
- `enforce_lmt` -> `enforce_lmt`
- `adm_lmt` -> `adm_lmt`
## 10. 最新征信判定与覆盖策略
系统只保留员工最新征信,规则如下:
-`person_id` 作为员工唯一标识
-`query_date` 作为征信最新时间判定字段
- 员工上传新文件时:
- 若库中无现有征信数据,直接写入
- 若新 `query_date` 大于等于现有 `query_date`,覆盖旧数据
- 若新 `query_date` 小于现有 `query_date`,该员工本次文件判定为失败,不覆盖
单员工覆盖写入步骤:
1. 删除 `ccdi_debts_info` 中该员工旧记录
2. 删除 `ccdi_credit_negative_info` 中该员工旧记录
3. 插入本次解析后的最新负债明细
4. 插入本次解析后的最新负面信息
以上 4 步置于同一事务中,保证员工维度原子化。
## 11. 页面设计
### 11.1 菜单
在“信息维护”下新增菜单:
- 菜单名称:`征信维护`
- 路由:`creditInfo`
- 组件:`ccdiCreditInfo/index`
- 权限前缀:`ccdi:creditInfo:*`
### 11.2 顶部操作区
- 批量上传征信 HTML 按钮
- 查询条件:
- 员工姓名
- 柜员号
- 身份证号
- 是否已维护征信
上传弹窗要求:
- 仅允许选择 `.html/.htm`
- 支持一次选择多个文件
- 提交后调用业务上传接口
- 接口返回后直接展示成功/失败汇总及失败清单
### 11.3 主列表
主列表按员工一行展示,字段建议:
- 员工姓名
- 柜员号
- 身份证号
- 所属部门
- 最近征信查询日期
- 负债笔数
- 负债总额
- 民事案件笔数
- 强制执行笔数
- 行政处罚笔数
- 操作
聚合规则:
- 基础信息来自 `ccdi_base_staff`
- 最近征信查询日期、负债笔数、负债总额来自 `ccdi_debts_info`
- 三类负面计数来自 `ccdi_credit_negative_info`
- 任一业务表存在数据,即视为“已维护征信”
### 11.4 详情弹窗
详情弹窗分为两部分:
- 摘要区域
- 员工姓名
- 身份证号
- 征信查询日期
- 负债总额
- 负债笔数
- 民事案件笔数
- 强制执行笔数
- 行政处罚笔数
- 明细区域
- 负债信息表格
- 负面信息汇总卡片
负债表格字段:
- 负债大类
- 负债小类
- 债权人类型
- 负债名称
- 负债本金余额
- 负债总额
- 负债状态
负面信息展示字段:
- 民事案件笔数 / 金额
- 强制执行笔数 / 金额
- 行政处罚笔数 / 金额
### 11.5 删除交互
列表点击删除时,提示:
`确认删除该员工当前已维护的征信信息吗?`
确认后删除该员工在两张业务表中的全部数据。
## 12. 接口设计
新增业务接口,前端不直接调用 `/lsfx/credit/parse`
### 12.1 批量上传
- 方法:`POST`
- 路径:`/ccdi/creditInfo/upload`
- 入参:多个 HTML 文件
- 返回:
- 总文件数
- 成功数
- 失败数
- 失败明细列表
失败明细建议字段:
- `fileName`
- `personId`
- `personName`
- `reason`
### 12.2 列表分页查询
- 方法:`GET`
- 路径:`/ccdi/creditInfo/list`
- 功能:分页查询员工征信摘要列表
### 12.3 详情查询
- 方法:`GET`
- 路径:`/ccdi/creditInfo/{personId}`
- 功能:查询员工征信摘要、负债明细和负面信息
### 12.4 删除
- 方法:`DELETE`
- 路径:`/ccdi/creditInfo/{personId}`
- 功能:删除员工当前征信数据
## 13. 后端设计
建议改动集中在 `ccdi-info-collection` 模块,新增独立征信维护资源。
建议新增:
- `controller/CcdiCreditInfoController.java`
- `service/ICcdiCreditInfoService.java`
- `service/impl/CcdiCreditInfoServiceImpl.java`
- `domain/CcdiCreditNegativeInfo.java`
- `domain/dto/CreditInfoUploadResultDTO.java`
- `domain/vo/CreditInfoListVO.java`
- `domain/vo/CreditInfoDetailVO.java`
- `mapper/CcdiCreditNegativeInfoMapper.java`
- `mapper/CcdiCreditInfoQueryMapper.java`
- `resources/mapper/...`
职责说明:
- `CcdiCreditInfoController`
- 暴露上传、列表、详情、删除接口
- `CcdiCreditInfoService`
- 协调解析、归户、最新记录校验、覆盖写入
- `CcdiCreditNegativeInfoMapper`
- 维护负面信息表增删查
- `CcdiCreditInfoQueryMapper`
- 承担员工维度列表聚合查询和详情聚合查询
## 14. 异常处理
上传过程中逐文件处理,失败不影响其他员工成功:
- 文件后缀非法:失败原因为“文件格式不支持”
- 解析接口调用失败:失败原因为“征信解析失败”
- 解析成功但身份证号为空:失败原因为“未解析到员工身份证号”
- 身份证号未匹配到员工:失败原因为“未匹配到员工信息”
- 上传征信日期早于当前最新记录:失败原因为“上传征信日期早于当前已维护最新记录”
- 员工覆盖写入事务失败:失败原因为“征信数据保存失败”
删除接口按幂等处理:
- 即使员工当前无征信数据,也返回删除成功
## 15. 测试方案
### 15.1 后端测试
- 上传服务测试
- 单文件解析成功后正确写入两张业务表
- 同员工新日期文件覆盖旧数据
- 同员工旧日期文件被拒绝覆盖
- 一个员工失败不影响其他员工成功
- 删除后两张表数据均被清空
- 聚合查询测试
- 只有负债信息
- 只有负面信息
- 两者都有
- 两者都没有
- 分页下聚合字段正确
- 控制器测试
- 批量上传成功
- 混合成功/失败上传
- 详情查询成功/无数据
- 删除接口幂等
### 15.2 前端验证
- 上传多个 HTML 文件并查看结果提示
- 列表按员工维度展示聚合摘要
- 详情弹窗正确显示负债表和负面信息
- 删除后该员工征信摘要即时消失
- 前端生产构建通过
## 16. 文档与脚本产出要求
本次实施阶段需同步补充:
- 后端实施计划:`docs/plans/backend/`
- 前端实施计划:`docs/plans/frontend/`
- SQL 脚本:`sql/``sql/migration/`
- 实施记录:`docs/reports/implementation/`
文档落点需遵循仓库目录规范,避免写入错误目录。