快递查询API

← 技术博客

物流轨迹数据结构详解:progresses 数组怎么用

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

查一次快递,返回的不只是一个「当前到哪了」,还有一整条物流轨迹 —— 也就是 progresses 数组。很多人只取了 deliveryStatus 就收工, 白白浪费了这条能拼出完整时间线的数据。本文只讲 progresses数据结构和用法: 每个字段是什么、怎么排序、怎么渲染成时间线、怎么入库。至于 12 种状态码各自的含义, 由状态码详解那篇负责,这里不重复。

一、progresses 在返回里的位置

每条成功的查询结果 results[i].data 里,除了运单级别的字段,还带一个 progresses 数组。下面是一条运输中包裹的精简示例:

{
  "trackingNumber": "CGD0000123456",
  "deliveryStatus": "IN_TRANSIT",
  "deliveryStatusText": "运输中",
  "isDelivered": false,
  "dateDelivered": null,
  "dateLastProgress": "2026-07-25 18:02:11",
  "progresses": [
    {
      "dateTime": "2026-07-25 18:02:11",
      "location": "深圳转运中心",
      "status": "运输中",
      "statusCode": "IN_TRANSIT",
      "description": "快件已从深圳转运中心发出"
    },
    {
      "dateTime": "2026-07-24 09:15:40",
      "location": "广州集散中心",
      "status": "已揽收",
      "statusCode": "PICKED_UP",
      "description": null
    }
  ]
}

二、每条轨迹的五个字段

progresses 里每一项代表一个物流节点,共五个字段。其中 locationdescription 可能缺失(值为 null 或不出现),渲染前务必判空。

字段类型含义
dateTime字符串该节点发生时间,格式固定为 yyyy-MM-dd HH:mm:ss
location字符串 / 空节点发生地点,如「深圳转运中心」;部分节点没有地点
status字符串归一后的简洁中文描述,可直接展示
statusCode字符串12 种统一状态码之一,用于程序判断(含义见状态码详解
description字符串 / 空承运商原文附加说明,比 status 更细;可能为空

一句话分工:想展示给人看就用 status / location / description, 想让程序判断就用 statusCode。两者一一对应,互不干扰。

三、排序:最新在最前,progresses[0] 就是当前状态

接口已经把 progresses 按时间倒序排好 —— 最新的节点在数组开头。 所以你不需要自己再排序,也不用比较时间戳找「最新那条」:

const d = json.data.results[0].data;
// progresses[0] 就是最新节点,与 deliveryStatus 指向同一状态
const zuixin = d.progresses[0];
console.log(zuixin.statusCode);   // 等同于 d.deliveryStatus
console.log(zuixin.dateTime);     // 等同于 d.dateLastProgress

换句话说:progresses[0].statusCode 与运单级的 deliveryStatus 一致, progresses[0].dateTimedateLastProgress 一致。 只想显示「当前状态」用运单级字段更省事;要展示完整历程才遍历整个数组。

四、渲染成时间线 UI

既然数组已经倒序,渲染时间线就是「从头到尾遍历、逐条追加」。下面用原生 DOM 把它塞进一个 <ul id="guiji">,注意对可能为空的 location 做了判空:

// 后端已按时间倒序返回,直接遍历即可,无需再排序
const ul = document.getElementById('guiji');
for (const p of d.progresses) {
  const li = document.createElement('li');
  // location 可能为空,用 || 兜底避免出现 "undefined"
  const difang = p.location ? ' · ' + p.location : '';
  li.textContent = p.dateTime + difang + ' ' + p.status;
  ul.appendChild(li);
}

想做得更精致,可以把最新一条(index === 0)高亮为「当前状态」,其余作为历史节点弱化显示; description 存在时作为二级说明补在 status 下方。这些都是纯展示逻辑,数据层面无需额外处理。

五、配合 isDelivered 与时间字段

轨迹数组之外,运单级还有三个字段和它配合得很好:

字段用途
isDelivered布尔值。为 true 即已签收,可停止轮询、归档订单
dateDelivered签收时间;未签收时为空
dateLastProgress最新一条轨迹的时间,等于 progresses[0].dateTime

一个常用场景是判断包裹「多久没动了」:拿当前时间减去 dateLastProgress, 超过某个阈值(比如 48 小时)就标记为「疑似停滞」提醒人工介入 —— 全程不用翻 progresses, 直接读这个字段即可。

六、存进数据库的建议

把轨迹落库时,推荐拆成两张表,避免每次更新都重写整条数组:

  • 运单主表:一条运单一行,存 trackingNumbercourierCodedeliveryStatusisDelivereddateDelivereddateLastProgress。 查列表、做筛选都靠它。
  • 轨迹明细表:一个节点一行,存 dateTimelocationstatusstatusCodedescription,外加所属运单号。展示详情时按 dateTime 倒序取出即可。

追加新节点时会遇到重复:同一单再次查询,旧节点还会再返回一遍。用 trackingNumber + dateTime + statusCode 作为去重键,只插入新出现的节点,就不会写重。 isDeliveredtrue 后这单已是终态,可以停止再查,省额度也省写入。

七、小结

  • progresses 已按时间倒序,progresses[0] 就是当前状态,不用自己排序。
  • locationdescription 可能为空,渲染与入库都要判空。
  • 展示用 status / location,判断用 statusCode;是否结束看 isDelivered
  • 落库拆「运单主表 + 轨迹明细表」,用三字段组合键去重。

每个 statusCode 的具体含义与流转见物流状态码详解; 完整字段清单见接口文档。 还没有密钥就先注册(邮箱即可,无需企业认证), 或用首页在线演示跑一遍看看真实的 progresses 长什么样。