Files
baiyue-firstleg/docs/公共导出与下载中心接入说明.md
T
2026-08-04 09:05:28 +08:00

9.0 KiB
Raw Blame History

公共导出与下载中心接入说明

本文描述当前已经实现的 RocketMQ + 事务 Outbox 版本。完整状态机、故障模型和容量门槛见公共导出中心 RocketMQ 架构设计。

1. 边界

  • file-service 统一承载任务、可靠调度、通用 XLSX、存储、过期清理、下载鉴权和指标。
  • File 不跨库读取 IAM、Order、Warehouse 等业务数据;业务服务只实现标准只读 Provider。
  • MQ 只传任务标识,不传筛选条件、业务行、Token 或文件。
  • 一期存储是 FILE_STORAGE_ROOT 目录且按单节点部署;多实例前必须使用跨主机共享卷或对象存储。
  • 当前首个类型为 CUSTOMER_LIST,由 IAM 提供数据。

2. 执行链路

Admin
  -> POST /api/platform/v1/files/export-tasks
  -> file_export_task_guard 行锁下复核重复提交及活动上限
  -> 同一事务写 file_export_task + file_export_outbox
  -> Publisher 投递 RocketMQ(失败无限退避)
  -> Consumer 按 dispatch_version + 数据库租约领取(并发 4)
  -> POST /internal/exports/v1/{TYPE}/batches
  -> 通用 SXSSFWorkbook 流式写盘
  -> 条件提交 SUCCEEDED
  -> GET /api/platform/v1/files/export-tasks/{id}/download
PENDING -> RUNNING -> SUCCEEDED -> EXPIRED
                   -> PENDING(自动重试,dispatch_version + 1)
                   -> FAILED  -> PENDING(人工重试,dispatch_version + 1)

消息补投保持同一版本;只有执行失败、执行租约恢复和人工重试递增版本。独立 watchdog 覆盖 Provider 拉取和最终写盘,最终成功 SQL 同时校验任务状态、版本、租约所有者与数据库 deadline。

Reconciler 负责补写缺失 Outbox、同版本重发长期 SENT 命令、恢复超时执行租约、释放超时 Outbox 租约及过期文件清理。旧 ExportTaskWorker 已删除,避免绕过版本和退避窗口。

3. 浏览器 API

  • POST /api/platform/v1/files/export-tasks
  • GET /api/platform/v1/files/export-tasks
  • GET /api/platform/v1/files/export-tasks/{id}
  • GET /api/platform/v1/files/export-tasks/{id}/download
  • POST /api/platform/v1/files/export-tasks/{id}/retry

创建示例:

{
  "exportType": "CUSTOMER_LIST",
  "format": "XLSX",
  "taskName": "客户列表",
  "parameters": { "keyword": "柏悦", "status": "ACTIVE" }
}

创建按注册表校验访问域、启用状态和来源权限。客户导出要求 platform:customer:export;列表和下载要求 platform:download:list。默认只能访问本人任务,platform:download:all 可访问同一租户与应用域的全部任务。人工重试只允许 FAILED,并重新校验类型启用状态和来源权限。

限制:单用户最多 3 个活动任务、单类型最多 5 个活动任务;同一用户 60 秒内相同类型和规范化参数返回已有活动任务。

4. Provider 契约

POST /internal/exports/v1/{exportType}/batches
X-Internal-Service-Token: ${INTERNAL_SERVICE_TOKEN}
Content-Type: application/json

请求使用 common-core 的 ExportBatchRequest,必须包含 taskId、tenantId、appCode、requestedBy、dispatchVersion、parameters、cursor、snapshotToken 和 limit。Provider 必须按身份字段约束查询,不得信任 parameters 内的租户字段。

响应使用 ExportBatchResponse:首批返回 schemaVersion、schemaHash、snapshotToken、columns、rows、游标和估算总行数;后续批次必须保持 schemaHash,可省略 columns。File 在 Feign 解码流读取阶段限制单批 4MB,再校验游标推进、列/行/文件上限,并支持 STRING、INTEGER、DECIMAL、BOOLEAN、DATE、DATETIME 六类 Excel 单元格。

IAM CUSTOMER_LIST Provider 强制 tenantId=0、appCode=PLATFORM,采用 HMAC 签名的最大商户 ID 快照。因此不会混入开始后新增客户,但不冻结期间的修改与删除。

新增类型只需:业务服务实现标准 Provider、在 baiyue.file.export.definitions 注册受控服务/路径/权限/访问域/容量参数、补权限和测试。不得修改公共 Controller、Consumer 或 Writer。

5. MQ 与 DLQ

  • Topic:${MQ_TOPIC_PREFIX}baiyue-file-export-command-v1,4 个队列。
  • Consumer:${MQ_TOPIC_PREFIX}baiyue-file-export-worker-v1,同步并发 4。
  • Producer:${MQ_TOPIC_PREFIX}baiyue-file-export-publisher-v1。
  • eventId、taskId、dispatchVersion、schemaVersion 同时写入 User Properties。
  • 业务失败从不进入 DLQ;只有无法反序列化或不支持版本的消息不确认消费。
  • DLQ Handler 不解析损坏 body,只从四个属性定位,并在同一事务内把 Outbox 置 QUARANTINED、当前 PENDING 任务置 FAILED。缺少定位属性只记录 CRITICAL 和 messageId,不误伤任务。
  • DLQ Handler 的数据库处置失败持续重试,不允许因有限次数耗尽进入无人消费的二级 DLQ。

