Files
baiyue-firstleg/docs/cross-region-firstleg-lastleg-deployment-plan.md
T
2026-07-30 18:01:36 +08:00

495 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 头程国内、尾程海外的跨区域微服务部署方案
更新时间:2026-07-29
依据:当前 `D:/baiyue` Spring Cloud Alibaba 微服务、`悦/头程物流流程图.svg`、头程下单原型和货代后台原型。
> 审核状态:待用户审核。本文是项目拆分、部署和迁移的实施方案;文中目标架构、海外资源和切流步骤不代表已经完成生产部署。当前生成的部署目录仅是同一 Git 仓库内的 Maven 聚合和配置骨架。
## 1. 结论
推荐采用“国内业务单元 + 海外业务单元 + 跨区域可靠事件桥”,不要把同一套 Nacos、MySQL、Redis 或一个微服务调用链直接横跨国内外服务器。
这里的“国内业务单元、海外业务单元”首先是代码、数据库和数据所有权边界,不等于一期必须立即购买海外服务器。当前项目应立即按两个部署单元拆分配置、服务职责和事件契约,但一期允许所有服务都部署在国内同一机房:`lastleg-service`、海外仓实例和海外集成实例先以 `REGION_CODE=CN` 运行。待事件链路、鉴权和对象存储满足迁移门槛后,再把尾程写入单元迁到海外,不需要重新拆项目。
```text
国内单元:客户、订单、头程、国内仓、认证和权限的主数据
海外单元:尾程、海外仓、清关、提柜、派送、POD、Amazon SP-API
跨区交接:本地事务 Outbox -> HTTPS/mTLS 事件中继 -> Inbox 去重 -> 处理回执
统一轨迹:海外发布里程碑事件,国内保存只读投影供客户查询
```
这一方案符合原型的业务边界:
- 头程在国内完成“下单 → 到仓 → 出仓 → 装船发运”。
- 尾程在海外完成“到港前 → 到港 → 到仓 → 出仓 → 签收”。
- “装船发运”是两个区域的数据所有权交接点。
### 1.1 当前已生成的部署骨架
| 路径 | 当前用途 | 当前状态 |
| --- | --- | --- |
| `D:/baiyue/deployments/cn` | 国内部署单元 Maven 聚合、环境变量示例和构建脚本 | 已生成,待审核;一期实际部署入口 |
| `D:/baiyue/deployments/overseas` | 海外部署单元 Maven 聚合、环境变量示例和构建脚本 | 已生成,待审核;暂不代表海外服务器已部署 |
| `D:/baiyue/common-event-contract` | 国内外共享的事件信封、类型和区域契约 | 已生成基础源码,6 个单元测试通过;尚未接入业务事件链路,不得视为可用于生产 |
| `D:/baiyue/event-bridge-service` | 跨区事件接收幂等、待发送事件和投递尝试的独立服务边界 | 已生成基础骨架,8 项测试通过;内部令牌、目标区域校验和 Inbox 幂等接收已实现,生产传输开关为关闭 |
两个部署目录引用根仓库现有模块,不复制业务源码,也不建立第二个 Git 仓库。构建命令为:
国内和海外聚合均已包含 `common-event-contract` 与 `event-bridge-service`。国内默认构建为 18 个 Reactor 模块;一期尾程临时在国内运行时,使用 `phase1-domestic-lastleg` Profile 构建 19 个 Reactor 模块并包含 `lastleg`。
```powershell
cd D:\baiyue\deployments\cn
.\build.ps1
# 一期尾程临时部署在国内
.\build.ps1 -IncludePhase1Lastleg
cd D:\baiyue\deployments\overseas
.\build.ps1
```
只校验默认聚合关系时,在对应目录执行 `mvn -f pom.xml -DskipTests validate`;校验一期国内尾程兼容聚合时执行 `mvn -f pom.xml -Pphase1-domestic-lastleg -DskipTests validate`。事件契约可在根目录执行 `mvn -pl common-event-contract -am test`。以上构建通过只说明代码依赖和测试通过,不等于生产部署、网络连通或数据迁移验收通过。
## 2. 当前代码现状判断
| 模块 | 当前情况 | 对跨区部署的影响 |
| --- | --- | --- |
| `gateway` | 已按服务名走 Nacos 和 `lb://` 路由 | 可以分别部署国内、海外 Gateway,但必须连接各自区域的 Nacos |
| `auth-service` | 国内统一登录,HS256 JWT | 可先放国内;正式跨区前建议改为 RS256/ES256 + JWKS |
| `iam-service` | 租户、角色、菜单、按钮权限 | 主库建议放国内,海外通过 Token 权限快照工作,不应每次跨区查询 IAM |
| `order-service` | 已有下单和状态机,也有 `ord_outbox_event` | Outbox 只是表和写入逻辑,尚缺可靠发布器、MQ/中继和跨区消费者 |
| `firstleg-service` | 目前只有健康检查、任务分配等少量能力 | 原型中的到仓、测量、排柜、订舱、装柜、报关等还需实现 |
| `lastleg-service` | 当前基本只有健康检查 | 还没有到港前、清关、提柜、海外仓、派送、POD 的业务闭环 |
| `warehouse-service` | 当前是一个服务边界 | 需要区分国内仓实例和海外仓实例,各自独占区域数据库 |
| `file-service` | 当前默认使用本地磁盘 | 跨区域部署不能继续依赖单机 `D:/...`,需要迁移到 OSS/S3 类对象存储 |
| `integration-service` | 已预留百度 OCR、Amazon SP-API 配置 | 国内实例启用百度 OCR;海外实例启用 Amazon SP-API 和当地承运商接口 |
| `event-bridge-service` | 已有端口 9300、独立库和三张基础表;内部服务令牌、目标区域、事件信封与摘要校验及 Inbox 幂等已实现,8 项测试通过 | mTLS、来源白名单、RocketMQ、真实跨公网 HTTP 投递、Outbox 扫描调度和业务消费者尚未实现,不能用于生产交接 |
因此现在可以先搭好部署骨架和事件契约,但不能把 `lastleg-service` 部署到海外后就认定尾程业务已经完成。
## 3. 推荐部署拓扑
```mermaid
flowchart LR
FE[客户/平台前端] --> EDGE[统一域名 + CDN/WAF/全局路由]
subgraph CN[国内业务单元]
CNGW[gateway-cn]
AUTH[auth-service]
IAM[iam-service]
ORDER[order-service]
FIRST[firstleg-service]
CNWMS[warehouse-service CN]
CNFILE[file-service CN]
CNOCR[integration-service Baidu OCR]
CNDB[(国内独立 MySQL)]
CNREDIS[(国内 Redis)]
CNNACOS[Nacos prod-cn]
CNOUT[Outbox Publisher]
CNPROJ[统一轨迹投影]
end
subgraph OS[海外业务单元]
OSGW[gateway-overseas]
LAST[lastleg-service]
OSWMS[warehouse-service OVERSEAS]
OSFILE[file-service OVERSEAS]
OSINT[integration-service Amazon/海关/承运商]
OSDB[(海外独立 MySQL)]
OSREDIS[(海外 Redis)]
OSNACOS[Nacos prod-overseas]
OSIN[Inbox Consumer]
OSOUT[Outbox Publisher]
end
EDGE -->|Auth/IAM/Order/First-leg| CNGW
EDGE -->|Last-leg/海外仓作业| OSGW
CNGW --> AUTH & IAM & ORDER & FIRST & CNWMS & CNFILE
OSGW --> LAST & OSWMS & OSFILE & OSINT
FIRST --> CNOUT
CNOUT -->|HTTPS 443 + mTLS| OSIN
OSIN --> LAST
LAST --> OSOUT
OSOUT -->|里程碑/回执 HTTPS 443 + mTLS| CNPROJ
```
### 3.1 国内部署的服务
| 服务 | 是否国内主部署 | 说明 |
| --- | --- | --- |
| `gateway` | 是 | 国内客户、平台后台入口 |
| `auth-service` | 是 | 账号登录、刷新、退出的唯一写入口 |
| `iam-service` | 是 | 企业、成员、角色、菜单和按钮权限主数据 |
| `order-service` | 是 | 商户下单、审核、受理以及订单主档 |
| `firstleg-service` | 是 | 到国内仓、测量差异、排柜、订舱、拖车、装柜和国内报关 |
| `warehouse-service` | 是 | 国内仓资料和国内仓作业数据 |
| `file-service` | 是 | 企业资质、国内报关资料、装柜照片等国内文件入口 |
| `integration-service` | 是 | 百度 OCR、国内报关代理或港务接口适配 |
| `audit-service` | 是 | 国内业务审计主记录 |
| `print-service` | 按需 | 国内标签、箱唛、作业单打印 |
### 3.2 海外部署的服务
| 服务 | 是否海外部署 | 说明 |
| --- | --- | --- |
| `gateway` | 是 | 海外运营后台、海外 PDA 和承运商回调入口 |
| `lastleg-service` | 是 | 到港前、清关、提柜、到仓、派送、签收和异常主服务 |
| `warehouse-service` | 是 | 海外仓预约、开柜、清点、打托、拣货和装车 |
| `file-service` | 是 | BOL、DO/PU、开柜照片、POD 等海外文件 |
| `integration-service` | 是 | Amazon SP-API、FBA 预约、海关、码头和海外承运商适配 |
| `audit-service` | 是 | 海外操作本地审计;摘要可异步回传国内 |
| `print-service` | 按需 | BOL、派送单、海外标签和仓内作业单 |
海外不部署 `order-service` 和 `firstleg-service` 的写实例,国内也不部署 `lastleg-service` 的写实例,避免同一业务聚合在两个区域同时写入。
### 3.3 一期与目标物理归属
| 服务 | 一期物理位置 | 后期主写位置 | 迁移说明 |
| --- | --- | --- | --- |
| `auth-service`、`iam-service` | 国内 | 国内 | 统一登录和权限主数据保持国内唯一写入 |
| `order-service`、`firstleg-service` | 国内 | 国内 | 订单和头程事实保持国内唯一写入 |
| `lastleg-service` | 国内 | 海外 | 一期先在国内运行;后期只把新创建的尾程聚合切到海外,旧单在国内排空 |
| `warehouse-service` | 国内单实例 | 国内仓实例在国内,海外仓实例在海外 | 同一代码按区域配置部署,数据库完全隔离 |
| `file-service` | 国内 | 国内、海外各一套 | 文件元数据和对象桶按数据产生区域归属,按业务需要复制 |
| `integration-service` | 国内 | 国内、海外各一套 | 国内启用 OCR/港务适配,海外启用 Amazon/海关/承运商适配 |
| `audit-service`、`print-service` | 国内 | 按区域各自部署 | 本地操作本地留痕和打印,不跨区同步调用 |
| `gateway`、Nacos、Redis | 国内 | 国内、海外各一套 | 两区互不注册、互不直接访问 Redis |
## 4. 原型节点与服务归属
### 4.1 国内头程
| 原型阶段 | 原型动作 | 主要服务 |
| --- | --- | --- |
| 下单 | 客户下单、委托下单、快速下单、换标下单 | `order-service`;一期仅实现普通头程订单,不扩展快速/换标完整产品 |
| 到仓 | 预约取货、送货上门 | `firstleg-service` + 国内 `warehouse-service` |
| 到仓 | 入库测量、差异核对、指定库位 | 国内 `warehouse-service`,结果事件回写 `firstleg-service` |
| 出仓 | 智能排柜、订舱、预约拖车 | `firstleg-service` |
| 出仓 | PDA 拣货、装柜、柜照和铅封照 | 国内 `warehouse-service` + `file-service` |
| 出仓 | 报关资料、代理报关、回执 | `firstleg-service` + 国内 `integration-service` + `file-service` |
| 出仓 | 港务信息、预计开船、到港 | `firstleg-service` + 船司/港务适配器 |
| 交接 | 装船发运 | `firstleg-service` 生成不可变交接快照并写 Outbox |
### 4.2 海外尾程
| 原型阶段 | 原型动作 | 主要服务 |
| --- | --- | --- |
| 到港前 | FBA 仓预约、BOL 预清货、清关资料、货物中转备注 | `lastleg-service` + 海外 `integration-service` |
| 到港 | 清关放行、DO/PU、通知码头车队提柜 | `lastleg-service` |
| 到仓 | 开柜拍照比对、PDA 打托、派送车队预约 | `lastleg-service` + 海外 `warehouse-service` + `file-service` |
| 出仓 | BOL 送货、PDA 拣货装车、整柜派送、自提出仓 | `lastleg-service` + 海外 `warehouse-service` |
| 签收 | 私人仓/FBA 签收回执、送仓异常 | `lastleg-service` + `file-service` + Amazon/承运商适配器 |
## 5. 跨区交接设计
### 5.1 正确交接点
当前 `order-service` 在订单 `APPROVED` 时写 `FIRST_LEG_ORDER_APPROVED`,这不是尾程交接事件。尾程只能在真实“装船发运”完成后建立预到港任务。
建议新增:
```text
FirstLegShipmentDeparted.v1
LastMileHandoffAccepted.v1
LastMileHandoffRejected.v1
LastMileMilestoneChanged.v1
LastMileCompleted.v1
```
### 5.2 交接事件示例
```json
{
"eventId": "01J...ULID",
"eventType": "FirstLegShipmentDeparted",
"eventVersion": 1,
"occurredAt": "2026-07-29T16:30:00+08:00",
"traceId": "trace-xxx",
"sourceRegion": "CN",
"operatorTenantId": 10001,
"customerTenantId": 20001,
"aggregateId": 90001,
"aggregateVersion": 7,
"handoverVersion": 1,
"payload": {
"firstLegOrderIds": [80001, 80002],
"containerNo": "MSCU1234567",
"sealNo": "SEAL123456",
"vesselName": "EXAMPLE VESSEL",
"voyageNo": "V001",
"mblNo": "MBL001",
"hblNos": ["HBL001"],
"etd": "2026-07-30T10:00:00+08:00",
"eta": "2026-08-18T09:00:00-07:00",
"destinationPortCode": "USLAX",
"destinationWarehouse": {
"type": "FBA",
"code": "LGB8"
},
"cargoSnapshot": [],
"documentRefs": [],
"checksum": "sha256:..."
}
}
```
文件正文不要放进消息,只传文件 ID、版本、SHA-256 和一次性下载凭证。海外确认保存后再返回 `LastMileHandoffAccepted`。
### 5.3 可靠性规则
1. 头程状态更新、状态历史和 Outbox 必须在一个国内 MySQL 本地事务中完成。
2. 国内发布器轮询 `PENDING/RETRY` Outbox,使用 HTTPS 443 + mTLS 投递到海外事件入口。
3. 海外先按 `eventId` 写 Inbox,唯一索引去重,再创建尾程交接记录。
4. 消费失败时不确认;重复投递必须返回相同业务结果。
5. 海外校验通过后写本地 Outbox,异步回传 Accepted/Rejected 回执。
6. 国内收到 Accepted 后才把交接状态标记为 `ACCEPTED`,但不能回滚已经真实发生的装船事实。
7. 修改交接数据要发布更高 `handoverVersion` 的新事件,不能覆盖旧快照。
8. 跨区断网时,两个区域本地业务继续运行;Outbox 积压告警,网络恢复后自动补投。
不建议让 `firstleg-service` 通过 OpenFeign 跨公网同步调用 `lastleg-service` 创建尾程单。国际链路一次超时就会把真实装船操作拖入不确定状态。
### 5.4 Topic、事件桥和订单通知
一期不强制引入跨区 MQ,可由 Outbox 发布器直接通过 HTTPS/mTLS 投递;引入 RocketMQ 后,国内和海外各使用独立集群,禁止一个 Broker 集群横跨两区。建议 Topic 如下:
| Topic | 方向 | 主要事件 |
| --- | --- | --- |
| `shipment-handover-cn` | 国内 → 海外 | `FirstLegShipmentDeparted.v1` |
| `shipment-handover-result-os` | 海外 → 国内 | `LastMileHandoffAccepted.v1`、`LastMileHandoffRejected.v1` |
| `shipment-milestone-os` | 海外 → 国内 | `LastMileMilestoneChanged.v1`、`LastMileCompleted.v1` |
| `security-session-cn` | 国内 → 海外 | 会话撤销、账号禁用、权限版本变化 |
同一尾程聚合以 `aggregateId` 作为顺序键;只能保证同一聚合有序,不追求全局有序。事件入 Inbox 并提交本地事务后才返回成功,失败采用有上限的指数退避,超过阈值进入死信并告警。
短信、邮件、站内信和企业微信等客户通知不得放在订单事务内同步发送。国内 `notification-service` 或通知消费者订阅国内订单事件及海外回传的里程碑事件,按 `eventId + channel + recipient` 幂等发送;承运商回调在海外本地处理,再通过里程碑事件回传国内。
## 6. Gateway 与流量路由
建议对外保持一个域名,例如 `api.example.com`,由 CDN/WAF/全局流量入口按路径转发:
| 路径 | 目标区域 |
| --- | --- |
| `/api/auth/**` | 国内 Gateway |
| `/api/platform/v1/iam/**`、`/api/merchant/v1/iam/**` | 国内 Gateway |
| `/api/merchant/v1/orders/**`、`/api/platform/v1/orders/**` | 国内 Gateway |
| `/api/merchant/v1/first-leg/**`、`/api/platform/v1/first-leg/**` | 国内 Gateway |
| `/api/merchant/v1/last-leg/**`、`/api/platform/v1/last-leg/**` | 海外 Gateway |
| 海外仓/PDA 专用路径 | 海外 Gateway |
国内 Gateway 只通过国内 Nacos 发现本地服务;海外 Gateway 只通过海外 Nacos 发现本地服务。跨区内部事件入口不暴露在普通业务 Gateway 上,应使用独立内部域名、WAF 白名单、mTLS 和消息签名。
如果暂时没有全局流量产品,可以先使用两个域名:
```text
https://api-cn.example.com
https://api-us.example.com
```
前端通过同一个 API SDK 根据业务域选择基础地址,不要让浏览器知道 Nacos 服务名。
一期即固定对外域名和证书,域名先指向国内 WAF/负载均衡;计划切换前至少 7 天把 DNS TTL 调整到 300 秒。后期统一域名由全局流量入口按路径转发,`auth/iam/order/first-leg` 固定国内,`last-leg` 才允许按灰度规则转发海外。DNS 只负责稳定入口,不用 DNS 权重承担租户或订单级灰度,因为 DNS 缓存无法保证同一业务聚合始终进入同一区域。
灰度期可先使用 `api-us-canary.example.com` 供指定运营账号和租户验证,正式流量由 Gateway 根据可信的 `execution_region` 路由。Gateway 必须删除客户端伪造的区域头。若前端需要访问两个域名,CORS 只允许明确的生产前端域名、必要方法和请求头;启用 Cookie 时禁止 `Access-Control-Allow-Origin: *`,并统一处理预检请求。优先使用同一对外域名,减少浏览器跨域和登录态问题。
## 7. Nacos 设计
不要使用一个 Nacos 集群横跨国内和海外,也不要让海外服务连接国内 `8848`。
| 区域 | Nacos | Namespace | Group | 注册服务示例 |
| --- | --- | --- | --- | --- |
| 国内 | `nacos-cn.internal` | `prod-cn` | `BAIYUE` | gateway、auth、iam、order、firstleg、国内 warehouse/file/integration |
| 海外 | `nacos-overseas.internal` | `prod-overseas` | `BAIYUE` | gateway、lastleg、海外 warehouse/file/integration |
两个区域可以保留相同服务名,例如都叫 `warehouse-service`,因为它们位于不同 Nacos 集群。配置必须增加:
```text
REGION_CODE=CN / OVERSEAS
DEPLOYMENT_CELL=prod-cn / prod-overseas
DATA_RESIDENCY=CN / OVERSEAS
```
当前各服务的 `spring.config.import` 固定读取 `group=BAIYUE`,所以两个区域先保持相同 Group,通过独立集群和 Namespace 隔离。若后续希望使用 `BAIYUE-CN/BAIYUE-US`,必须先把所有服务配置导入 URI 中的 Group 改成环境变量。
生产密钥不得放进 Nacos 明文配置,应进入云 KMS、Secret Manager 或主机受限环境变量。
## 8. 数据库、Redis 和文件边界
### 8.1 数据库
- 国内 MySQL:`baiyue_auth`、`baiyue_iam`、`baiyue_order`、`baiyue_firstleg`、`baiyue_warehouse_cn`、`baiyue_file_cn`、`baiyue_integration_cn`、`baiyue_audit_cn`、`baiyue_print_cn`。
- 一期尾程仍在国内使用 `baiyue_lastleg_cn`,库名提前带区域后缀,避免迁移时混淆数据所有权。
- 海外 MySQL:`baiyue_lastleg_os`、`baiyue_warehouse_os`、`baiyue_file_os`、`baiyue_integration_os`、`baiyue_audit_os`、`baiyue_print_os`。
- 禁止跨区数据库外键、跨区事务和双写。
- 国内只保存海外状态的只读轨迹投影;海外仍是尾程事实的唯一写入者。
- ID 继续使用应用生成的雪花 ID/ULID,避免区域数据库自增 ID 冲突。
数据库迁移优先采用“旧单排空、新单切换”,不移动执行中的尾程聚合。确需迁移历史数据时,先全量复制,再用 Binlog/CDC 追平;切换前短暂停止对应聚合写入,确认延迟为零、行数和关键字段校验一致后,才允许海外库转为主写。国内旧库在回滚观察期保持只读,不得与海外库同时写入。
### 8.2 Redis 与登录会话
当前各 Servlet 服务会访问 Redis 检查 Access Token 的 `jti` 会话键。若海外 `lastleg-service` 直接读取国内 Redis,跨区断网会导致海外所有请求鉴权失败。
推荐目标方案:
1. Auth 使用 RS256/ES256 签发短期 Access Token,海外 Gateway/服务通过 JWKS 本地缓存验签。
2. Access Token 有效期缩短到 10~15 分钟。
3. 登录、撤销、禁用账号发布 `SessionIssued/SessionRevoked/AccountDisabled` 事件,复制到海外本地 Redis。
4. 海外服务只访问海外 Redis,不访问国内 Redis。
以上改造是海外影子环境上线前置条件,不应放到海外正式切流之后。当前代码仍使用 HS256,并要求通过 Redis 中存在 `jti` 才允许访问;迁移时先支持带 `kid` 的 RS256/ES256 Token 和 JWKS 本地缓存,再将海外校验改为“本地验签 + 短有效期 + 本地撤销集合”。私钥只保存在国内 Auth,海外只持有公钥。过渡期国内可短暂兼容旧 Token,但海外灰度账号必须重新登录取得新 Token,稳定后移除 HS256。
PoC 阶段可以通过专线/VPN临时读取国内 Redis,但不能作为正式生产方案;Redis 的 `6379` 绝不能暴露公网。
### 8.3 文件
当前本地磁盘文件存储不适合双区域。建议:
- 国内文件进入国内 OSS/COS/S3 兼容桶。
- 海外文件进入海外区域对象存储桶。
- 只复制尾程履约必需的报关资料、BOL、箱单和授权照片。
- 文件下载使用短期预签名 URL,跨区域复制前做病毒扫描、MIME/魔数和 SHA-256 校验。
- POD 等海外原始文件保留在海外,国内保存合规副本或文件引用。
跨境传输企业、联系人、收货地址和报关资料前,应完成数据分类、最小化、访问审计和适用地区的合规评估。
## 9. 统一客户轨迹
客户不应为了看完整物流轨迹分别查询国内、海外两套数据库。建议国内新增“统一轨迹只读投影”,可以先放在 `order-service`,后续独立为 `tracking-query-service`。
```text
头程本地状态 -> 国内轨迹投影
海外 LastMileMilestoneChanged -> 跨区事件 -> 国内轨迹投影
客户 GET /api/merchant/v1/orders/{id}/timeline -> 只查国内投影
```
轨迹投影允许最终一致,目标延迟 P95 小于 30 秒。页面应展示“最后同步时间”,跨区中断时显示“海外轨迹同步延迟”,不能把旧状态误报成业务失败。
## 10. integration-service 的区域部署
现有 `integration-service` 不需要再增加 `common-sdk`。同一模块按区域配置不同适配器:
| 国内实例 | 海外实例 |
| --- | --- |
| `BAIDU_OCR_ENABLED=true` | `BAIDU_OCR_ENABLED=false` |
| `AMAZON_SP_API_ENABLED=false` | `AMAZON_SP_API_ENABLED=true` |
| 国内港务/报关代理适配器 | Amazon SP-API、海外海关、码头、车队/承运商适配器 |
各业务服务只调用内部抽象接口,不直接依赖百度或 Amazon SDK。第三方回调必须校验签名、保存原始回调摘要,并按第三方事件 ID 幂等处理。
## 11. 网络和安全
1. 国内与海外通过站点到站点 VPN、云企业网或专线连接;跨区业务事件只开放 HTTPS 443。
2. `3306`、`6379`、`8848`、MQ Broker 端口不得暴露公网。
3. 跨区事件入口使用 mTLS、IP 白名单、时间戳、nonce 和 HMAC/数字签名,防重放。
4. Gateway 清除外部伪造的 `X-User-Id`、`X-Tenant-Id`、`X-App-Code` 等内部头。
5. 国内和海外使用不同数据库账号、Redis密码、内部服务 Token 和对象存储凭证。
6. 所有交接、清关放行、提柜、装车、签收和人工覆盖动作写不可篡改审计日志。
7. 跨区链路设置独立限流、超时、熔断和重试策略,禁止无限重试。
## 12. 高可用与灾备规格
### 12.1 正式生产推荐
每个区域至少:
- 2 个应用节点,前置负载均衡/WAF。
- 3 节点 Nacos 集群或云托管注册配置中心。
- MySQL 主备/托管高可用,开启 Binlog 和时间点恢复。
- Redis 高可用或托管版。
- 区域本地 MQ/事件中继服务。
- 区域对象存储和跨可用区备份。
目标建议:核心写接口可用性 99.9%,数据库 RPO ≤ 5 分钟、RTO ≤ 60 分钟,跨区事件 P95 ≤ 30 秒。
### 12.2 低成本一期过渡
可以先使用国内、海外各一台应用服务器加托管 MySQL/Redis/对象存储,但必须接受单机故障风险,并至少做到:
- Docker Compose 或 systemd 自动拉起。
- 每日全备、持续 Binlog、异地备份。
- HTTPS、WAF/安全组、只开放 80/443。
- Outbox/Inbox、失败重试和人工补投工具先完成。
- 上线前做跨区断网 30 分钟并恢复补投的演练。
## 13. 分阶段实施顺序
### 阶段 A:国内完成逻辑拆分并上线
1. 所有服务仍可只部署国内,但按 `CN/OVERSEAS` 部署单元拆分 Nacos 配置、库名、对象存储目录和服务启动清单。
2. 为尾程聚合增加不可变的 `execution_region`,创建时确定归属,执行过程中不允许改变。
3. 实现尾程五个阶段的国内版本,并将本地磁盘文件迁到国内对象存储。
4. 国内先使用独立 `baiyue_lastleg_cn`,禁止尾程表与订单、头程或仓储跨库联表。
### 阶段 B:在国内打通可靠事件
1. 实现 `FirstLegShipmentDeparted.v1`、交接回执和里程碑事件。
2. 完成 Outbox 发布器、Inbox 幂等、聚合版本检查、重试、死信、人工补投和通知消费者。
3. 事件桥先在国内回环运行,验证跨服务契约,不把第一次验证留到跨国网络。
4. 完成重复投递、乱序、30 分钟断网、文件复制失败和恢复补投演练。
### 阶段 C:建立海外影子环境
1. 建立独立海外 Nacos、Gateway、MySQL、Redis、对象存储和监控,不注册到国内 Nacos。
2. 在切流前完成 RS256/ES256 + JWKS、海外本地撤销集合以及对象存储按需复制。
3. 海外服务先只消费脱敏测试事件或生产镜像事件,禁止产生真实外部操作和业务回执。
4. 对比国内结果与海外影子结果,连续 7 天满足数据、权限、性能和事件延迟门槛后才能灰度。
### 阶段 D:按新尾程聚合灰度
1. 先选择内部测试租户,再按 `1% → 10% → 30% → 100%` 放量,每档至少观察一个完整业务周期。
2. 只有新创建的尾程聚合进入海外并永久写入 `execution_region=OVERSEAS`;已经在国内执行的旧单继续留在国内直至完成。
3. Gateway 按聚合归属路由,不按一次请求随机分流;国内订单和统一轨迹查询入口保持不变。
4. 每档验证交接成功率、事件积压、鉴权失败率、文件可用率和租户隔离后再继续放量。
### 阶段 E:旧单排空与正式收口
1. 国内停止创建新的尾程聚合,只保留旧单处理、查询和应急能力。
2. 旧单全部完成并超过售后观察期后,将 `baiyue_lastleg_cn` 改为只读归档。
3. 停止临时兼容路由和 HS256,海外成为尾程唯一写入单元;完成灾备和回滚演练后收口。
## 14. 上线验收门槛
- 国内 Nacos 停止海外注册后,国内服务仍能正常下单和执行头程。
- 国际链路断开时,真实装船操作成功且事件保留在 Outbox,不产生半完成尾程单。
- 网络恢复后,同一事件重复投递 10 次只创建一条尾程交接记录。
- 海外拒绝不完整交接时,国内能收到结构化拒绝原因并进入人工处理。
- 海外里程碑乱序到达时,较旧版本不能覆盖较新状态。
- 海外 MySQL/Redis短暂不可用时,不影响国内订单和头程。
- 商户 A 不能访问商户 B 的交接、柜、货物、文件和 POD。
- 未授权用户即使直接调用清关放行、装车、签收接口也返回 403。
- POD、清关资料和装柜照片下载均使用短期授权并留下审计记录。
- 跨区事件积压、死信、延迟和回执缺失均有告警和补投手册。
### 14.1 灰度继续和回滚阈值
- 继续放量:跨区事件 P95 小于 30 秒、P99 小于 2 分钟,交接成功率不低于 99.9%,Inbox 重复创建业务记录为 0,必需文件 SHA-256 校验通过率 100%。
- 暂停放量:最老 Outbox 积压超过 5 分钟、海外接口 P95 比国内基线升高 30%、新增 401/403 比基线升高 0.2 个百分点,或出现无法自动补投的死信。
- 立即回滚:连续 5 分钟 5xx 超过 1%、发现跨租户访问、数据丢失/重复写入、错误业务状态推进或关键文件不可恢复。
- 回滚先停止创建新的海外聚合,已有海外聚合原则上继续由海外处理,不能直接路由到国内旧库。若海外无法继续写入,必须先冻结写入,通过反向事件或 CDC 补齐国内应急库并校验后再切回;禁止两区同时写同一聚合。
- 国内旧库、旧版本应用和路由配置至少保留 7 天回滚观察期。每次切换和回滚均记录审批人、时间点、数据校验结果和路由版本。
## 15. 近期 P0 清单
当前 P0 基线:部署聚合入口、公共事件契约和事件桥 Inbox 幂等基础骨架已经生成;头程真实发运、尾程聚合、可靠发布传输、统一轨迹和通知仍未闭环。
1. `P0-01`:冻结装船发运交接点和 `FirstLegShipmentDeparted.v1` 字段,给尾程聚合增加不可变 `execution_region`、聚合版本和唯一幂等键。验收:重复交接只创建一个聚合,旧版本不能覆盖新版本。
2. `P0-02`:完成头程到仓、测量、排柜、订舱、装柜、报关和装船发运最小流程,生成带版本与摘要的不可变交接快照。验收:前置条件不满足不能发运,关键证据可审计。
3. `P0-03`:在现有 `event-bridge-service` 基础上补齐 Outbox 扫描和发布状态机、有限重试、死信、人工补投及指标。验收:业务提交和事件不丢失,进程重启后可继续投递。
4. `P0-04`:实现 Inbox 到尾程业务消费者和接受/拒绝回执,并完成尾程到港前、到港、到仓、出仓、签收五阶段。验收:重复、乱序、坏摘要和非法状态流转均有自动化测试。
5. `P0-05`:先在国内回环打通事件桥,完成事件签名、来源白名单和 mTLS;RocketMQ 发布消费与真实跨区 HTTPS 投递按最终传输选型实现。验收:断网 30 分钟后可自动恢复补投,链路无跨库和同步跨区 Feign。
6. `P0-06`:将文件存储抽象为对象存储,完成版本、病毒扫描、SHA-256 和短期下载授权。验收:交接所需文件可按清单受控复制且下载有租户审计。
7. `P0-07`:把 JWT 改为 RS256/ES256 + JWKS,移除海外服务对国内 Redis 正向会话键的依赖。验收:海外断开国内 Redis 后仍可校验短期令牌和本地撤销信息。
8. `P0-08`:建立 `CN/OVERSEAS` 配置模板、区域化数据库名和 `REGION_CODE/DEPLOYMENT_CELL/DATA_RESIDENCY` 启动校验。验收:区域错配时服务拒绝启动,两区不共享 MySQL、Redis 和 Nacos。
9. `P0-09`:实现国内统一轨迹投影和异步订单通知。验收:重复不新增、乱序不回退、通知失败不回滚业务且可死信补投。
10. `P0-10`:建立覆盖断网、重复、乱序、跨租户、文件失败和回滚的自动化验收。验收:国内回环全部通过后才允许建设海外影子环境。
## 16. 最终建议
现在最合适的落地路线不是立即购买很多服务器,而是先完成三个基础能力:
1. 把 `firstleg-service` 的真实“装船发运”节点做出来。
2. 把现有 Outbox 表补成可发布、可重试、可告警的可靠事件链路。
3. 在国内先为 `lastleg-service` 实现 Inbox + 交接回执并验证五个尾程阶段,再将同一部署单元迁到海外影子环境。
完成这三项后,国内和海外才能真正成为两个可以独立运行、网络中断后仍可恢复的业务单元。