电商订单系统集成物流查询:批量轮询与自动签收
2026-07-26 · 阅读约 7 分钟
做电商,发货之后最常见的需求就是让订单状态跟着物流自动走:包裹到哪了、有没有签收,
都不该靠客服一单一单手动查。本文讲清怎么把物流查询接口接进你的订单系统 ——
从订单表怎么存运单信息,到定时批量轮询,再到isDelivered为真时自动把订单置为「已签收」。
重点是一套能省额度、又不漏更新的设计。
一、整体流程
集成的核心是一条闭环:发货写入运单 → 定时批量查询未完成订单 → 更新状态 → 已签收则收尾。 用一张图理清各环节:
下单 → 发货(写入 courierCode + trackingNumber)
│
┌─────────────┴─────────────┐
│ 定时任务(如每 30 分钟) │
│ 1. 取出「未签收」订单 │
│ 2. 每 10 条一批调接口 │
│ 3. 写回最新状态 │
│ 4. isDelivered → 已签收 │
└───────────────────────────┘ 二、订单表怎么存运单信息
发货环节就要把两样东西写进订单:快递公司编码和运单号。
编码就是接口用的 courierCode(如 sf、zto),
千万别只存一个中文快递公司名 —— 查询时要传的是编码,不是名字。建议的字段:
| 字段 | 示例值 | 说明 |
|---|---|---|
courierCode | sf | 快递公司编码,查询必传 |
trackingNumber | SF9900001234567 | 运单号(示例为合成号码) |
deliveryStatus | IN_TRANSIT | 缓存的最新统一状态码 |
isDelivered | false | 是否已签收,轮询过滤全靠它 |
lastCheckedAt | 2026-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:成功就更新缓存的状态,失败则记日志、下次再试。
关键在最后一步 —— 当 isDelivered 为 true,直接把订单收尾为「已签收」,
并顺手记下 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 数组一起存下即可(每条含 dateTime、status、
statusCode、location)。
六、轮询周期与省额度
几条实践建议,直接决定你月底用掉多少额度:
- 周期别太密:物流状态变化慢,30 分钟到几小时一轮足矣,凌晨还能再放宽。
- 已签收即止:靠
isDelivered过滤,终态订单一律不再查。 - 失败分类:
NOT_FOUND是查到了但暂无轨迹(会计费),别几秒就重试;TRACKING_FAILED才适合退避重试。 - 用足缓存:成功结果里带
cache.fromCache与cache.cachedAt, 几十分钟内的重复查询本就命中服务端缓存。
按这套设计,就算有上万条在途订单,每轮也只查真正「还在路上」的那部分, 免费额度每月 10000 次在多数中小店铺场景里绰绰有余。
七、小结
- 发货时把
courierCode和运单号一起写进订单,查询才有据可依。 - 定时任务只取「未签收」订单,每 10 条一批查,是省额度的核心。
isDelivered为真即自动收尾,订单状态与物流自动同步。
错误码逐一说明见快递批量查询与错误处理; 状态码流转见物流状态码详解; 完整字段见接口文档。还没有密钥? 注册即可,邮箱注册、无需企业认证,也可先用首页在线演示试跑。