×

《淘宝订单API落地方案:taobao.trade.fullinfo.get 与 消息推送 vs 轮询 的成本/可靠性》(附Python源码)

万邦科技Lex 万邦科技Lex 发表于2026-10-12 10:46:19 浏览36 评论1

1人参与发表评论

《淘宝订单API落地方案:taobao.trade.fullinfo.get 与 消息推送 vs 轮询 的成本/可靠性》(附Python源码)

一、先拍结论

taobao.trade.fullinfo.get 永远不是“同步方式”,它是“真相源拉取动作”。
真正二选一的是:
  • 消息推送(TMC / 订单变更消息 / 聚石塔 DSS) → 低延迟、省轮询、但会丢/会重/会延迟

  • 轮询(trades.sold.increment.get + fullinfo.get) → 稳、能兜底、但烧 API 额度、有延迟

生产级淘宝订单同步只有一种正确架构:
消息做触发器,增量轮询做兜底,fullinfo 做真相源,tid+status+modified 做幂等键。
官方也明说:纯轮询“比较低效,不推荐”;消息+fullinfo“最复杂但效率最高”;DSS“效率最为高效”。

二、三种同步方式定位

方式
核心接口
延迟
成本
可靠性
适用
全量初始化
trades.sold.get → trade.fullinfo.get
小时级
高(历史单多)
中
首次接入/换库
增量轮询
trades.sold.increment.get → fullinfo.get
30s~10min
中(按调用次数)
高(自己可控)
中小卖家、兜底
消息推送
TMC / 订单消息 → fullinfo.get
秒级
低调用费,DSS 按单计费
中(要自己补)
中大型卖家
聚石塔 DSS
平台推到塔内 RDS / MQ
秒级
¥0.12/百单 + 套餐
高(但仍是最终一致)
大促/几十万单/天
淘宝官方口径:
  • trades.sold.get:初始化用,不该拿来拿增量

  • trades.sold.increment.get:增量用,不该拿来拉三个月历史

  • trade.fullinfo.get:列表只给半截,详情才给子单/优惠/物流/退款


三、消息推送的隐藏约束(不写代码也会漏单)

1. 消息不是真相

订单消息只说“tid=123 变了”,不说“变成啥”。
必须再调 trade.fullinfo.get 拉详情。

2. 消息会丢

  • 回调地址超时(有资料提到默认约 5s)→ 平台判失败

  • 重试 3 次(10s/30s/60s)还失败 → 不再推

  • 大促淘宝内部延迟 → 消息晚 1 分钟以上

  • Webhook 验签错 → 直接丢弃

3. 消息会重

同一条 tid 的状态变更可能推 2 次。
msgId 去重,tid+status+modified 推进状态。

4. 消息不保证顺序

WAIT_SELLER_SEND_GOODS 可能比 TRADE_PAID 先到。
状态机必须支持“旧事件晚到”。

四、轮询的隐藏约束(不写代码也会烧钱)

1. 增量窗口前移

start_modified = last_sync - 10min   # 平时
start_modified = last_sync - 30min   # 双11/大促
不前移 → 淘宝写库延迟 → 漏单。

2. 倒序分页必须从后往前翻

increment.get 按 modified 倒序返回。
正向翻页时订单被修改 → 总数变 → 漏单。

3. type 不传全 = 漏单

默认只查 guarantee_trade/auto_delivery/ec/cod/step。
要全量必须显式传所有交易类型。

4. 时间跨度

一次请求时间跨度有限(有资料称一天内),别塞 7 天。

5. 成本模型

超免额后:
  • 塔内 fullinfo.get ≈ ¥0.02 / 百次

  • 塔外 ≈ ¥0.20 / 百次(10 倍)

  • DSS ≈ ¥0.12 / 百单(不是按次,是按新增订单)

轮询 1000 个变更 tid → 1000 次 fullinfo
塔外:¥2.0 / 轮
塔内:¥0.2 / 轮
每秒轮询?一个月能烧出一台服务器钱。

五、生产架构:消息 + 轮询 双通道

┌─────────────┐
│ 淘宝订单事件 │ TMC / DSS / Webhook
└──────┬──────┘
       │ msgId 去重
       ▼
InboundMessage(msg_id, tid, event_time)
       │
       │ 只入队,不业务处理
       ▼
Queue: order_change
       │
       ├──► Worker: trade.fullinfo.get(tid)  ──► OrderSnapshot  ──► 状态机
       │
┌──────▼──────┐
│ 兜底定时器   │ trades.sold.increment.get(modified_start-10min, now)
└──────┬──────┘
       ▼
   fullinfo.get 拉漏的 tid
铁律:
  • 消息来了:入队 + 异步拉

  • 轮询来了:补可能漏的 tid

  • 两者都写同一张 order_snapshot 表

  • 谁后到、谁版本新,谁赢


六、Python:增量轮询器(官方坑全处理)

# top/order_incremental.py
from datetime import datetime, timedelta
import time

# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
class IncrementalOrderSyncer:
    def __init__(self, top_client, store, slide_back_sec=600):
        self.top = top_client
        self.store = store                  # 本地 last_modified 存储
        self.slide_back = slide_back_sec    # 平时10min,大促30min

    def _all_trade_types(self):
        # 不传全 = 漏单
        return [
            "fixed","auction","guarantee_trade","step","independent_simple_trade",
            "independent_shop_trade","au_trade","netcn_trade","external_trade",
            "instant_trade","b2c_cod","hotel_trade","super_market_trade",
            "super_market_cod_trade","taohua","waimai","o2o_offlinetrade",
            "nopaid","eticket","tmall_i18n","insurance_plus","finance",
            "pre_auth_type","lazada",
        ]

    def run_once(self, page_size=100):
        last = self.store.get_last_modified() or datetime.now() - timedelta(days=1)
        start = last - timedelta(seconds=self.slide_back)
        end = datetime.now()

        params = {
            "start_modified": start.strftime("%Y-%m-%d %H:%M:%S"),
            "end_modified": end.strftime("%Y-%m-%d %H:%M:%S"),
            "type": ",".join(self._all_trade_types()),
            "use_has_next": True,          # 避免 count(*)
            "page_no": 1,
            "page_size": page_size,
        }

        tids = []
        # 倒序返回:从后往前翻
        page = 1
        while True:
            params["page_no"] = page
            resp = self.top.execute("taobao.trades.sold.increment.get", params)
            body = resp.get("trades_sold_increment_get_response", {})
            result = body.get("trades", {})
            trades = result.get("trade", [])
            if not trades:
                break

            for t in trades:
                tids.append(str(t["tid"]))
                # 记录最大 modified 作为下次起点
                self.store.maybe_update_last_modified(t["modified"])

            if not result.get("has_next"):
                break
            page += 1

        # 再拉详情
        for tid in tids:
            self.pull_fullinfo(tid)

    def pull_fullinfo(self, tid: str):
        resp = self.top.execute("taobao.trade.fullinfo.get", {
            "tid": tid,
            "fields": (
                "tid,status,payment,modified,buyer_nick,receiver,"
                "orders,logistics,refund_infos,oaid,credit_card_fee"
            ),
        })
        trade = resp.get("trade_fullinfo_get_response", {}).get("trade")
        if trade:
            apply_order_snapshot(trade)

七、Python:消息接收端(快返回 + 去重)

# top/webhook_order.py
import time
from fastapi import FastAPI, Request, Response

app = FastAPI()

# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
@app.post("/webhook/taobao/order")
async def order_webhook(req: Request):
    raw = await req.body()
    # 1) 验签(TOP:md5(secret + k+v 排序 + secret) 或 top-sig,按你接入方式)
    if not verify_top_sign(raw, req.headers):
        return Response("invalid", status_code=401)

    payload = parse_top_message(raw)
    msg_id = payload.get("msgId") or payload.get("id")
    tid = str(payload.get("tid") or payload.get("orderId") or "")

    # 2) msgId 去重
    if inbound.exists(msg_id):
        return Response("ok", status_code=200)

    try:
        inbound.insert(msg_id, tid, payload, received_at=time.time())
    except DuplicateKeyError:
        return Response("ok", status_code=200)

    # 3) 只入队,不同步拉接口
    order_change_queue.publish({"tid": tid, "msg_id": msg_id})

    # 4) 必须快返回,别在 HTTP 线程里调 fullinfo.get
    return Response("ok", status_code=200)
消费者:
def consume_order_change():
    while True:
        job = order_change_queue.pop()
        try:
            pull_fullinfo(job["tid"])      # 消息只触发,fullinfo 才是真相
        except RetryableError:
            order_change_queue.requeue(job, backoff=2 ** job["retry"])
        except FatalError as e:
            dead_letter.save(job, str(e))

八、状态机:消息和轮询写同一张表

TERMINAL = {"TRADE_CLOSED", "TRADE_CLOSED_BY_TAOBAO", "TRADE_FINISHED"}

def apply_order_snapshot(trade: dict):
    tid = str(trade["tid"])
    cur = order_repo.get(tid)

    incoming_modified = parse_time(trade["modified"])
    incoming_version = int(incoming_modified.timestamp() * 1000)

    if cur is None:
        order_repo.insert(trade)
        emit_order_event(trade)
        return

    if incoming_version < cur.version:
        return  # 旧消息晚到,丢弃
    if incoming_version == cur.version and trade["status"] == cur.status:
        return  # 幂等

    if cur.status in TERMINAL and trade["status"] not in TERMINAL_TRANSITIONS[cur.status]:
        log.warn("rollback blocked", tid, cur.status, trade["status"])
        return

    order_repo.update(trade, version=incoming_version)
    emit_order_event(trade)

九、成本对比(日 3 万单变更)

方案
日调 fullinfo
塔内费用
塔外费用
实时性
漏单风险
每 30s 轮询
~86400 次
¥17.28
¥172.8
30s
低(前移不够仍漏)
每 5min 轮询
~8640 次
¥1.73
¥17.28
5min
中
消息 + 5min 兜底
~3000 次
¥0.6
¥6.0
秒级
低
DSS 推送 + 不轮询
0 次 API
¥36(3万单×0.12/百)
不可用
秒级
低(DSS 自身延迟)
结论:
单少 → 轮询最省心;单多 → 消息+DSS 更便宜;纯消息 → 大促必漏;纯轮询 → 塔外必破产。

十、大促额外坑

  • trade.fullinfo.get 大促前后可能延时,付款/物流信息晚 1 分钟

  • commission_fee 大促期间可能不返回

  • order.refund_id / special_refund_type 大促期间可能不返回

  • 增量窗口必须前移 30 分钟

  • 消息重试失败 → 必须有 5min 增量兜底


十一、落地检查表

  • [ ] 首次:trades.sold.get 分段拉 3 个月 + fullinfo

  • [ ] 日常:TMC/DSS 消息 → 入队 → fullinfo

  • [ ] 兜底:increment.get 每 1~5 min,窗口前移 10min(大促 30min)

  • [ ] increment 分页从后往前,type 传全

  • [ ] msgId 去重,tid+modified 推进状态

  • [ ] fullinfo 是真相源,消息/列表都不是

  • [ ] 塔外部署订单类接口 = 烧钱;敏感字段(姓名/手机/地址)走 OAID/密文

  • [ ] 大促前压测消息消费者,不是压测轮询


十二、一句话收口

淘宝订单同步的成熟度和“你会不会调 fullinfo”无关,
和这三件事有关:
消息只当闹钟,轮询只当保险,fullinfo 才当法官。
消息丢了轮询补,轮询慢了消息催,两者都落到 tid+status+modified 上求最终一致——
这才是淘宝 TOP 订单域的生产解法。


群贤毕至

访客
admin admin2026-10-12 11:00:05 · 回复 111