快递查询API

← 技术博客

电商订单系统集成物流查询:批量轮询与自动签收

2026-07-26 · 阅读约 7 分钟

做电商,发货之后最常见的需求就是让订单状态跟着物流自动走:包裹到哪了、有没有签收, 都不该靠客服一单一单手动查。本文讲清怎么把物流查询接口接进你的订单系统 —— 从订单表怎么存运单信息,到定时批量轮询,再到isDelivered为真时自动把订单置为「已签收」。 重点是一套能省额度、又不漏更新的设计。

一、整体流程

集成的核心是一条闭环:发货写入运单 → 定时批量查询未完成订单 → 更新状态 → 已签收则收尾。 用一张图理清各环节:

下单 → 发货(写入 courierCode + trackingNumber)
                    │
      ┌─────────────┴─────────────┐
      │  定时任务(如每 30 分钟)  │
      │  1. 取出「未签收」订单     │
      │  2. 每 10 条一批调接口     │
      │  3. 写回最新状态          │
      │  4. isDelivered → 已签收  │
      └───────────────────────────┘

二、订单表怎么存运单信息

发货环节就要把两样东西写进订单:快递公司编码运单号。 编码就是接口用的 courierCode(如 sfzto), 千万别只存一个中文快递公司名 —— 查询时要传的是编码,不是名字。建议的字段:

字段示例值说明
courierCodesf快递公司编码,查询必传
trackingNumberSF9900001234567运单号(示例为合成号码)
deliveryStatusIN_TRANSIT缓存的最新统一状态码
isDeliveredfalse是否已签收,轮询过滤全靠它
lastCheckedAt2026-07-26 10:00上次查询时间,用于控制频率

三、只查未完成的订单

这是最重要的省钱原则:已签收的订单不要再查。物流查询按查询条数计费, 对着一堆早就签收的订单反复调接口,是纯粹的浪费。定时任务开头先做过滤:

// 伪代码:挑出需要更新的订单
const pending = orders.filter(o =>
  o.trackingNumber && !o.isDelivered   // 有单号、且还没签收
);

进一步还能按 lastCheckedAt 再筛一层:距上次查询不足半小时的就先跳过, 因为物流状态几十分钟才变一次,查太勤既费额度又没有新信息。

四、每 10 条一批调接口

查询接口单次 items 最多放 10 个运单号,所以要把 pending 切成每批 10 条。把订单映射成 items 时,一定要带上各自的 courierCode

async function chaxunBatch(batch) {
  const items = batch.map(o => ({
    courierCode: o.courierCode,
    trackingNumber: o.trackingNumber,
  }));
  const res = await fetch('https://api.kuaidichaxunapi.com/v1/tracking/trace', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + process.env.KUAIDI_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ items }),
  });
  const { data } = await res.json();
  return data.results;
}

for (const batch of chunk(pending, 10)) {
  const results = await chaxunBatch(batch);
  for (const r of results) applyResult(r);
  await sleep(300);   // 批之间稍作停顿,避免触发限频
}

五、写回状态与自动签收

每条结果先看 success:成功就更新缓存的状态,失败则记日志、下次再试。 关键在最后一步 —— 当 isDeliveredtrue,直接把订单收尾为「已签收」, 并顺手记下 dateDelivered 作为签收时间:

function applyResult(r) {
  if (!r.success) { logError(r.error); return; }
  const d = r.data;
  updateOrder(d.trackingNumber, {
    deliveryStatus: d.deliveryStatus,          // 统一状态码
    statusText: d.deliveryStatusText,          // 可直接展示的中文
    lastProgressAt: d.dateLastProgress,        // 最新一条轨迹时间
    lastCheckedAt: now(),
  });
  if (d.isDelivered) {
    markDelivered(d.trackingNumber, d.dateDelivered);  // 自动置为「已签收」
  }
}

订单一旦被 markDelivered,第三节的过滤就会把它排除在外, 下次轮询自然不再查它,闭环就此完成。想给用户展示完整轨迹的, 把返回的 progresses 数组一起存下即可(每条含 dateTimestatusstatusCodelocation)。

六、轮询周期与省额度

几条实践建议,直接决定你月底用掉多少额度:

  • 周期别太密:物流状态变化慢,30 分钟到几小时一轮足矣,凌晨还能再放宽。
  • 已签收即止:靠 isDelivered 过滤,终态订单一律不再查。
  • 失败分类NOT_FOUND 是查到了但暂无轨迹(会计费),别几秒就重试; TRACKING_FAILED 才适合退避重试。
  • 用足缓存:成功结果里带 cache.fromCachecache.cachedAt, 几十分钟内的重复查询本就命中服务端缓存。

按这套设计,就算有上万条在途订单,每轮也只查真正「还在路上」的那部分, 免费额度每月 10000 次在多数中小店铺场景里绰绰有余。

七、小结

  • 发货时把 courierCode 和运单号一起写进订单,查询才有据可依。
  • 定时任务只取「未签收」订单,每 10 条一批查,是省额度的核心。
  • isDelivered 为真即自动收尾,订单状态与物流自动同步。

错误码逐一说明见快递批量查询与错误处理; 状态码流转见物流状态码详解; 完整字段见接口文档。还没有密钥? 注册即可,邮箱注册、无需企业认证,也可先用首页在线演示试跑。