最近项目中需要用到查询快递信息的功能,像各个快递公司查询快递一样,显示时间节点和地点,即路由信息(轨迹)。最简单省事的方法自然是云市场买接口次数,但本着能白嫖就白嫖的原则,自然是要使用免费的。

一、准备工作

1. 进入[顺丰开放平台](https://open.sf-express.com/)注册账号并登录。
2. 进入控制台——开发者对接——认证(**第一次使用需要认证**,个人认证的话只需要身份证即可)
3. 认证完成后进入控制台——开发者对接——新建应用,新建后点击关联API,选择路由查询接口
   ![image-20240723105152887](https://upyun.huochairener-blog.cn/hexo/articlePicture/202408011633032.png)

4. 保存**顾客编码**、**沙箱校验码**、**生产校验码**,后续会使用,若忘了保存可在开发者对接中点击应用详情查看

二、测试

1. 进入沙箱工具——API测试工具,选择**沙箱环境**,输入**沙箱校验码**,选择**API名称**(即刚刚关联的路由API)![image-20240723105320476](https://upyun.huochairener-blog.cn/hexo/articlePicture/202408011633033.png)

2. 提交测试
3. 滚动屏幕,下面有API请求结果,出现以下结果,说明测试成功
   ![image-20240723105329061](https://upyun.huochairener-blog.cn/hexo/articlePicture/202408011633034.png)

4. 回到顶部,点击接口文档,找到[路由查询接口接口-速运类API](https://open.sf-express.com/Api/ApiDetails?level3=397&interName=%E8%B7%AF%E7%94%B1%E6%9F%A5%E8%AF%A2%E6%8E%A5%E5%8F%A3-EXP_RECE_SEARCH_ROUTES)
5. 根据这些参数,我们来组装POST请求,可以使用SDK也可不使用

非SDK方式

通过HuTool工具类发送请求,其中的ExpressConstants是自己建的常量类,里面定义了顺丰的顾客编码、校验码之类的。若是只是在自己的项目中使用顺丰,则没必要另外建了,直接用字符串即可。
若不使用HuTool工具类也可,根据以下代码自行修改
沙箱环境地址和生产环境地址在API-SDK的开发文档

1
2
3
4
//沙箱环境的地址
private static final String CALL_URL_BOX = "https://sfapi-sbox.sfexpress.com/std/service";
//生产环境的地址
private static final String CALL_URL_PROD = "https://sfapi.sfexpress.com/std/service";

引入HuTool工具类

1
2
3
4
5
6
<!--hutool工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.11</version>
</dependency>

编写实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import cn.hutool.core.lang.UUID;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import com.erp.admin.service.ExpresService;
import com.erp.common.constant.ExpressConstants;
import com.erp.common.core.domain.AjaxResult;
import com.erp.common.exception.ServiceException;
import com.erp.common.utils.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Service
public class ExpresServiceImpl implements ExpresService {
/**
* 查询物流路由信息
* @param expressNum 运单号
* @param phoneNumber 手机号后四位
* @return
* @throws IOException
* @throws GeneralSecurityException
*/
@Override
public AjaxResult queryRoutes(String expressNum,String phoneNumber) throws IOException, GeneralSecurityException {
if (expressNum == null || expressNum.isEmpty() || phoneNumber== null || phoneNumber.isEmpty()){
throw new ServiceException("运单号/手机号不能为空!");
}
String result;
//顺丰运单号正则
String sfRegex = ExpressConstants.SF_CODE_RULE;
//设置正则不区分大小写
Pattern sfPattern = Pattern.compile(sfRegex, Pattern.CASE_INSENSITIVE);
Matcher sfMatcher = sfPattern.matcher(expressNum);
if(sfMatcher.matches()){
result = querySFExpress(expressNum, phoneNumber);
//返回参数中,routes为空,则运单号不正确
Map routeRespsMap = (Map) JSONObject.parseObject(result).getJSONArray("routeResps").get(0);
List<Map> routesList = (List<Map>)routeRespsMap.get("routes");
if (routesList == null || routesList.isEmpty()){
throw new ServiceException("运单号或手机号不正确!");
}
//按照时间逆序排列 顺丰返回的是按时间顺序排列的,而一般显示路由信息是逆序排列
routesList.sort(Comparator.comparing(route -> String.valueOf(route.getOrDefault("acceptTime", 0).toString()), Comparator.reverseOrder()));
return AjaxResult.success("SF",routesList);
}else {
throw new ServiceException("运单号或手机号不正确!");
}
}

/**
* 查询顺丰物流路由信息
*/
private String querySFExpress(String expressNum,String phoneNumber) throws NoSuchAlgorithmException {
String timeStamp = DateUtils.parseDateToStr("yyyyMMddHHmmssSSS",new Date());
JSONObject msgDataJson = new JSONObject();
msgDataJson.put("language","0");
msgDataJson.put("trackingType","1");
msgDataJson.put("trackingNumber",expressNum);
msgDataJson.put("methodType","1");
msgDataJson.put("checkPhoneNo",phoneNumber);
String msgData = JSONUtil.toJsonStr(msgDataJson);
//md5加密摘要
String encryptStr;
try {
encryptStr = URLEncoder.encode(msgData + timeStamp + ExpressConstants.SF_CHECK_WORD_PROD, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(encryptStr.getBytes(StandardCharsets.UTF_8));
String msgDigest = (new Base64()).encodeAsString(md5.digest());

//创建Http POST请求对象
HttpRequest get = HttpUtil.createPost(ExpressConstants.SF_CALL_URL_BOX);
Map<String,String> headers = new HashMap<>();
headers.put("appCode", ExpressConstants.SF_CLIENT_CODE);
headers.put("timestamp", timeStamp);
headers.put("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
//设置请求头
get.addHeaders(headers);
//设置参数
get.form("partnerID",ExpressConstants.SF_CLIENT_CODE)
.form("requestID", UUID.randomUUID().toString().replace("-", ""))
.form("serviceCode", "EXP_RECE_SEARCH_ROUTES")
.form("timestamp", timeStamp)
.form("msgData", msgData)
.form("msgDigest",msgDigest);
String body = get.execute().body();

JSONObject msgDataResult = JSONObject.parseObject(body)
.getJSONObject("apiResultData")
.getJSONObject("msgData");
return msgDataResult.toString();
}
}

常量类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 查询物流路由常量
*/
public class ExpressConstants
{
/**顺丰顾客编码*/
public static final String SF_CLIENT_CODE = "******";
/**顺丰生产校验码*/
public static final String SF_CHECK_WORD_PROD = "**********";
/**顺丰沙箱校验码*/
public static final String SF_CHECK_WORD_DEV = "***********";
/**顺丰沙箱环境的地址 -PRO*/
public static final String SF_CALL_URL_BOX = "*********************";
/**顺丰生产环境的地址 -PRO*/
public static final String SF_CALL_URL_PROD = "********************";
/**顺丰运单号正则表达式*/
public static final String SF_CODE_RULE = "^[A-Za-z0-9-]{4,35}$";
}

SDK方式

1. 进入[丰桥API-SDK(JAVA)使用说明](https://open.sf-express.com/developSupport/976720?activeIndex=399112)
2. 下载SDK
3. 根据PDF文档编写代码![image-20240723105339459](https://upyun.huochairener-blog.cn/hexo/articlePicture/202408011633035.png)
4. 主要问题是jar包不好引入,我是用的是idea,每次引入完之后刷新maven就需要重新引入,打包时也打不进去,网上方法找遍了,都不好使,只能[手动将外部jar包引入本地仓库](https://blog.csdn.net/qq_40093289/article/details/110822523)

三、上线

API需要先完成接口配置后才可以联调测试,API在沙箱环境7天内成功调用三次以上才可申请上线

在沙箱调用成功三次之后就可以点击开发者对接—查看API,点击后面的上线,即可完成上线。

四、Tips

  • 顺丰API可以查询任何人的快递信息,另外有调用次数,生产环境500000次/日,测试环境2000次/日
  • 手机号是收件人或寄件人的后四位,两个手机号都可以查到
  • 若公司有月结卡号的话,可以绑定月结卡号,这样就只需要物流单号就可以查询了,但必须是用月结下的单才可以。