no message
This commit is contained in:
parent
671a9f225e
commit
1fe53780b9
|
|
@ -147,12 +147,22 @@ aliyun:
|
|||
authCode: od2FgE9a9g
|
||||
|
||||
# 物流API配置
|
||||
logistics:
|
||||
kdniao:
|
||||
# 快递鸟 API配置
|
||||
appId: '1889454' # 快递鸟应用ID,需要到快递鸟官网申请
|
||||
appKey: 'b2483529-807d-49af-b0e1-1ed218baa4db' # 快递鸟API密钥,需要到快递鸟官网申请
|
||||
url: 'https://api.kdniao.com/api/dist' # 快递鸟即时查询接口地址
|
||||
# logistics:
|
||||
# kdniao:
|
||||
# # 快递鸟 API配置
|
||||
# appId: '1889454' # 快递鸟应用ID,需要到快递鸟官网申请
|
||||
# appKey: 'b2483529-807d-49af-b0e1-1ed218baa4db' # 快递鸟API密钥,需要到快递鸟官网申请
|
||||
# url: 'https://api.kdniao.com/api/dist' # 快递鸟即时查询接口地址
|
||||
# 快递鸟支持两种接口:
|
||||
# 1. 即时查询接口:https://api.kdniao.com/api/dist
|
||||
# 2. 物流跟踪接口:https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx
|
||||
# 2. 物流跟踪接口:https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx
|
||||
|
||||
logistics:
|
||||
# 阿里云快递查询API配置
|
||||
aliyun:
|
||||
# 阿里云API网关AppCode,需要在阿里云市场购买快递查询服务后获取
|
||||
appCode: "78064b8aaa294f64ab07a285f129aea6"
|
||||
# API主机地址
|
||||
host: "https://kzexpress.market.alicloudapi.com"
|
||||
# API路径
|
||||
path: "/api-mall/api/express/query"
|
||||
|
|
@ -27,4 +27,9 @@ public class LogisticsQueryRequest {
|
|||
* 快递公司名称(可选,如果不传会自动识别)
|
||||
*/
|
||||
private String expressName;
|
||||
|
||||
/**
|
||||
* 手机号(阿里云快递查询API需要)
|
||||
*/
|
||||
private String mobile;
|
||||
}
|
||||
|
|
@ -35,6 +35,16 @@ public interface LogisticsService {
|
|||
*/
|
||||
LogisticsInfo queryLogistics(String trackingNumber, String expressCode);
|
||||
|
||||
/**
|
||||
* 根据快递单号、快递公司编码和手机号查询物流信息
|
||||
*
|
||||
* @param trackingNumber 快递单号
|
||||
* @param expressCode 快递公司编码
|
||||
* @param mobile 手机号
|
||||
* @return 物流信息
|
||||
*/
|
||||
LogisticsInfo queryLogistics(String trackingNumber, String expressCode, String mobile);
|
||||
|
||||
/**
|
||||
* 根据订单ID查询物流信息
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.ghy.order.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
|
|
@ -10,8 +9,11 @@ import com.ghy.order.domain.OrderMaster;
|
|||
import com.ghy.order.request.LogisticsQueryRequest;
|
||||
import com.ghy.order.service.LogisticsService;
|
||||
import com.ghy.order.service.OrderMasterService;
|
||||
import com.ghy.order.utils.HttpUtils;
|
||||
import com.ghy.order.utils.LogisticsUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
@ -35,29 +37,41 @@ public class LogisticsServiceImpl implements LogisticsService {
|
|||
private OrderMasterService orderMasterService;
|
||||
|
||||
/**
|
||||
* 快递鸟 API配置
|
||||
* 阿里云快递查询 API配置
|
||||
*/
|
||||
@Value("${logistics.kdniao.appId:}")
|
||||
private String appId;
|
||||
@Value("${logistics.aliyun.appCode:}")
|
||||
private String appCode;
|
||||
|
||||
@Value("${logistics.kdniao.appKey:}")
|
||||
private String appKey;
|
||||
@Value("${logistics.aliyun.host:https://kzexpress.market.alicloudapi.com}")
|
||||
private String host;
|
||||
|
||||
@Value("${logistics.kdniao.url:https://api.kdniao.com/api/dist}")
|
||||
private String apiUrl;
|
||||
@Value("${logistics.aliyun.path:/api-mall/api/express/query}")
|
||||
private String path;
|
||||
|
||||
@Override
|
||||
public LogisticsInfo queryLogistics(LogisticsQueryRequest request) {
|
||||
return queryLogistics(request.getTrackingNumber(), request.getExpressCode());
|
||||
return queryLogistics(request.getTrackingNumber(), request.getExpressCode(), request.getMobile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogisticsInfo queryLogistics(String trackingNumber) {
|
||||
return queryLogistics(trackingNumber, null);
|
||||
return queryLogistics(trackingNumber, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogisticsInfo queryLogistics(String trackingNumber, String expressCode) {
|
||||
return queryLogistics(trackingNumber, expressCode, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据快递单号查询物流信息(阿里云API)
|
||||
*
|
||||
* @param trackingNumber 快递单号
|
||||
* @param expressCode 快递公司编码
|
||||
* @param mobile 手机号
|
||||
* @return 物流信息
|
||||
*/
|
||||
public LogisticsInfo queryLogistics(String trackingNumber, String expressCode, String mobile) {
|
||||
LogisticsInfo logisticsInfo = new LogisticsInfo();
|
||||
logisticsInfo.setTrackingNumber(trackingNumber);
|
||||
logisticsInfo.setQueryTime(new Date());
|
||||
|
|
@ -70,41 +84,47 @@ public class LogisticsServiceImpl implements LogisticsService {
|
|||
return logisticsInfo;
|
||||
}
|
||||
|
||||
// 如果没有指定快递公司,先识别快递公司
|
||||
if (StrUtil.isBlank(expressCode)) {
|
||||
expressCode = LogisticsUtils.identifyExpressCompany(trackingNumber);
|
||||
}
|
||||
// 调用阿里云快递查询 API
|
||||
JSONObject result = callAliyunAPI(trackingNumber, mobile);
|
||||
|
||||
// 调用快递鸟 API查询物流信息
|
||||
JSONObject result = callKdniaoAPI(trackingNumber, expressCode);
|
||||
|
||||
if (result.getBool("Success", false)) {
|
||||
if (result.getBool("success", false)) {
|
||||
// 查询成功
|
||||
logisticsInfo.setSuccess(true);
|
||||
logisticsInfo.setExpressCode(expressCode);
|
||||
logisticsInfo.setExpressName(result.getStr("ShipperName"));
|
||||
logisticsInfo.setStatus(getKdniaoStatus(result.getStr("State")));
|
||||
logisticsInfo.setStatusDesc(result.getStr("State"));
|
||||
|
||||
// 解析物流轨迹
|
||||
List<LogisticsInfo.LogisticsTrace> traces = new ArrayList<>();
|
||||
JSONArray tracesArray = result.getJSONArray("Traces");
|
||||
if (tracesArray != null) {
|
||||
for (int i = 0; i < tracesArray.size(); i++) {
|
||||
JSONObject trace = tracesArray.getJSONObject(i);
|
||||
LogisticsInfo.LogisticsTrace logisticsTrace = new LogisticsInfo.LogisticsTrace();
|
||||
logisticsTrace.setTime(trace.getDate("AcceptTime"));
|
||||
logisticsTrace.setLocation(trace.getStr("AcceptStation"));
|
||||
logisticsTrace.setDescription(trace.getStr("AcceptStation"));
|
||||
logisticsTrace.setStatus(trace.getStr("Remark"));
|
||||
traces.add(logisticsTrace);
|
||||
// 解析快递公司信息
|
||||
JSONObject data = result.getJSONObject("data");
|
||||
if (data != null) {
|
||||
logisticsInfo.setExpressCode(data.getStr("cpCode"));
|
||||
logisticsInfo.setExpressName(data.getStr("logisticsCompanyName"));
|
||||
logisticsInfo.setStatus(getAliyunStatus(data.getStr("logisticsStatusDesc")));
|
||||
logisticsInfo.setStatusDesc(data.getStr("logisticsStatusDesc"));
|
||||
|
||||
// 解析物流轨迹
|
||||
List<LogisticsInfo.LogisticsTrace> traces = new ArrayList<>();
|
||||
JSONArray tracesArray = data.getJSONArray("logisticsTraceDetailList");
|
||||
if (tracesArray != null) {
|
||||
for (int i = 0; i < tracesArray.size(); i++) {
|
||||
JSONObject trace = tracesArray.getJSONObject(i);
|
||||
LogisticsInfo.LogisticsTrace logisticsTrace = new LogisticsInfo.LogisticsTrace();
|
||||
|
||||
// 处理时间戳
|
||||
Long timeStamp = trace.getLong("time");
|
||||
if (timeStamp != null) {
|
||||
logisticsTrace.setTime(new Date(timeStamp));
|
||||
}
|
||||
|
||||
logisticsTrace.setLocation(trace.getStr("areaName"));
|
||||
logisticsTrace.setDescription(trace.getStr("desc"));
|
||||
logisticsTrace.setStatus(trace.getStr("logisticsStatus"));
|
||||
traces.add(logisticsTrace);
|
||||
}
|
||||
}
|
||||
logisticsInfo.setTraces(traces);
|
||||
}
|
||||
logisticsInfo.setTraces(traces);
|
||||
} else {
|
||||
// 查询失败
|
||||
logisticsInfo.setSuccess(false);
|
||||
logisticsInfo.setErrorMsg(result.getStr("Reason", "查询失败"));
|
||||
logisticsInfo.setErrorMsg(result.getStr("msg", "查询失败"));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
|
|
@ -130,88 +150,125 @@ public class LogisticsServiceImpl implements LogisticsService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 调用快递鸟 API
|
||||
* 调用阿里云快递查询 API
|
||||
*
|
||||
* @param trackingNumber 快递单号
|
||||
* @param expressCode 快递公司编码
|
||||
* @param mobile 手机号
|
||||
* @return API返回结果
|
||||
*/
|
||||
private JSONObject callKdniaoAPI(String trackingNumber, String expressCode) {
|
||||
private JSONObject callAliyunAPI(String trackingNumber, String mobile) {
|
||||
try {
|
||||
// 构建请求数据
|
||||
JSONObject requestData = new JSONObject();
|
||||
requestData.set("ShipperCode", expressCode);
|
||||
requestData.set("LogisticCode", trackingNumber);
|
||||
String method = "GET";
|
||||
|
||||
String requestDataStr = requestData.toString();
|
||||
// 构建请求头
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Authorization", "APPCODE " + appCode);
|
||||
|
||||
// 计算数据签名 - 按照快递鸟官方文档
|
||||
// S1: RequestData(未编码) + ApiKey
|
||||
String s1 = requestDataStr + appKey;
|
||||
log.info("S1: {}", s1);
|
||||
// 构建查询参数
|
||||
Map<String, String> querys = new HashMap<>();
|
||||
querys.put("expressNo", trackingNumber);
|
||||
if (StrUtil.isNotBlank(mobile)) {
|
||||
querys.put("mobile", mobile);
|
||||
}
|
||||
|
||||
// S2: MD5加密(32位小写)
|
||||
String s2 = cn.hutool.crypto.digest.DigestUtil.md5Hex(s1);
|
||||
log.info("S2: {}", s2);
|
||||
log.info("阿里云快递查询API请求参数: expressNo={}, mobile={}", trackingNumber, mobile);
|
||||
|
||||
// S3: Base64编码
|
||||
String s3 = java.util.Base64.getEncoder().encodeToString(s2.getBytes("UTF-8"));
|
||||
log.info("S3: {}", s3);
|
||||
// 调用API
|
||||
HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys);
|
||||
String result = EntityUtils.toString(response.getEntity());
|
||||
log.info("阿里云快递查询API响应结果: {}", result);
|
||||
|
||||
// S4: URL编码(仅请求接口需要)
|
||||
String s4 = java.net.URLEncoder.encode(s3, "UTF-8");
|
||||
log.info("S4: {}", s4);
|
||||
// 检查响应状态码
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode != 200) {
|
||||
log.error("阿里云API返回错误状态码: {}", statusCode);
|
||||
JSONObject errorResult = new JSONObject();
|
||||
errorResult.set("success", false);
|
||||
errorResult.set("message", "API返回错误状态码: " + statusCode);
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
// 构建请求参数 - 即时查询接口使用GET请求
|
||||
Map<String, Object> requestParam = new HashMap<>();
|
||||
requestParam.put("RequestData", requestDataStr);
|
||||
requestParam.put("EBusinessID", appId);
|
||||
requestParam.put("RequestType", "8002"); // 即时查询接口
|
||||
requestParam.put("DataSign", s4); // 使用URL编码后的签名
|
||||
requestParam.put("DataType", "2"); // JSON格式
|
||||
// 检查响应内容是否为空
|
||||
if (StrUtil.isBlank(result)) {
|
||||
log.error("阿里云API返回空响应");
|
||||
JSONObject errorResult = new JSONObject();
|
||||
errorResult.set("success", false);
|
||||
errorResult.set("message", "API返回空响应");
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
log.info("快递鸟API请求参数: {}", requestParam.toString());
|
||||
// 检查响应内容是否以{开头(JSON格式)
|
||||
if (!result.trim().startsWith("{")) {
|
||||
log.error("阿里云API返回非JSON格式响应: {}", result.substring(0, Math.min(100, result.length())));
|
||||
JSONObject errorResult = new JSONObject();
|
||||
errorResult.set("success", false);
|
||||
errorResult.set("message", "API返回非JSON格式响应");
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
// 使用GET请求调用即时查询接口
|
||||
String result = HttpUtil.get(apiUrl, requestParam);
|
||||
log.info("快递鸟API响应结果: {}", result);
|
||||
// 尝试解析JSON
|
||||
try {
|
||||
return JSONUtil.parseObj(result);
|
||||
} catch (Exception jsonException) {
|
||||
log.error("解析阿里云API响应JSON失败: {}", jsonException.getMessage());
|
||||
JSONObject errorResult = new JSONObject();
|
||||
errorResult.set("success", false);
|
||||
errorResult.set("message", "解析API响应失败: " + jsonException.getMessage());
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
return JSONUtil.parseObj(result);
|
||||
} catch (Exception e) {
|
||||
log.error("调用快递鸟API失败: {}", e.getMessage(), e);
|
||||
log.error("调用阿里云快递查询API失败: {}", e.getMessage(), e);
|
||||
JSONObject errorResult = new JSONObject();
|
||||
errorResult.set("Success", false);
|
||||
errorResult.set("Reason", "API调用失败: " + e.getMessage());
|
||||
errorResult.set("success", false);
|
||||
errorResult.set("message", "API调用失败: " + e.getMessage());
|
||||
return errorResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将快递鸟状态转换为系统状态码
|
||||
* 将阿里云状态转换为系统状态码
|
||||
*
|
||||
* @param kdniaoStatus 快递鸟状态
|
||||
* @param aliyunStatus 阿里云状态
|
||||
* @return 系统状态码
|
||||
*/
|
||||
private Integer getKdniaoStatus(String kdniaoStatus) {
|
||||
if (StrUtil.isBlank(kdniaoStatus)) {
|
||||
private Integer getAliyunStatus(String aliyunStatus) {
|
||||
if (StrUtil.isBlank(aliyunStatus)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (kdniaoStatus) {
|
||||
switch (aliyunStatus) {
|
||||
case "在途":
|
||||
case "运输中":
|
||||
case "TRANSPORT":
|
||||
return 0;
|
||||
case "揽收":
|
||||
case "已揽收":
|
||||
case "ACCEPT":
|
||||
return 1;
|
||||
case "疑难":
|
||||
case "异常":
|
||||
case "EXCEPTION":
|
||||
return 2;
|
||||
case "签收":
|
||||
case "已签收":
|
||||
case "SIGN":
|
||||
return 3;
|
||||
case "退签":
|
||||
case "已退签":
|
||||
case "REJECT":
|
||||
return 4;
|
||||
case "派件":
|
||||
case "派送中":
|
||||
case "DELIVERING":
|
||||
return 5;
|
||||
case "退回":
|
||||
case "已退回":
|
||||
case "RETURN":
|
||||
return 6;
|
||||
case "到达驿站":
|
||||
case "STA_INBOUND":
|
||||
return 5; // 到达驿站也算派件状态
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
package com.ghy.order.utils;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Http工具类 - 用于阿里云API调用
|
||||
* 参考:https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
|
||||
*/
|
||||
public class HttpUtils {
|
||||
|
||||
/**
|
||||
* 发送GET请求
|
||||
*
|
||||
* @param host 主机地址
|
||||
* @param path 路径
|
||||
* @param method 请求方法
|
||||
* @param headers 请求头
|
||||
* @param querys 查询参数
|
||||
* @return HttpResponse
|
||||
* @throws Exception
|
||||
*/
|
||||
public static HttpResponse doGet(String host, String path, String method,
|
||||
Map<String, String> headers,
|
||||
Map<String, String> querys) throws Exception {
|
||||
HttpClient httpClient = new DefaultHttpClient();
|
||||
HttpGet request = new HttpGet(buildUrl(host, path, querys));
|
||||
for (Map.Entry<String, String> e : headers.entrySet()) {
|
||||
request.addHeader(e.getKey(), e.getValue());
|
||||
}
|
||||
return httpClient.execute(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送POST请求
|
||||
*
|
||||
* @param host 主机地址
|
||||
* @param path 路径
|
||||
* @param method 请求方法
|
||||
* @param headers 请求头
|
||||
* @param querys 查询参数
|
||||
* @param bodys 请求体
|
||||
* @return HttpResponse
|
||||
* @throws Exception
|
||||
*/
|
||||
public static HttpResponse doPost(String host, String path, String method,
|
||||
Map<String, String> headers,
|
||||
Map<String, String> querys,
|
||||
Map<String, String> bodys) throws Exception {
|
||||
HttpClient httpClient = new DefaultHttpClient();
|
||||
HttpPost request = new HttpPost(buildUrl(host, path, querys));
|
||||
for (Map.Entry<String, String> e : headers.entrySet()) {
|
||||
request.addHeader(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
if (bodys != null) {
|
||||
List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();
|
||||
|
||||
for (String key : bodys.keySet()) {
|
||||
nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
|
||||
}
|
||||
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
|
||||
formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
|
||||
request.setEntity(formEntity);
|
||||
}
|
||||
|
||||
return httpClient.execute(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL
|
||||
*
|
||||
* @param host 主机地址
|
||||
* @param path 路径
|
||||
* @param querys 查询参数
|
||||
* @return 完整URL
|
||||
*/
|
||||
private static String buildUrl(String host, String path, Map<String, String> querys) throws Exception {
|
||||
StringBuilder sbUrl = new StringBuilder();
|
||||
sbUrl.append(host);
|
||||
if (path != null && path.length() > 0) {
|
||||
sbUrl.append(path);
|
||||
}
|
||||
if (null != querys) {
|
||||
StringBuilder sbQuery = new StringBuilder();
|
||||
for (Map.Entry<String, String> query : querys.entrySet()) {
|
||||
if (0 < sbQuery.length()) {
|
||||
sbQuery.append("&");
|
||||
}
|
||||
if (query.getKey() != null && query.getKey().length() > 0) {
|
||||
sbQuery.append(query.getKey());
|
||||
}
|
||||
if (query.getValue() != null && query.getValue().length() > 0) {
|
||||
sbQuery.append("=").append(URLEncoder.encode(query.getValue(), "utf-8"));
|
||||
}
|
||||
}
|
||||
if (0 < sbQuery.length()) {
|
||||
sbUrl.append("?").append(sbQuery);
|
||||
}
|
||||
}
|
||||
|
||||
return sbUrl.toString();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue