Files
ccdi/docs/plans/2026-03-11-project-detail-pull-bank-info-design.md

12 KiB

项目详情拉取本行信息设计

概述

本次设计面向项目详情页“上传数据”菜单中的“拉取本行信息”能力。用户在页面点击按钮后,弹出录入弹窗,支持手动输入身份证号、上传身份证 Excel 文件自动解析回填、选择时间跨度,然后提交后台异步拉取本行流水。

后端以项目现有“文件上传记录 + 线程池 + 落本地流水表”的链路为基础实现,不再新增第二套独立任务体系。每个身份证对应一条文件上传记录和一个线程任务,先调用流水分析平台“拉取行内流水”接口获取 logId,再复用现有“解析状态轮询 -> 获取文件上传状态 -> 获取流水列表并入库”的后半段处理链路。

已确认范围

  • 页面入口保留在 ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue
  • 点击“拉取本行信息”后弹出表单弹窗,不再使用简单确认框
  • 弹窗字段包括:
    • 证件号码文本域
    • 身份证文件上传
    • 时间跨度
  • 身份证文件解析规则:
    • 读取首个 sheet
    • 只读取第一列
    • 忽略表头
    • 忽略空行
    • 忽略重复值
  • 上传身份证文件后立即自动解析,并将结果回填到文本域
  • 文本域与文件解析结果合并后按输入顺序去重
  • 文件上传记录表中的 uploadUser 使用 SecurityUtils.getUserName()
  • 调用流水分析平台 fetchInnerFlow 时的 uploadUserId 使用 SecurityUtils.getUserId()
  • 创建上传记录时,先将身份证号写入 accountNos 作为主体账号
  • 每个身份证使用一个线程处理
  • 在调用“获取单个文件上传后的状态”接口时,根据返回值中的文件名更新文件上传记录
  • 获取 logId 之后的处理步骤与现有“导入流水文件”方法保持一致

方案对比

方案一:在现有文件上传服务中扩展并抽取公共流水线

  • 继续使用 CcdiFileUploadControllerICcdiFileUploadServiceCcdiFileUploadServiceImpl
  • 新增身份证文件解析接口和拉取本行信息提交接口
  • 将现有“文件上传成功拿到 logId 后”的处理逻辑抽成公共方法,供文件上传和本行拉取共同复用

优点:

  • 与现有上传记录列表、线程池、状态轮询、流水入库逻辑完全一致
  • 复用度最高,后续维护成本最低
  • 记录表、状态统计、页面轮询无需新增体系

缺点:

  • 需要对现有 CcdiFileUploadServiceImpl 做一次中等规模重构

方案二:新增独立的本行拉取服务

  • 新建独立 Controller 和 Service
  • 仅在最后复用“获取流水列表并入库”的局部能力

优点:

  • 对现有文件上传代码侵入较小

缺点:

  • 会出现两套高度相似的任务调度和状态回写逻辑
  • 后续容易出现功能漂移和修复不一致

方案三:在 Controller 中直接串接已有逻辑

  • Controller 直接完成记录插入、线程调度和接口调用

优点:

  • 早期开发速度快

缺点:

  • Controller 职责过重
  • 不利于测试和后续扩展

选型

采用方案一:在现有文件上传服务中扩展,并抽取“拿到 logId 后的公共处理流水线”。

该方案最符合当前项目已经存在的上传记录表、线程池和流水落库架构,可以保证文件上传和本行拉取在状态、错误处理和数据口径上保持一致。

前端交互设计

页面文件仍为 ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue

弹窗结构

新增“拉取本行信息”弹窗,包含以下控件:

  1. 证件号码文本域
  • 占位提示:支持逗号、中文逗号、换行分隔
  • 用于展示最终待提交的身份证集合
  1. 身份证文件上传
  • 仅支持 .xlsx.xls
  • 选中文件后立即自动调用后端解析接口
  • 解析成功后,把有效身份证集合合并回填到文本域
  1. 时间跨度
  • 开始日期
  • 结束日期
  • 提交时必填

