电商型官网订单发货后,用户最关心的就是"我的包裹到哪了"。如果没有物流查询功能,客服会被"查快递"的咨询淹没。接入物流查询接口后,用户在订单详情页就能自助查看包裹轨迹,大幅降低客服压力。国内常用的聚合物流平台有快递鸟、快递100,它们统一对接了顺丰、中通、圆通等几百家快递公司,用一个接口搞定所有查询。本篇讲清集成要点。
一、快递公司编码与即时查询
物流查询接口需要两个参数:快递单号和快递公司编码。每家快递公司有固定编码(如顺丰 SF、中通 ZTO、圆通 YTO),用户输入单号后往往不知道是哪家公司,所以需要"单号识别"——接口根据单号前缀自动判断快递公司。识别不准时再让用户手动选择。尧图的做法是:先自动识别,识别失败弹出下拉选择框。
// 物流轨迹即时查询
async function queryLogistics(trackNo, shipperCode) {
// 1. 若未提供快递公司,先自动识别
if (!shipperCode) {
const recog = await kuaidiBird.recognize(trackNo);
if (!recog.success || recog.shippers.length === 0) {
return { needManual: true }; // 需用户手动选择
}
shipperCode = recog.shippers[0].ShipperCode;
}
// 2. 先查缓存(物流轨迹变化不频繁,缓存10分钟)
const cacheKey = `logistics:${shipperCode}:${trackNo}`;
let traces = await redis.get(cacheKey);
if (traces) return JSON.parse(traces);
// 3. 调用物流接口查询
const result = await kuaidiBird.getOrderTraces({
ShipperCode: shipperCode,
LogisticCode: trackNo
});
if (result.Success) {
traces = result.Traces.reverse(); // 最新轨迹在前
await redis.set(cacheKey, JSON.stringify(traces), 'EX', 600);
return traces;
}
return fail('查询失败:' + result.Reason);
}
轨迹数据通常按时间正序返回(最早在前),但用户更关心最新状态,所以前端展示时要反转。缓存策略很关键——物流轨迹不会每秒变化,缓存 10 分钟能挡住 90% 的重复查询,避免频繁调用接口扣费。订单签收后轨迹不再变化,可以永久缓存。
二、订阅推送与状态更新
即时查询适合用户主动查看,但有些场景需要系统主动感知物流变化——比如包裹签收后自动发通知、派件中自动提醒取件。这时要用"订阅推送":发货时向物流平台订阅该单号,平台在物流状态变化时主动回调你的接口推送最新轨迹。这样不用反复轮询查询,实时性更高、成本更低。
// 发货时订阅物流推送
async function shipOrder(orderId, trackNo, shipperCode) {
// 1. 更新订单为已发货
await db.update('orders', {
id: orderId,
status: 'shipped',
track_no: trackNo,
shipper_code: shipperCode,
ship_time: now()
});
// 2. 订阅物流推送
const subResult = await kuaidiBird.subscribe({
ShipperCode: shipperCode,
LogisticCode: trackNo,
CallbackUrl: 'https://ldpk.cn/api/logistics/callback'
});
if (!subResult.Success) {
log.error('物流订阅失败', trackNo, subResult.Reason);
}
}
// 物流推送回调处理
app.post('/api/logistics/callback', (req, res) => {
const data = JSON.parse(req.body.RequestData);
for (const item of data.Data) {
const { LogisticCode, ShipperCode, Traces, State } = item;
// 1. 更新本地缓存的轨迹
redis.set(`logistics:${ShipperCode}:${LogisticCode}`,
JSON.stringify(Traces.reverse()), 'EX', 86400);
// 2. 根据状态触发业务逻辑
if (State === '3') { // 已签收
onDelivered(LogisticCode);
} else if (State === '4') { // 派件中
onDispatching(LogisticCode);
}
}
res.json({ Success: true }); // 必须返回成功,否则平台会重试
});
回调处理同样要保证幂等——物流平台可能对同一条轨迹重复推送多次。尧图用轨迹的"时间+内容"做去重键,已处理过的推送直接返回成功。状态码要映射成业务语义:0 在途、1 揽收、2 困难、3 签收、4 派件、5 退回,不同平台编码可能略有差异,对接时要核对文档。
三、轨迹展示与异常处理
前端展示物流轨迹要兼顾信息完整与视觉清晰。尧图常用"时间轴"样式:最新状态高亮置顶,历史轨迹按时间倒序排列,每条轨迹显示时间、地点、动作描述。签收状态用绿色标记,异常状态(退回、滞留)用红色提醒。
<!-- 物流轨迹时间轴 -->
<div class="logistics-timeline">
<?php foreach ($traces as $i => $trace): ?>
<div class="trace-item <?= $i === 0 ? 'latest' : '' ?>">
<div class="trace-dot"></div>
<div class="trace-content">
<p class="trace-time"><?= $trace['AcceptTime'] ?></p>
<p class="trace-desc"><?= $trace['AcceptStation'] ?></p>
</div>
</div>
<?php endforeach; ?>
</div>
<style>
.logistics-timeline{padding:16px 0}
.trace-item{display:flex;gap:16px;padding:12px 0;position:relative}
.trace-item::before{content:'';position:absolute;left:7px;top:24px;bottom:-12px;width:2px;background:var(--line)}
.trace-item:last-child::before{display:none}
.trace-dot{width:14px;height:14px;border-radius:50%;background:var(--line);flex-shrink:0;margin-top:4px}
.trace-item.latest .trace-dot{background:var(--vermilion);box-shadow:0 0 0 4px rgba(184,58,62,.15)}
.trace-time{font-size:13px;color:var(--muted);margin-bottom:4px}
.trace-desc{font-size:14px;color:var(--ink);line-height:1.6}
.trace-item.latest .trace-desc{color:var(--vermilion);font-weight:600}
</style>
异常处理要覆盖几种情况:单号错误(接口返回"无此单号")、快递公司选错(轨迹为空或与预期不符)、接口超时(降级返回缓存数据并提示"查询繁忙")。尧图建议给每笔订单存一个"物流异常标记",连续 3 天无轨迹更新自动告警,客服主动跟进——这比等用户投诉才发现问题体面得多。物流查询看似小功能,做扎实了能显著提升电商官网的用户体验与复购率。