2025-08-08 16:19:11 +08:00
|
|
|
|
package com.gear.oa.utils;
|
|
|
|
|
|
|
|
|
|
|
|
import com.alibaba.fastjson2.JSON;
|
|
|
|
|
|
import com.alibaba.fastjson2.JSONArray;
|
|
|
|
|
|
import com.alibaba.fastjson2.JSONObject;
|
|
|
|
|
|
import com.gear.oa.domain.vo.OaExpressVo;
|
|
|
|
|
|
import org.apache.commons.codec.binary.Base64;
|
|
|
|
|
|
import org.apache.commons.codec.digest.DigestUtils;
|
|
|
|
|
|
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
|
|
import java.io.InputStreamReader;
|
|
|
|
|
|
import java.io.OutputStream;
|
|
|
|
|
|
import java.net.HttpURLConnection;
|
|
|
|
|
|
import java.net.URL;
|
|
|
|
|
|
import java.net.URLEncoder;
|
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
|
import java.text.SimpleDateFormat;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
|
|
|
|
public class StoRouteQueryUtil {
|
|
|
|
|
|
// 请填写你自己的key/secret
|
|
|
|
|
|
private static final String APP_KEY = "CAKDSqybdwbzlUC";
|
|
|
|
|
|
private static final String APP_SECRET = "wGRMVXwIf8sp4m4W6tWJcOaDfY7EPblQ";
|
|
|
|
|
|
private static final String FROM_CODE = "CAKDSqybdwbzlUC"; // 你的from_code
|
|
|
|
|
|
private static final String API_URL = "https://cloudinter-linkgateway.sto.cn/gateway/link.do";
|
|
|
|
|
|
|
|
|
|
|
|
// 申通签名算法 body+appSecret,MD5后Base64
|
|
|
|
|
|
public static String sign(String body, String secret) {
|
|
|
|
|
|
String text = body + secret;
|
|
|
|
|
|
byte[] md5 = DigestUtils.md5(text);
|
|
|
|
|
|
return Base64.encodeBase64String(md5);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询申通轨迹,支持批量单号
|
|
|
|
|
|
* @param waybillNos 运单号集合
|
|
|
|
|
|
* @return 查询结果JSON
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static String queryRoute(List<String> waybillNos) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 构造content参数
|
|
|
|
|
|
JSONObject content = new JSONObject();
|
|
|
|
|
|
content.put("order", "asc");
|
|
|
|
|
|
JSONArray waybillNoList = new JSONArray();
|
|
|
|
|
|
for (String no : waybillNos) waybillNoList.add(no);
|
|
|
|
|
|
content.put("waybillNoList", waybillNoList);
|
|
|
|
|
|
String contentStr = content.toJSONString();
|
|
|
|
|
|
|
|
|
|
|
|
// 构造表单参数字符串
|
|
|
|
|
|
StringBuilder form = new StringBuilder();
|
|
|
|
|
|
form.append("api_name=STO_TRACE_QUERY_COMMON");
|
|
|
|
|
|
form.append("&content=").append(URLEncoder.encode(contentStr, "UTF-8"));
|
|
|
|
|
|
form.append("&from_appkey=").append(APP_KEY);
|
|
|
|
|
|
form.append("&from_code=").append(APP_KEY);
|
|
|
|
|
|
form.append("&to_appkey=sto_trace_query");
|
|
|
|
|
|
form.append("&to_code=sto_trace_query");
|
|
|
|
|
|
form.append("&data_digest=").append(URLEncoder.encode(sign(contentStr, APP_SECRET), "UTF-8"));
|
|
|
|
|
|
|
|
|
|
|
|
System.out.println("申通表单请求体:" + form);
|
|
|
|
|
|
|
|
|
|
|
|
URL url = new URL(API_URL);
|
|
|
|
|
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
|
|
|
|
conn.setDoOutput(true);
|
|
|
|
|
|
conn.setDoInput(true);
|
|
|
|
|
|
conn.setUseCaches(false);
|
|
|
|
|
|
conn.setRequestMethod("POST");
|
|
|
|
|
|
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
|
|
|
|
|
|
OutputStream out = conn.getOutputStream();
|
|
|
|
|
|
out.write(form.toString().getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
|
out.flush();
|
|
|
|
|
|
out.close();
|
|
|
|
|
|
StringBuilder sbResult = new StringBuilder();
|
|
|
|
|
|
if (200 == conn.getResponseCode()) {
|
|
|
|
|
|
BufferedReader responseReader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
|
|
|
|
|
|
String readLine;
|
|
|
|
|
|
while ((readLine = responseReader.readLine()) != null) {
|
|
|
|
|
|
sbResult.append(readLine).append("\n");
|
|
|
|
|
|
}
|
|
|
|
|
|
responseReader.close();
|
|
|
|
|
|
}
|
|
|
|
|
|
return sbResult.toString();
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 解析申通返回数据,转为OaExpressVo列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static OaExpressVo parseData(String result) {
|
|
|
|
|
|
if (result == null || result.trim().isEmpty()) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 新增:如果返回是XML,直接打印并返回null
|
|
|
|
|
|
if (result.trim().startsWith("<")) {
|
|
|
|
|
|
System.err.println("申通返回XML错误:" + result);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
JSONObject json = JSON.parseObject(result);
|
|
|
|
|
|
JSONObject data = json.getJSONObject("data");
|
|
|
|
|
|
if (data == null) return null;
|
|
|
|
|
|
for (String waybillNo : data.keySet()) {
|
|
|
|
|
|
JSONArray arr = data.getJSONArray(waybillNo);
|
|
|
|
|
|
if (arr == null || arr.isEmpty()) continue;
|
|
|
|
|
|
JSONObject last = arr.getJSONObject(arr.size() - 1);
|
|
|
|
|
|
OaExpressVo vo = new OaExpressVo();
|
|
|
|
|
|
vo.setExpressCode(last.getString("waybillNo"));
|
|
|
|
|
|
// 设置时间
|
|
|
|
|
|
String opTime = last.getString("opTime");
|
|
|
|
|
|
if (opTime != null) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 解析时间格式 "2025-07-27 13:01:00"
|
|
|
|
|
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
|
vo.setAcceptTime(sdf.parse(opTime));
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
vo.setAcceptTime(null);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 设置状态 - 使用scanType作为状态
|
|
|
|
|
|
vo.setFirstStatusName(last.getString("memo"));
|
|
|
|
|
|
// 设置备注 - 使用memo作为备注
|
|
|
|
|
|
vo.setRemark(last.getString("scanType"));
|
|
|
|
|
|
return vo;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// main方法测试
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
|
List<String> waybillNos = new ArrayList<>();
|
2025-08-08 17:31:34 +08:00
|
|
|
|
waybillNos.add("777329412312954"); // 测试单号
|
2025-08-08 16:19:11 +08:00
|
|
|
|
String result = queryRoute(waybillNos);
|
|
|
|
|
|
System.out.println("申通原始返回:" + result);
|
|
|
|
|
|
OaExpressVo vo = parseData(result);
|
|
|
|
|
|
if (vo != null) {
|
|
|
|
|
|
System.out.println("解析后:" + vo);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
System.out.println("未解析到数据");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|