# 公共导出与下载中心接入说明 本文描述当前已经实现的 RocketMQ + 事务 Outbox 版本。完整状态机、故障模型和容量门槛见[公共导出中心 RocketMQ 架构设计](./公共导出中心RocketMQ架构设计.md)。 ## 1. 边界 - `file-service` 统一承载任务、可靠调度、通用 XLSX、存储、过期清理、下载鉴权和指标。 - File 不跨库读取 IAM、Order、Warehouse 等业务数据;业务服务只实现标准只读 Provider。 - MQ 只传任务标识,不传筛选条件、业务行、Token 或文件。 - 订单与客户存储是 `FILE_STORAGE_ROOT` 目录且按单节点部署;多实例前必须使用跨主机共享卷或对象存储。 - 当前首个类型为 `CUSTOMER_LIST`,由 IAM 提供数据。 ## 2. 执行链路 ```text 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 ``` ```text 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` 创建示例: ```json { "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 契约 ```http 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. 配置与部署 关键环境变量: ```text 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` 是 File 部署必填项但允许生产空值:测试环境显式填 `test-`,生产环境显式填空字符串。Compose 单服务部署会插值整份文件,因此 File 服务块本身使用空默认值,避免 Auth/IAM 等无关服务被 MQ 配置阻断;`deploy-services.sh` 只在部署 File 时按环境强校验该变量与 `ROCKETMQ_NAME_SERVER`。 当前 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 或改数据库。 单任务冒烟: ```powershell $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`: ```powershell $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` 继续轮询与下载。这样停止/恢复基础设施仍由有权限的运维操作,脚本只验证任务是否最终追平: ```powershell .\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 @('', '', '', '') -MinimumRows 200000 ``` 脚本通过只证明 API 创建、数据库状态机、MQ 触发、Provider、XLSX 写入和下载主链路。CPU/堆/磁盘、Provider P95/P99、IAM 普通接口退化比例仍须同时从 Prometheus/Grafana 取证;DLQ 毒消息必须由 `mqadmin` 按 §19.2 投递,不能由普通验收账号伪造。