前端交互规则

  1. 用户选择身份证文件后:
  • 立即上传到解析接口
  • 前端显示“正在解析身份证文件”
  • 解析成功后:
    • 将文件解析出的身份证集合与文本域当前内容合并
    • 去重后回填到文本域
    • 提示解析成功及有效条数
  • 解析失败后:
    • 保留文本域现有内容
    • 显示明确错误提示
  1. 用户点击“确认拉取”时:
  • 前端先对文本域内容做本地拆分和去重
  • 校验身份证集合非空、日期范围完整
  • 调用正式提交接口
  • 提交成功后关闭弹窗,刷新上传记录列表和统计,并开启现有轮询
  1. 页面上传记录列表无需新增新页面:
  • 本行拉取创建的记录与文件上传记录共用同一列表
  • 状态、上传时间、上传人展示口径保持一致

后端接口设计

接口统一放在 CcdiFileUploadController 下。

1. 解析身份证文件

  • 路径:POST /ccdi/file-upload/parse-id-card-file
  • 请求类型:multipart/form-data
  • 入参:
    • file
  • 返回:
    • idCards
    • count

用途:

  • 供弹窗选择文件后即时解析
  • 只负责读取、去重、校验身份证,不创建任务

2. 提交拉取本行信息任务

  • 路径:POST /ccdi/file-upload/pull-bank-info
  • 请求类型:application/json
  • 入参:
    • projectId
    • idCards
    • startDate
    • endDate
  • 返回:
    • batchId

用途:

  • 正式提交本行拉取任务
  • 一次请求可提交多个身份证

DTO / VO 设计

建议新增:

  • CcdiPullBankInfoSubmitDTO

    • Long projectId
    • List<String> idCards
    • String startDate
    • String endDate
  • CcdiIdCardParseVO

    • List<String> idCards
    • Integer count

服务层设计

继续使用:

  • ICcdiFileUploadService
  • CcdiFileUploadServiceImpl

建议新增两个对外方法:

  1. parseIdCardFile(MultipartFile file)
  • 读取首个 sheet 第一列
  • 忽略表头、空值、重复值
  • 统一执行身份证格式校验
  1. submitPullBankInfo(Long projectId, List<String> idCards, String startDate, String endDate, Long userId, String username)
  • 校验项目和日期
  • 插入上传记录
  • 在事务提交后调度线程池任务

核心数据流设计

一、身份证文件解析

  1. Controller 接收 Excel 文件
  2. Service 使用 EasyExcel 读取首个 sheet 第一列
  3. 将单元格内容转成字符串并清理空白
  4. 忽略首行表头
  5. 使用 LinkedHashSet 去重并保序
  6. 对每个值执行身份证格式校验
  7. 返回有效身份证集合

二、正式提交任务

  1. 校验 projectId
  2. 查询项目,获取 lsfxProjectId
  3. 校验 startDateendDate
  4. 校验身份证集合非空
  5. 为每个身份证创建一条 ccdi_file_upload_record
    • projectId = 当前项目
    • lsfxProjectId = 项目关联流水分析ID
    • fileStatus = uploading
    • fileName = 身份证号
    • accountNos = 身份证号
    • uploadUser = SecurityUtils.getUserName()
    • uploadTime = 当前时间
  6. 批量插入记录
  7. 在事务提交后启动调度线程

三、线程处理单个身份证

每个身份证一个线程,使用现有 fileUploadExecutor

单线程处理步骤:

  1. 调用 fetchInnerFlow

    • groupId = lsfxProjectId
    • customerNo = 身份证号
    • dataChannelCode = ZJRCU
    • requestDateId = 当天 yyyyMMdd
    • dataStartDateId = 开始日期 yyyyMMdd
    • dataEndDateId = 结束日期 yyyyMMdd
    • uploadUserId = SecurityUtils.getUserId()
  2. 从响应中获取唯一 logId

  3. 进入公共处理流水线

    • 更新记录 logId
    • 更新状态为 parsing
    • 轮询解析状态
    • 调用“获取单个文件上传状态”接口
    • 读取文件名字段,优先使用 uploadFileName,取不到则回退 downloadFileName
    • 将文件名回写到 ccdi_file_upload_record.file_name
    • 提取主体名称、主体账号
    • 调用“获取流水列表”接口分页拉取并入库
    • 成功后更新状态为 parsed_success
    • 失败时更新状态为 parsed_failed

公共流水线重构设计

