在订单物流查询场景中,系统通常需要根据快递公司编码和快递单号查询物流轨迹。本文结合项目中的 Kuaidi100Util 工具类,分享快递100 API 的封装方式,并重点说明缓存机制的作用。
快递100对同一个快递单号的查询频率有要求:
每一单查询频率至少间隔半小时以上,否则可能会造成锁单。
因此,在调用快递100接口前,需要先查询缓存,避免短时间内重复请求同一个快递单号。
一、工具类完整代码
java
package com.study.server;
import com.alibaba.fastjson.JSONObject;
import com.study.server.cache.Express100Cache;
import com.study.server.web.exception.BaseException;
import com.study.server.web.utils.MD5Util;
import com.study.server.web.utils.RequestSendUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* 快递100接口工具类
*
* 主要功能:
* 1. 根据快递公司编码、快递单号、手机号查询物流信息
* 2. 生成快递100接口签名
* 3. 调用快递100实时查询接口
* 4. 使用缓存控制同一单号的查询频率,避免锁单
*/
@Slf4j
@Component
public class Kuaidi100Util {
/**
* 快递100接口地址
*/
@Value("${express.kuaidi100.url:xxx}")
private String url;
/**
* 快递100分配的授权 key
*/
@Value("${express.kuaidi100.key:xxx}")
private String key;
/**
* 快递100分配的客户编号 customer
*/
@Value("${express.kuaidi100.customer:xxx}")
private String customer;
/**
* 获取快递物流信息
*
* 注意:
* 快递100要求同一快递单号查询频率至少间隔半小时以上,
* 否则可能会造成锁单。
* 因此这里先从缓存中读取,如果缓存存在,则直接返回缓存数据,
* 不再请求快递100接口。
*
* @param expressCompanyNumber 快递公司编码,例如:yuantong、shunfeng、zhongtong
* @param expressOrderNo 快递单号
* @param phone 收件人或寄件人手机号,部分快递公司必填
* @return 快递100返回的物流信息 JSON 字符串
*/
public String getExpressInfo(String expressCompanyNumber, String expressOrderNo, String phone) {
// 1. 先根据快递单号从缓存中获取物流信息
// 作用:避免同一个快递单号在半小时内重复请求快递100,防止锁单
String expressInfo = Express100Cache.get(expressOrderNo);
// 2. 如果缓存中已经存在物流信息,则直接返回缓存结果
if (!StringUtils.isEmpty(expressInfo)) {
return expressInfo;
}
// 3. 构造快递100接口外层请求参数
JSONObject paramObject = new JSONObject();
// customer 是快递100分配给客户的唯一编号
paramObject.put("customer", customer);
// 4. 构造快递100接口业务参数 param
JSONObject bodyObject = new JSONObject();
// 快递公司编码
bodyObject.put("com", expressCompanyNumber);
// 快递单号
bodyObject.put("num", expressOrderNo);
// 收件人或寄件人手机号
bodyObject.put("phone", phone);
try {
// 5. 生成接口签名
// 快递100签名规则:
// sign = MD5(param + key + customer).toUpperCase()
// 其中 param 为业务参数 JSON 字符串
String sign = MD5Util.upperCaseMD5Encode(bodyObject.toJSONString() + key + customer);
// 将签名放入外层请求参数
paramObject.put("sign", sign);
} catch (Exception e) {
// 6. 签名生成异常时,记录日志并抛出业务异常
log.error("快递100接口签名异常:{},异常信息:{}", paramObject.toJSONString(), e.getMessage());
throw new BaseException("快递100接口签名异常!");
}
// 7. 将业务参数 param 放入请求参数
paramObject.put("param", bodyObject);
// 8. 设置请求头
JSONObject headerObject = new JSONObject();
// 快递100接口使用 application/x-www-form-urlencoded 方式提交
headerObject.put("Content-type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType());
// 9. 发送 HTTP POST 请求
// 连接超时时间:3000ms
// 读取超时时间:3000ms
String response = RequestSendUtil.httpPost(
url + "/poll/query.do",
paramObject,
headerObject,
3000,
3000
);
// 10. 将快递100返回结果解析为 JSON
JSONObject responseJson = JSONObject.parseObject(response);
// 11. 判断接口是否返回异常编码
// 如果 returnCode 不为空,说明快递100接口返回了错误信息
String returnCode = responseJson.getString("returnCode");
if (!StringUtils.isEmpty(returnCode)) {
throw new BaseException("快递信息异常,代码:" + returnCode);
}
// 12. 将查询结果写入缓存
// 建议缓存时间设置为 30 分钟以上,例如 31 分钟或 35 分钟
// 作用:满足快递100"同一单号至少半小时查询一次"的限制
Express100Cache.set(expressOrderNo, response);
// 13. 返回快递100接口响应结果
return response;
}
}
二、缓存的核心作用
这里的缓存不是为了简单提升性能,而是为了满足快递100的接口限制:
同一快递单号查询频率至少间隔半小时以上,否则可能造成锁单。
所以代码中先执行:
java
String expressInfo = Express100Cache.get(expressOrderNo);
如果缓存中有数据:
java
if (!StringUtils.isEmpty(expressInfo)) {
return expressInfo;
}
就直接返回,不再调用快递100。
只有缓存不存在时,才真正请求接口:
java
String response = RequestSendUtil.httpPost(...);
请求成功后再写入缓存:
java
Express100Cache.set(expressOrderNo, response);
三、调用流程总结
text
开始查询物流
↓
根据快递单号查询缓存
↓
缓存存在?
↓ 是
直接返回缓存结果
↓ 否
组装请求参数
↓
生成 sign 签名
↓
调用快递100接口
↓
解析响应结果
↓
判断是否异常
↓
写入缓存
↓
返回物流信息
四、技术要点总结
这个工具类主要解决了几个问题:
| 技术点 | 说明 |
|---|---|
| 配置化 | url、key、customer 从配置文件读取 |
| 签名 | 按快递100规则生成 MD5 大写签名 |
| HTTP请求 | 使用 POST 请求调用 /poll/query.do |
| 异常处理 | 签名异常、接口异常统一抛出业务异常 |
| 缓存控制 | 防止同一单号半小时内重复查询 |
| 防锁单 | 满足快递100接口频率要求 |
五、总结
快递100 API 工具类的封装重点不只是"能调通接口",更重要的是:
通过缓存控制同一快递单号的查询频率,避免因频繁查询导致锁单。
最终实现效果:
- 减少第三方接口调用次数
- 提高物流查询响应速度
- 降低接口异常影响
- 避免同一单号短时间重复查询
- 满足快递100半小时查询频率要求