20261016 从定时任务到主明细入库:Spring Boot 对接亚马逊订单的数据同步设计
对接亚马逊订单,最容易想到的实现是:定时调用订单接口,把返回结果写进数据库。
但订单会持续变化。一笔订单可能先处于待处理状态,随后变为已发货,也可能被取消;订单列表与商品明细需要分别获取,两类接口还可能分页。只完成一次请求,并不代表一笔订单已经同步完整。
本文结合一个 Spring Boot 项目的实际源码,梳理一套由 XXL-JOB 驱动的订单同步方案:先生成时间窗口任务,再拉取订单主单到暂存表,最后逐单获取商品明细,批量写入正式表。
一、利用XXLJOB来定时触发拉去订单
它使用的核心接口是:
| 接口 | 获取内容 | 在项目中的用途 |
|---|---|---|
| getOrders | 指定条件下的订单列表 | 按创建时间或最后更新时间采集主单 |
| getOrderItems | 指定订单的商品列表 | 补齐 SKU、数量、价格、税费、促销等明细 |
| getOrder | 单笔订单 | 指定订单补拉 |
Agent 层还封装了 getOrderAddress,但在本文分析的 Job 主流程中,没有发现对该方法的调用。地址主要取自订单响应中的 ShippingAddress,响应未提供的字段不会凭空补齐。
这里的"先列表、后明细"是两次不同的接口调用:getOrders 获取订单头信息,getOrderItems 获取订单商品。不能将它理解成只对订单列表返回的 JSON 进行本地拆表。
二、为什么分成三个阶段
订单同步被拆成三个职责:
对应的主要 XXL-JOB 入口是:
| 阶段 | 按更新时间处理 | 按创建时间处理 |
|---|---|---|
| 生成窗口任务 | initOrderJob | initOrderJobByCreateTime |
| 拉取订单主单 | syncOrderJob | syncOrderJobByCreateTime |
| 拉取商品明细 | syncOrderDetailJob | 共用同一套明细流程 |
这些是独立入口。源码声明了它们可以被调度,并不意味着一次调用就自动串行执行三个 Job。
拆分后的价值在于:主单采集不必等待所有订单商品查询完成,明细采集也有明确的待处理数据来源。主单已经落入暂存表后,后续处理可以继续从数据库推进。
三、六张表如何协作
| 表 | 数据粒度 | 主要职责 |
|---|---|---|
| amzn_job_sync_time | 卖家与业务类型的进度记录 | 保存时间窗口生成到哪里 |
| amzn_order_job | 一条时间窗口任务 | 保存查询范围、分页 token 和任务状态 |
| amzn_order_stash | 一次采集得到的单笔订单快照 | 暂存主单,等待商品明细采集 |
| amzn_order | 一笔正式订单 | 保存订单当前业务数据 |
| amzn_order_item | 一条订单商品 | 保存数量、价格、税费及商品编码 |
| amzn_order_address | 订单对应的地址记录 | 保存响应中已有的地址信息 |
另外,amzn_api_logs 保存接口请求与响应,用于排查调用问题。它与订单暂存表不是同一种数据:暂存表保存结构化订单字段,日志表保存调用记录。
整体流程如下:
bash
flowchart TD
A["同步进度表"] --> B["生成时间窗口任务"]
B --> C["amzn_order_job"]
C --> D["getOrders 分页查询"]
D --> E["amzn_order_stash"]
D --> F["amzn_order_address"]
E --> G["getOrderItems 逐单分页"]
G --> H["amzn_order"]
G --> I["amzn_order_item"]
G --> J["暂存状态更新为 DONE"]
四、任务生成:将时间进度变为可执行窗口
OrderJobHandler 获取卖家及其站点后,通过 CycleTimeService 查询 amzn_job_sync_time。
该表使用两种业务类型,分别维护进度:
- • ORDER_BY_UPDATETIME:按订单最后更新时间查询。
- • ORDER_BY_CREATETIME:按订单创建时间查询。
两者的意义不同。创建时间用于发现相应时间内创建的订单,更新时间用于发现已经存在但后来发生变化的订单。只查询新创建的订单,无法持续刷新旧订单的后续状态。
源码中的时间规则如下:
| 规则 | 按更新时间 | 按创建时间 |
|---|---|---|
| 查询截止时间 | 当前时间减 5 分钟 | 当前时间减 1 小时 |
| 首次初始化的默认范围 | 截止时间往前 7 天 | 截止时间往前 7 天 |
| 根据已有进度生成窗口时 | 开始时间再往前回退 1 小时 | 开始时间再往前回退 1 分钟 |
| 积压范围的拆分周期 | 7 天 | 7 天 |
例如,更新时间进度停在 10:00,本次截止时间是 10:25,则生成的实际查询范围是 09:00 至 10:25。回退一小时使相邻窗口存在重叠,后续通过订单更新时间判断是否需要重新处理。
这些时间是我们项目配置逻辑,不是所有订单系统都必须遵循的通用参数。
生成的任务写入 amzn_order_job,初始状态为 NEW,next_token 为空,记录卖家、站点、任务类型和起止时间。
五、主单采集:分页结果先进入暂存表
OrderSyncHandler 读取待执行的窗口任务,再由 OrderJobService.syncOrder 执行。
任务按卖家分组,卖家之间通过线程池并行,同一分组内顺序处理窗口任务。
按更新时间的任务最终调用 getOrders 的更新时间条件;按创建时间的任务则填入创建时间条件。远程调用经过:
Job 服务的 Feign 客户端 → Gateway → Agent SP → 亚马逊 Orders API。
Agent 复用 ApiClientFactory 获取卖家对应的 ApiClient,执行鉴权后的 SDK 调用。
每得到一页订单,代码依次转换订单字段、写入暂存表、保存已有地址,再更新窗口任务状态。
| job_status | 含义 |
|---|---|
| NEW | 尚未完成采集 |
| HAS_NEXT | 已处理部分页面,还有下一页 |
| DONE | 窗口内的订单列表采集结束 |
| ERROR | 调用返回失败 |
有下一页时,将返回的 token 保存到任务记录;后续可以从该分页位置继续。这个 DONE 仅说明订单列表采集结束,不能据此判断商品明细已经获取完整。
六、暂存表:决定哪些订单需要继续拉明细
OrderService.saveDataToStash 并不是无条件触发商品查询。
它先根据"卖家 + 站点 + 亚马逊订单号"查正式表,再比较订单最后更新时间:
| 正式表情况 | 新暂存记录的状态 |
|---|---|
| 正式表没有这笔订单 | NEW |
| 本次订单的更新时间更新 | NEW |
| 正式表更新时间大于等于本次快照 | NOT_DEAL |
无论是否需要处理,源码都会新增一条暂存记录。因此,暂存表可以保留多次采集快照,而正式表用于保存业务数据。
例如,同一订单被重叠窗口再次查到,但更新时间没有变化,就将这次快照标为 NOT_DEAL,避免重复请求商品接口。若更新时间变新,则标为 NEW,交给明细任务处理。
这让 amzn_order_stash 同时承担两个作用:保存订单采集过程,以及充当数据库中的待处理队列。
七、明细采集:逐单查询商品,再推进正式表
syncOrderDetailJob 查询暂存表中 NEW 和 WAIT_UPDATE 的记录,按订单更新时间降序,单轮最多取 10,000 条。
记录按卖家分组后,每个分组再拆成每批 200 条。这里的 200 是本地处理批次,不代表一个亚马逊请求能获取 200 个订单的商品。
对每笔订单,代码调用 getOrderItems,并继续获取该订单商品列表的所有页面。
因此,系统存在两种不同的分页:
- • 订单列表分页:一个时间范围内可能有很多订单。
- • 商品列表分页:一个订单下可能有很多商品。
正常成功路径上,获取商品后才准备写入正式主表和明细表:
主单的业务匹配键是"卖家 + 站点 + 订单号",商品明细在此基础上增加 orderItemId。代码使用的是这些业务键,而非只按数据库自增或分布式 ID 判断是否存在。
批处理方法 syncOrdersDetailSplit 使用独立事务,将该批正式主单、商品明细和暂存状态一起写入。(这里有个考点 就是 this调用事务注释的问题)
八、入库不仅是字段复制,还包含业务加工(后续ERP系统要根据订单表的数据去推送金蝶)
商品明细入库前,系统使用"卖家 + 站点 + sellerSku"匹配内部商品映射,补充 sku_id、sku、sku_name 和 fn_sku。
匹配成功标记 FILL_SUCCESS,初次未匹配到则标记 WAIT_FILL,后续可由 fillSku 任务补充。这一步将平台商品编码连接到企业内部商品体系。
订单字段还包含项目自己的识别规则,例如:
- • 根据 fulfillmentChannel 是否为 MFN,标记自配送订单。
- • 根据订单号前缀,标记多渠道订单。
- • 根据 sellerOrderId 的约定前缀,推断其他销售渠道。
- • 根据促销标识,标记 Vine 商品。
- • 根据国家时区配置,生成站点下单时间。
其中,前缀识别是项目业务约定,不应直接当作所有亚马逊订单都适用的平台规则。
九、如何判断"一笔订单同步完成"
这套设计里有三种进度,必须分开理解:
| 位置 | 回答的问题 |
|---|---|
| amzn_job_sync_time | 时间范围是否已经生成任务? |
| amzn_order_job.job_status | 这个窗口的订单列表是否已经采集结束? |
| amzn_order_stash.pull_detail_status | 这份订单快照是否已经完成后续处理? |
例如,窗口任务已经 DONE,但暂存记录仍为 NEW,说明主单已经拿到,商品明细还在等待处理。
暂存记录为 NOT_DEAL,表示它在进入暂存表时,被判定为不需要重复处理;它不是一次新的商品查询成功记录。
正常成功路径中,暂存记录 DONE 表示已经准备将该订单主单及商品明细写入正式表。