最近项目中需要用到查询快递信息的功能,像各个快递公司查询快递一样,显示时间节点和地点,即路由信息(轨迹)。最简单省事的方法自然是云市场买接口次数,但本着能白嫖就白嫖的原则,自然是要使用免费的。
一、准备工作
-
进入顺丰开放平台注册账号并登录。
-
进入控制台——开发者对接——认证(第一次使用需要认证,个人认证的话只需要身份证即可)
-
认证完成后进入控制台——开发者对接——新建应用,新建后点击关联API,选择路由查询接口
-
保存顾客编码、沙箱校验码、生产校验码,后续会使用,若忘了保存可在开发者对接中点击应用详情查看
二、测试
-
进入沙箱工具——API测试工具,选择沙箱环境,输入沙箱校验码,选择API名称(即刚刚关联的路由API)
-
提交测试
-
滚动屏幕,下面有API请求结果,出现以下结果,说明测试成功
-
回到顶部,点击接口文档,找到路由查询接口接口-速运类API
-
根据这些参数,我们来组装POST请求,可以使用SDK也可不使用
非SDK方式
通过HuTool工具类发送请求,其中的ExpressConstants是自己建的常量类,里面定义了顺丰的顾客编码、校验码之类的。若是只是在自己的项目中使用顺丰,则没必要另外建了,直接用字符串即可。
若不使用HuTool工具类也可,根据以下代码自行修改
沙箱环境地址和生产环境地址在API-SDK的开发文档
//沙箱环境的地址
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工具类
<!--hutool工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.11</version>
</dependency>
编写实现类
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();
}
}
常量类
/**
* 查询物流路由常量
*/
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方式
- 进入丰桥API-SDK(JAVA)使用说明
- 下载SDK
- 根据PDF文档编写代码
- 主要问题是jar包不好引入,我是用的是idea,每次引入完之后刷新maven就需要重新引入,打包时也打不进去,网上方法找遍了,都不好使,只能手动将外部jar包引入本地仓库
三、上线
API需要先完成接口配置后才可以联调测试,API在沙箱环境7天内成功调用三次以上才可申请上线
在沙箱调用成功三次之后就可以点击开发者对接—查看API,点击后面的上线,即可完成上线。
四、Tips
- 顺丰API可以查询任何人的快递信息,另外有调用次数,生产环境500000次/日,测试环境2000次/日
- 手机号是收件人或寄件人的后四位,两个手机号都可以查到
- 若公司有月结卡号的话,可以绑定月结卡号,这样就只需要物流单号就可以查询了,但必须是用月结下的单才可以。