From 2f5bb4a5a82ae173c44a374fdea590f371c1d2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Sun, 27 Sep 2026 22:40:21 +0800 Subject: [PATCH] =?UTF-8?q?:memo:=20=E6=96=B0=E5=A2=9E=E9=87=91=E8=9D=B6?= =?UTF-8?q?=E9=99=84=E4=BB=B6=E4=B8=8A=E4=BC=A0=E4=B8=8B=E8=BD=BD=E9=93=BE?= =?UTF-8?q?=E8=B7=AF=E6=A2=B3=E7=90=86=E6=96=87=E6=A1=A3=EF=BC=88A902?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0+A801-2=E4=B8=8B=E8=BD=BD+=E5=AF=B9=E5=BA=94?= =?UTF-8?q?=E5=85=B3=E7=B3=BB=E6=A0=B8=E5=AF=B9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-09-27-金蝶附件上传下载链路梳理.md | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 doc/docs/业务流程/2026-09-27-金蝶附件上传下载链路梳理.md diff --git a/doc/docs/业务流程/2026-09-27-金蝶附件上传下载链路梳理.md b/doc/docs/业务流程/2026-09-27-金蝶附件上传下载链路梳理.md new file mode 100644 index 00000000..8a7c565d --- /dev/null +++ b/doc/docs/业务流程/2026-09-27-金蝶附件上传下载链路梳理.md @@ -0,0 +1,202 @@ +# TMS ↔ 金蝶 上传/下载链路梳理 + +- 日期:2026-09-27 +- 依据:代码真实实现(blade-third-party-api/blade-bfi-api、blade-service/blade-transport)+ 《2026-09-24-kingdee-attachment-sync-design.md》《2026-09-27-kingdee-attachment-download-design.md》 +- 回答三个问题:**系统需要从金蝶下载哪些东西**、**上传和下载的逻辑是什么**、**上传与下载能否对应上(闭环)** + +--- + +## 一、结论摘要 + +1. **文件级下载目前只有一种:票池发票文件(发票影像 PDF)**。判断正确——全代码库中唯一返回文件流的金蝶接口是 A801-2「文件下载」(`IBfiClient.invoiceDownload`),它的消费方只有两处,都围绕票池发票文件:① 附件同步链路内「下载回传」收票单;② 面向前端的票池发票文件下载端点(2026-09-27 新增,当前未提交)。 +2. 除文件外,系统还从金蝶「下载」**数据**(JSON 查询结果):票池发票元数据(A801-1)、收票单结果(A801-3)、付款申请/付款处理结果(A103-2/A103-4)、应付/应收单结果(A102-4/A302-4)、开票申请单与开票结果(A303-4/A303-5)、客商(A902-1)、汇率(A801-4)。这些用于幂等预检、星瀚内码反查、状态/结果定时回写。 +3. **上传与下载基本能对应上,但有三处缺口**:① 已上传金蝶的附件(`blade_kingdee_attachment_sync.kingdee_path`)没有回源下载通道(A801-2 是否兼容 A902 返回的 path 未验证,二期待金蝶确认);② 收款认领链路(A301-1/2/3)客户端已实现、业务零接线;③ 客商资质附件上传(A902)二期预留,entityNumber 未确认——客商附件目前既无上传也无下载。 +4. 每一个「推送/保存」类接口都有对应的「查询」类接口做幂等预检或结果回写,数据面上传下载成对;文件面只有「下载发票→上传回传」一条闭环。 + +--- + +## 二、接口总清单与业务接线状态 + +`IBfiClient`(blade-bfi-api)共 25 个接口,按业务接线情况分三类: + +### 2.1 业务已接线(上传/推送类) + +| 编号 | 接口 | IBfiService 方法 | 业务调用点 | +| --- | --- | --- | --- | +| A103-1 | 应付下推付款申请 | `pushPayApply` | PaymentApplicationServiceImpl:1045 | +| A103-3 | 预付款申请保存 | `savePrePayApply` | PaymentApplicationServiceImpl:949 | +| A102-2 | 财务应付单保存 | `saveApBill` | KingdeeApBillSyncSupport:318 | +| A302-2 | 财务应收单保存 | `saveArBill` | KingdeeArBillSyncSupport:258 | +| A303-2 | 收票单新增(批量) | `saveReceivedInvoice` | KingdeeReceivedInvoiceSyncSupport:218 | +| A302-6 | 收票指定应付 | `invoiceAssign` | KingdeeReceivedInvoiceSyncSupport:288 | +| A303-1 | 开票申请单新增 | `saveInvoiceApply` | InvoiceApplicationServiceImpl:702 | +| A902-2 | 客户保存 | `saveCustomer` | CustomerArchiveServiceImpl:527 | +| **A902** | **附件上传到指定单据** | `uploadAttachment` | KingdeeAttachmentSyncSupport:164(唯一入口) | + +### 2.2 业务已接线(下载/查询类) + +| 编号 | 接口 | IBfiService 方法 | 业务调用点 | 用途 | +| --- | --- | --- | --- | --- | +| A801-1 | 查询全票池发票数据 | `queryInvoicePool` | InvoiceReceiptServiceImpl:285、1110 | 选票弹窗实时查询;保存收票登记落镜像;下载回源兜底 | +| A801-3 | 收票单查询 | `queryReceivedInvoice` | KingdeeReceivedInvoiceSyncSupport:115 | 推送幂等预检;定时回写 | +| **A801-2** | **文件下载(发票文件)** | `downloadInvoice` | InvoiceReceiptServiceImpl:1248、KingdeeAttachmentSyncSupport:367 | **见第三节** | +| A102-4 | 财务应付单查询 | `queryApBill` | KingdeeApBillSyncSupport:155、182 | 幂等预检;正式结算内码反查 | +| A302-4 | 财务应收单查询 | `queryArBill` | KingdeeArBillSyncSupport:106、133 | 幂等预检;应收结果回写、开票前置校验、内码反查 | +| A103-2 | 付款申请单查询 | `queryPayApply` | PaymentApplicationServiceImpl:743 | 付款结果定时回写;单条状态回查 | +| A103-4 | 付款处理查询 | `queryPayBill` | PaymentApplicationServiceImpl:833 | 付款处理结果勾稽回写 | +| A303-4 | 开票申请单查询 | `queryInvoiceApply` | InvoiceApplicationServiceImpl:581 | 推送幂等预检;附件内码反查 | +| A303-5 | 开票申请发票查询(OpenApi) | `queryInvoiceApplyInvoice` | InvoiceApplicationServiceImpl:857 | 开票结果回写(发票号落库) | +| A902-1 | 客户查询 | `queryCustomer` | CustomerArchiveServiceImpl:554、821;BfiCustomerController:70 | 推送前置幂等复查;定时同步复查;客商列表穿透查询 | +| A801-4 | 汇率查询 | `queryExchangeRate` | BfiExchangeRateController:63(GET /bfi/exchange-rate) | 前端汇率展示 | + +### 2.3 客户端已实现、业务零调用(仅测试覆盖) + +| 编号 | 接口 | 说明 | +| --- | --- | --- | +| A301-1 | 收款单查询(`queryRecBill`) | 收款认领链路未启用 | +| A301-2 | 收款认领公告查询(`queryClaimAnnounce`) | 同上 | +| A301-3 | 收款认领结果推送(`pushClaimResult`) | 同上(上传方向) | +| A100-2/3 | 令牌校验/撤回 | 基础设施预留,token 管理由 `BfiRequestInterceptor`/缓存承担 | + +> 结论:**收款认领(收款单从金蝶下载 → TMS 认领 → 结果回推)是设计上预留、尚未接线的下载场景**,是目前除发票文件外唯一「未来可能需要从金蝶下载」的业务,与《业财融合方案》中收款认领规划对应。 + +--- + +## 三、文件下载链路(金蝶 → TMS)——「下载什么」的完整答案 + +### 3.1 唯一被下载的文件:票池发票文件 + +文件从哪来:A801-1 票池查询响应的 `downloadUrl`(16 位 hex 文件 id,另有 `imageUrl`/`pdfurl`/`snapshotUrl` 同形态),保存收票登记落镜像时序列化为 `blade_kingdee_invoice_pool.attachmentsJson`:`{"source":"piaozone","path":"","name":"<发票号>.pdf"}`(InvoiceReceiptServiceImpl `buildAttachmentJson`:1206-1215)。 + +文件怎么取:A801-2 `POST /ierp/kapi/v2/awrp/bosext/attachment/download`,body `{"path":"..."}`,返回文件流。BFI 层实现 `BfiServiceImpl.downloadInvoice`:532-555(校验 path 非空、HTTP 200、日志齐全),失败时金蝶返回 JSON 错误体(如 `FILE_IS_NOT_EXIST`「附件不存在。」)。 + +### 3.2 用途一:附件同步链路内下载回传(后端自动,无前端交互) + +`KingdeeAttachmentSyncSupport.fetchContent`:365-385:当附件项 `kingdeePathSource=true`(镜像发票文件)时,调 `bfiService.downloadInvoice(path)` → `readCapped` 流式读取(64KB 缓冲,**20MB 封顶**,超限中断并记失败原因)→ 内存字节数组 → A902 上传至收票单附件面板。 + +特点:同步链路内的下载**必须**经 20MB 软限制(A902 上传契约约束),下载的文件即传即走、不落盘不存 OSS。 + +### 3.3 用途二:前端下载通道(用户点击,2026-09-27 新增,当前未提交) + +端点:`GET /blade-transport/invoice-receipt/invoice-pool/attachment/download?invoiceNo=<发票号>`(InvoiceReceiptController:90-96,类级 `@PreAuth(menu="invoice_receipt")`)。 + +实现(InvoiceReceiptServiceImpl `downloadPoolAttachment`:1219-1312): + +``` +按发票号定位 path(resolvePoolAttachment) + ① 本地镜像优先:blade_kingdee_invoice_pool 按票号查(MP 租户插件自动隔离),解析 attachmentsJson 取 path/name + ② 镜像未落库或无文件 → A801-1 实时回源(queryPoolByInvoiceNo,模糊匹配结果做票号全等防御) +→ bfiService.downloadInvoice(path) ← 在写任何响应字节之前 +→ 校验:HTTP 200(BFI 层)+ Content-Type 非 application/json(防 JSON 错误体当文件下发) +→ 写响应头(Content-Type 缺省兜底 application/pdf / Content-Length 透传 / Content-Disposition RFC5987 文件名) +→ 8KB 缓冲流式透传 feign Response → ServletOutputStream,不落盘不进堆,finally 关闭 Response +``` + +错误处理:写流前的业务失败(票号空/无金蝶侧文件/BFI JSON 错误体)经 `WebUtil.renderJson(response, R.fail(msg))` 写 HTTP 200 JSON,前端按响应 Content-Type 是否含 `application/json` 统一识别;传输中断(IOException,客户端断开)仅记日志。**此通道不设 20MB 上限**(20MB 是 A902 上传契约,A801-2 下载无此约束)。 + +前端三处入口(票池弹窗附件列、正式结算认领发票行、付款申请发票附件行)经 `kingdee-attachment.js` 分流:`source === 'piaozone'` 或「有 path 且非 http(s) 开头」判为金蝶型走该端点;OSS 直链附件维持现有 `downloadFileByUrl` 行为不变。 + +### 3.4 数据下载(查询回写)的触发机制 + +| 定时任务(PaymentKingdeeResultTask / CustomerKingdeeSyncTask) | cron 默认 | 拉取内容 | 回写目标 | +| --- | --- | --- | --- | +| payResult | 每 10 分钟 | A103-2 付款申请单状态 + A103-4 付款处理结果 | 付款申请 kingdee_status | +| receivedInvoiceResult | 每 10 分钟 | A801-3 收票单结果 | 收票登记 kingdee_push_status、内码 | +| formalSettlement 应收回写(`syncKingdeeArResult`:631,随 payResult 任务调度) | 同上 | A302-4 应收单结果 | 正式结算应收面板状态 | +| invoiceApplication 开票结果(`syncKingdeeInvoiceResult`:803) | 同上 | A303-4 申请单 + A303-5 发票明细 | 开票申请状态 + 发票号落库 | +| customer(CustomerKingdeeSyncTask) | 每天凌晨 2 点 | A902-1 客商(按信用代码批量复查) | 客商档案协同状态 | + +另有手动触发点:各单据「同步金蝶结果」按钮(如 InvoiceReceiptController `/sync-kingdee-result`)、推送前的幂等预检(A102-4/A302-4/A801-3/A303-4/A103-2)、附件内码反查、票池选票弹窗实时查询(A801-1,游标分页 + 可用性过滤)。 + +--- + +## 四、文件上传链路(TMS → 金蝶)——A902 附件同步 + +### 4.1 唯一上传通道 + +`KingdeeAttachmentSyncSupport.syncAttachments`:125-189,经 `BfiServiceImpl.uploadAttachment`:558-583 调 A902 `POST /ierp/kapi/v2/frame/attachment/uploadFile`(multipart,`file` + `attachmentUploadFileArgs` JSON:entityNumber/billPkId/controlKey),返回 `data.path` 落 `blade_kingdee_attachment_sync.kingdee_path`。 + +核心机制: + +- **幂等**:按 `(biz_type, biz_id, attachment_key)` 唯一键;同步前载入已成功集合,命中即跳过(重推 A902 零调用);失败记录重试成功时覆盖更新 +- **幂等键**:用户附件 = 附件 url;发票文件 = A801-2 的 path(path 变化即红冲重开,自动视为新文件重传) +- **限制**:单文件 20MB 软限制(下载前按声明大小拦截 + 下载后按实际字节校验) +- **隔离**:逐附件独立 try/catch,失败记 `fail_reason` 继续下一个;附件失败不回滚主单、不阻塞同批其他单据 +- **补传**:不自动重试;重推主单时自动补传(主单被幂等预检跳过不影响附件分支) + +### 4.2 五类单据接入点(均已落地,主单事务提交后执行) + +| 单据 | biz_type → entityNumber | 接入点(代码位置) | 附件来源 | billPkId 缺失时的反查 | +| --- | --- | --- | --- | --- | +| 付款申请 | `pay_application` → `ap_payapply` | PaymentApplicationServiceImpl.syncKingdee:387 → doSyncKingdee 事务提交后 :403 → syncAttachmentsAfterPush:414 | 单据 attachmentsJson(用户附件) | 无(本地 kingdeeBillId 缺失即跳过待重推) | +| 正式结算-应付 | `formal_settlement_ap` → `ap_finapbill` | FormalSettlementServiceImpl.syncKingdee:510 → syncAttachmentsAfterPush:525 | 结算单 attachmentsJson | A102-4 按防重键反查回填(`resolveKingdeeBillId`) | +| 正式结算-应收 | `formal_settlement_ar` → `ar_finarbill` | 同上(按 settlementType 分流 :547-548) | 同上 | A302-4 反查回填 | +| 收票登记 | `invoice_receipt` → `ap_invoice` | InvoiceReceiptServiceImpl.pushOneReceivedInvoice:720,A303-2 落 pushed 后、A302-6 指定应付前 :786 → syncAttachmentsAfterPush:809 | 登记附件(用户附件)+ 镜像发票文件(**A801-2 下载**) | A801-3 预检行自带 id 回填 :752-754 | +| 开票申请 | `invoice_application` → `sim_original_bill` | InvoiceApplicationServiceImpl.syncKingdee:497 → syncAttachmentsAfterPush:511 | 单据 attachmentsJson | A303-4 勾稽回填带回 | + +流水落点:收票登记(`InvoiceReceiptRecord`)与开票申请(`InvoiceApplicationRecord`)有 actionType 流水表,追加 `attachment_upload` 记录;付款申请/正式结算无流水表不追加。列表聚合态(无附件/未上传/部分未传/已上传)由 `successCountByBizId`(一次 GROUP BY)+ `aggregateStatus` 运行时聚合。 + +### 4.3 数据上传(单据推送)不在本档展开 + +付款申请(A103-1/A103-3)、财务应付/应收单(A102-2/A302-2)、收票单+指定应付(A303-2/A302-6)、开票申请(A303-1)、客商(A902-2)的报文组装与状态机,见各 Support 类与《业财融合方案》主文档。 + +--- + +## 五、上传 ↔ 下载对应关系(能否对上) + +### 5.1 已闭环的对应 + +| 对应项 | 上传侧 | 下载侧 | 状态 | +| --- | --- | --- | --- | +| **票池发票文件** | A801-2 下载(20MB 封顶)→ A902 回传收票单附件面板,key=path | A801-2 前端下载端点(不设上限),镜像优先 + A801-1 回源 | ✅ 完整闭环:同一份文件既能自动回传金蝶,又能给前端人工查看/下载 | +| **用户附件(OSS 直链)** | http(s) 下载(60s 超时、20MB 封顶)→ A902 上传 5 类单据 | 不经金蝶:前端直连 OSS(`downloadFileByUrl` 现有行为) | ✅ 各自闭环,两类附件按字段特征分流互不干扰 | +| **单据数据面** | A103-1/3、A102-2、A302-2、A303-2/6、A303-1、A902-2 | A103-2/4、A102-4、A302-4、A801-3、A303-4/5、A902-1(幂等预检 + 内码反查 + 结果回写) | ✅ 每个推送接口都有查询接口配套,成对出现 | +| **票池元数据** | (消费方)收票登记落镜像、A303-2 分录取数 | A801-1 实时查询(选票弹窗 + 回源) | ✅ 闭环 | + +### 5.2 对不上的缺口(单向或未启用) + +| # | 缺口 | 现状 | 影响 | +| --- | --- | --- | --- | +| 1 | **已上传附件的金蝶侧回源下载** | A902 上传成功后 `kingdee_path` 已落 `blade_kingdee_attachment_sync`,但没有任何代码用它发起 A801-2 下载 | TMS 侧附件若 OSS 丢失/清理,无法从金蝶恢复。A902 返回 path(`211e6ce5c9805c00`)与 A801-2 path(`234e244b2c803400`)同为 16 位 hex,疑似同命名空间,但兼容性**未验证**——2026-09-27 设计「不做的事」第 1 条明确列为二期待确认项 | +| 2 | **收款认领链路** | A301-1 收款单查询、A301-2 认领公告查询、A301-3 结果推送在 `IBfiClient`/`BfiServiceImpl` 已完整实现,业务层零调用 | 金蝶收款数据下载 → TMS 认领 → 结果回推整条链路待启用;启用前 A301 系列不产生任何下载 | +| 3 | **客商资质附件** | `KingdeeAttachmentConstant.ENTITY_NUMBERS` 仅预留注释(CustomerArchiveServiceImpl 侧无任何附件上传接线);客商的 entityNumber/controlKey 待金蝶确认 | 客商附件目前既不上传也不下载,二期接入时补一个 biz_type 条目即可复用现有架构 | +| 4 | 票池文件时效性 | 镜像 `attachmentsJson` 落库后 path 是否会过期未知;下载时镜像快照过期(红冲重开)报「附件不存在」**不自动刷新**,与上传取快照口径一致 | 需金蝶确认 `downloadUrl` 有效期与失效表现,决定是否要补 A801-1 定时刷新镜像机制 | +| 5 | 附件面板反向查询 | 金蝶无「查询单据附件列表」接口接入 | 附件同步状态只能以本地同步表为准,无法与金蝶侧附件面板对账 | + +--- + +## 六、验证核对表(文档 vs 代码一致性) + +| 设计文档断言 | 代码核实结果 | +| --- | --- | +| A902 五类单据接入、entityNumber 映射 | ✅ `KingdeeAttachmentConstant.ENTITY_NUMBERS` 五条映射与文档完全一致,未配置类型抛 ServiceException | +| A801-2 复用 `invoiceDownload`,附件链路无新增接口 | ✅ `KingdeeAttachmentSyncSupport.fetchContent` 直接调 `bfiService.downloadInvoice` | +| 附件上传在主单事务提交后执行(ADR-7) | ✅ 付款申请经 `syncKingdee` 包装层(doSyncKingdee 事务外 :403);正式结算/开票申请经 `SpringUtil.getBean(...).doSyncKingdee` 自代理(:512/:499);收票在推送编排内但独立 try/catch(:786) | +| 幂等键 url/path、失败覆盖更新、20MB 双重校验 | ✅ syncAttachments:144-177(跳过/超限拦截/覆盖更新),readCapped:388-403 | +| 下载按发票号定位、镜像优先 + 实时回源、先校验后写流 | ✅ resolvePoolAttachment:1292-1312、doDownloadPoolAttachment:1242-1285,JSON 错误体解析 readBfiJsonMessage:1315-1332 | +| 下载不设 20MB 上限、8KB 流式透传 | ✅ DOWNLOAD_BUFFER_SIZE 缓冲拷贝,无 readCapped 调用 | +| 失败经 `WebUtil.renderJson` 返回 200 JSON(勘误 #2) | ✅ downloadPoolAttachment:1222-1235 | +| 收票/开票申请追加 `attachment_upload` 流水,付款/结算不追加 | ✅ InvoiceReceiptServiceImpl:823、InvoiceApplicationServiceImpl:522;付款/结算接入点无 record 调用 | + +--- + +## 七、顺带发现的风险点(与本次梳理主题相关,建议确认) + +1. **客商定时同步被硬编码过滤到单一客商**:`CustomerArchiveServiceImpl.syncKingdeeCustomerData`:625-628 的查询条件含 `.eq(CustomerArchive::getCustomerCode, "KS000067")`,即每天凌晨 2 点的客商同步任务只会处理这一个客商编码,疑似联调/灰度遗留。上线前需移除,否则其余客商的定时推送与状态复查全部不生效。 +2. **2026-09-27 下载功能代码未提交**:`InvoiceReceiptController`、`IInvoiceReceiptService`、`InvoiceReceiptServiceImpl` 的下载端点改动仍在工作区(git status modified),设计文档已写 as-built,注意随下次提交入库。 +3. 镜像定位 `resolvePoolAttachment`:1293-1294 按票号 selectList 后取 `mirrors.get(0)`:依赖「同租户票号唯一」(A801-1 upsert 匹配键保证),若上游契约变化出现重复票号将静默取第一条,可考虑加 `last("limit 1")` 或唯一索引兜底(当前表无票号唯一索引约束见 `blade_kingdee_invoice_pool_a8011_20260920.sql`)。 + +--- + +## 八、术语与代码索引 + +| 术语 | 代码落点 | +| --- | --- | +| A902 附件上传 | `IBfiClient.uploadAttachment`(blade-bfi-api feign)/ `BfiServiceImpl.uploadAttachment` | +| A801-2 文件下载 | `IBfiClient.invoiceDownload` / `BfiServiceImpl.downloadInvoice` | +| 附件同步主流程 | `org.springblade.transport.support.KingdeeAttachmentSyncSupport` | +| 附件同步表 | `blade_kingdee_attachment_sync`(实体 `KingdeeAttachmentSync`,建表 `doc/sql/transport/blade_kingdee_attachment_sync_20260924.sql`) | +| 票池镜像 | `blade_kingdee_invoice_pool`(`KingdeeInvoicePool`,`InvoiceReceiptServiceImpl` 内 upsert) | +| 前端下载端点 | `InvoiceReceiptController.downloadPoolAttachment`(GET /invoice-receipt/invoice-pool/attachment/download) | +| entityNumber 映射 | `org.springblade.transport.constant.KingdeeAttachmentConstant` | +| 定时回写任务 | `org.springblade.transport.task.PaymentKingdeeResultTask`、`CustomerKingdeeSyncTask` |