当前 CcdiFileUploadServiceImpl 中,processFileAsync 同时承担“上传文件并拿到 logId”和“拿到 logId 后继续处理”两段职责。

本次建议拆成两段:

  1. 文件来源阶段
  • 上传文件拿到 logId
  • 或者拉取本行信息拿到 logId
  1. 公共处理阶段
  • 接收 projectIdlsfxProjectIdrecordlogId
  • 负责后续统一处理

拆分后:

  • 现有 processFileAsync 仍然保留,但只负责文件上传到平台并获得 logId
  • 新增 processPullBankInfoAsync 负责调用 fetchInnerFlow 并获得 logId
  • 两者统一调用新的公共处理方法,例如:
    • processRecordAfterLogIdReady(...)

文件上传记录表回写规则

记录初始化

  • fileName:先写身份证号占位
  • accountNos:写身份证号
  • enterpriseNames:初始为空

状态接口返回后

  • 若状态接口返回 uploadFileName,更新到 fileName
  • uploadFileName 为空但 downloadFileName 不为空,回写 downloadFileName
  • enterpriseNameList 存在时更新 enterpriseNames
  • accountNoList 存在时更新 accountNos

异常处理

提交阶段异常

以下场景直接拦截,不进入异步任务:

  • 项目不存在
  • 项目未绑定 lsfxProjectId
  • 身份证集合为空
  • 开始日期或结束日期为空
  • 开始日期大于结束日期

文件解析异常

  • 文件为空
  • 文件格式不是 Excel
  • 首个 sheet 第一列没有有效身份证
  • 存在非法身份证号

解析异常直接返回错误,不覆盖前端已有输入值。

异步执行异常

每个身份证单独处理,单条失败不影响其他条目:

  • fetchInnerFlow 失败:当前记录标记 parsed_failed
  • 轮询超时:当前记录标记 parsed_failed
  • 获取状态失败:当前记录标记 parsed_failed
  • 获取流水失败:清理该 logId 已入库流水后标记 parsed_failed

错误信息统一落入 error_message,并延续现有超长错误截断规则。

测试设计

后端测试

重点新增以下测试:

  1. 身份证文件解析测试
  • 读取首个 sheet 第一列
  • 忽略表头、空行、重复值
  • 非法身份证时返回失败
  1. 提交任务测试
  • 为每个身份证插入一条上传记录
  • 初始化 accountNos 为身份证号
  • uploadUser 正确记录当前用户名
  1. 公共流水线复用测试
  • fetchInnerFlow 成功后能进入公共处理链路
  • 状态接口返回文件名时能正确回写到记录表
  • 流水入库失败时能清理已写入数据
  1. Controller 测试
  • 解析接口成功/失败
  • 提交接口成功/失败

前端验证

  • 选择身份证文件后自动解析并回填
  • 手输身份证与文件解析结果正确合并去重
  • 日期必填校验生效
  • 提交成功后弹窗关闭并刷新列表
  • 正在执行的记录可通过现有轮询刷新状态

验收标准

功能验收

  • 上传数据页面可打开“拉取本行信息”弹窗
  • 身份证 Excel 上传后能自动解析并回填输入框
  • 提交后每个身份证都创建一条上传记录
  • 每个身份证走一个线程处理
  • 状态接口返回文件名后,记录列表能展示更新后的文件名
  • 成功记录能拉取流水并入库
  • 失败记录不会影响其他身份证继续执行

技术验收

  • 复用现有 fileUploadExecutor
  • 复用现有上传记录列表、统计和轮询机制
  • Controller 使用 Swagger 注释
  • Service 中公共处理逻辑不重复实现两套
  • 后端测试覆盖解析、提交、公共流水线复用

风险与约束

  1. 身份证文件模板不固定
  • 首版只按“首个 sheet 第一列”解析
  • 如后续存在多模板,再扩展模板识别
  1. fetchInnerFlow 返回值只提供 logId
  • 必须严格复用后续状态接口和流水接口,不能只看首个响应判断成功
  1. 线程池容量与批量身份证数量存在上限
  • 沿用现有线程池拒绝重试机制
  • 超量场景下记录单条失败,不阻断整批任务
  1. 文件名依赖状态接口返回
  • 需要兼容 uploadFileName 为空的场景,并回退 downloadFileName