6. 配置与部署

关键环境变量:

ROCKETMQ_NAME_SERVER
ROCKETMQ_BROKER_IP
MQ_TOPIC_PREFIX
FILE_EXPORT_CONSUMER_CONCURRENCY
FILE_EXPORT_RECONCILER_ENABLED
FILE_EXPORT_PUBLISHER_ENABLED
FILE_EXPORT_CONSUMER_ENABLED
FILE_EXPORT_RETENTION_DAYS
FILE_STORAGE_ROOT

MQ_TOPIC_PREFIX 是部署必填项但允许空值:测试环境显式填 test-,生产环境显式填空字符串。不能使用 :-test- 作为 Compose 默认值,否则生产空值会被重新替换成测试前缀。

当前 RocketMQ Compose 不映射 Broker 宿主机端口,File 与 Broker 必须同在 infra-network,因此 ROCKETMQ_BROKER_IP 填 rocketmq-broker。Topic 初始化会等待 BaiyueCluster 真正注册,任一幂等初始化命令失败后自动重试。

File 容器初始 JVM 为 -Xms1g -Xmx2g -XX:+ExitOnOutOfMemoryError。Provider 单批连接/读取超时分别为 3 秒/15 秒;执行租约 300 秒、watchdog 60 秒、deadline 30 分钟、消费超时 90 分钟、单文件 50MB。

开发阶段确认无保留任务后,重建并执行 file/src/main/resources/db/file.sql。RocketMQ 使用 deploy/docker-compose.rocketmq.yml;脚本会先校验并展开 ROCKETMQ_BROKER_IP,再显式创建 4 队列命令 Topic、Worker 消费组、DLQ Handler 消费组以及可读写的 Worker DLQ。上线顺序:Broker -> SQL -> Nacos 注册表 -> IAM Provider -> File 三开关全关 -> Reconciler -> Publisher -> Consumer -> Admin。

生产启用并发 4 前必须完成 4 个 20 万行任务并发压测;若 CPU、堆、磁盘、Provider 或 IAM 普通接口延迟不达设计门槛,回退并发 2。

7. 观测与验收

Prometheus 指标包括任务创建/完成/耗时、队列年龄、Outbox 数量/年龄/最大尝试次数、Reconciler 动作与 export_dlq_messages_total;日志携带任务与版本标识但不打印完整数据或令牌。

最低验收:任务与 Outbox 同事务;Broker 停机恢复后追平;重复消息只生成一个有效文件;watchdog 覆盖最终写盘;deadline 竞态不能提交成功;同版本补投不递增版本;毒消息只隔离一次;IAM 快照不混入新增客户;生成的 XLSX 可真实打开且日期、金额为可计算类型;普通用户不能访问他人任务。

7.1 测试环境自动验收入口

scripts/export-center-test-env-acceptance.ps1 通过 Gateway 创建真实 CUSTOMER_LIST 任务,轮询终态,下载文件并校验 XLSX 的 ZIP 签名。Token 只通过参数或环境变量注入,脚本不会登录、部署、停止 Broker 或改数据库。

单任务冒烟:

$env:ADMIN_ACCESS_TOKEN = '<测试环境平台用户Token>'
.\scripts\export-center-test-env-acceptance.ps1 `
  -GatewayBaseUrl 'http://8.163.30.128:9000'

并发 4 容量验收必须使用 4 个不同申请用户,避免 60 秒重复提交合并,并要求每个账号都具备 platform:customer:export 与 platform:download:list:

$tokens = @($env:EXPORT_TOKEN_1, $env:EXPORT_TOKEN_2, $env:EXPORT_TOKEN_3, $env:EXPORT_TOKEN_4)
.\scripts\export-center-test-env-acceptance.ps1 `
  -GatewayBaseUrl 'http://8.163.30.128:9000' `
  -AccessTokens $tokens -TaskCount 4 -MinimumRows 200000 -TimeoutSeconds 3600

故障注入分两段执行:故障期间使用 -CreateOnly 创建并保存任务 ID;恢复 Broker、File 或 IAM 后,用相同申请人的 Token 和 -TaskIds 继续轮询与下载。这样停止/恢复基础设施仍由有权限的运维操作,脚本只验证任务是否最终追平:

.\scripts\export-center-test-env-acceptance.ps1 `
  -GatewayBaseUrl 'http://8.163.30.128:9000' -AccessTokens $tokens -TaskCount 4 -CreateOnly

.\scripts\export-center-test-env-acceptance.ps1 `
  -GatewayBaseUrl 'http://8.163.30.128:9000' -AccessTokens $tokens `
  -TaskIds @('<taskId1>', '<taskId2>', '<taskId3>', '<taskId4>') -MinimumRows 200000

脚本通过只证明 API 创建、数据库状态机、MQ 触发、Provider、XLSX 写入和下载主链路。CPU/堆/磁盘、Provider P95/P99、IAM 普通接口退化比例仍须同时从 Prometheus/Grafana 取证;DLQ 毒消息必须由 mqadmin 按 §19.2 投递,不能由普通验收账号伪造。