diff --git a/ruoyi-oa/pom.xml b/ruoyi-oa/pom.xml
index 25a7d2f..56aba55 100644
--- a/ruoyi-oa/pom.xml
+++ b/ruoyi-oa/pom.xml
@@ -57,6 +57,12 @@
spring-boot-starter-mail
+
+ com.zto.zop
+ zopsdk
+ 0.11
+
+
diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaExpressController.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaExpressController.java
index 76948b5..adef327 100644
--- a/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaExpressController.java
+++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaExpressController.java
@@ -1,5 +1,6 @@
package com.ruoyi.oa.controller;
+import java.io.IOException;
import java.util.List;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@@ -101,7 +102,7 @@ public class OaExpressController extends BaseController {
@GetMapping("/refresh/{expressIds}")
- public R getRefreshExpress(@PathVariable Long[] expressIds) {
+ public R getRefreshExpress(@PathVariable Long[] expressIds) throws IOException {
return toAjax(iOaExpressService.getRefreshExpress(Arrays.asList(expressIds)));
}
}
diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaExpressService.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaExpressService.java
index 0f528d7..beea4b2 100644
--- a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaExpressService.java
+++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaExpressService.java
@@ -6,6 +6,7 @@ import com.ruoyi.oa.domain.bo.OaExpressBo;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.PageQuery;
+import java.io.IOException;
import java.util.Collection;
import java.util.List;
@@ -52,6 +53,6 @@ public interface IOaExpressService {
* @param list
* @return
*/
- Boolean getRefreshExpress(List list);
+ Boolean getRefreshExpress(List list) throws IOException;
}
diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaExpressServiceImpl.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaExpressServiceImpl.java
index 74c08dd..63f592a 100644
--- a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaExpressServiceImpl.java
+++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaExpressServiceImpl.java
@@ -7,7 +7,9 @@ import com.ruoyi.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.oa.utils.BestRouteQueryUtil;
import com.ruoyi.oa.utils.SfRouteQueryUtil;
+import com.ruoyi.oa.utils.ZtoTrackQueryUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.ruoyi.oa.domain.bo.OaExpressBo;
@@ -16,6 +18,7 @@ import com.ruoyi.oa.domain.OaExpress;
import com.ruoyi.oa.mapper.OaExpressMapper;
import com.ruoyi.oa.service.IOaExpressService;
+import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Collection;
@@ -129,7 +132,7 @@ public class OaExpressServiceImpl implements IOaExpressService {
}
@Override
- public Boolean getRefreshExpress(List expressIds) {
+ public Boolean getRefreshExpress(List expressIds) throws IOException {
for (Long expressId : expressIds) {
OaExpressVo oaExpressVo = baseMapper.selectVoById(expressId);
String expressType = oaExpressVo.getExpressType();
@@ -142,6 +145,24 @@ public class OaExpressServiceImpl implements IOaExpressService {
oaExpressVo.setLastStatus(oaExpressVo1.getFirstStatusName());
}
}
+ if (expressType.equals("ZTO") && oaExpressVo.getStatus() ==1L) {
+ // 校验为顺丰则进入此位置更新状态
+ OaExpressVo oaExpressVo1 = ZtoTrackQueryUtil.queryTrack(oaExpressVo.getExpressCode(), oaExpressVo.getOwnerPhone() != null ? oaExpressVo.getOwnerPhone() : oaExpressVo.getSupplyPhone());
+ if (oaExpressVo1 != null) {
+ oaExpressVo.setLastUpdateTime(oaExpressVo1.getAcceptTime());
+ oaExpressVo.setLastStatus(oaExpressVo1.getFirstStatusName());
+ }
+ }
+
+ if (expressType.equals("Best") && oaExpressVo.getStatus() ==1L) {
+ // 校验为顺丰则进入此位置更新状态
+ OaExpressVo oaExpressVo1 = BestRouteQueryUtil.queryRoute(oaExpressVo.getExpressCode());
+
+ if (oaExpressVo1 != null) {
+ oaExpressVo.setLastUpdateTime(oaExpressVo1.getAcceptTime());
+ oaExpressVo.setLastStatus(oaExpressVo1.getFirstStatusName());
+ }
+ }
OaExpress add = BeanUtil.toBean(oaExpressVo, OaExpress.class);
baseMapper.updateById(add);;
}
diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/task/OaExpressRefreshTask.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/task/OaExpressRefreshTask.java
index 3d8f384..b1f0d43 100644
--- a/ruoyi-oa/src/main/java/com/ruoyi/oa/task/OaExpressRefreshTask.java
+++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/task/OaExpressRefreshTask.java
@@ -4,6 +4,7 @@ import com.ruoyi.oa.service.impl.OaExpressServiceImpl;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
+import java.io.IOException;
import java.util.List;
@Component
@@ -14,7 +15,7 @@ public class OaExpressRefreshTask {
// 每20分钟执行一次
@Scheduled(cron = "0 0/20 * * * ?")
- public void refreshExpress() {
+ public void refreshExpress() throws IOException {
List expressIds = oaExpressService.getAllSfExpressIdsToRefresh();
if (expressIds != null && !expressIds.isEmpty()) {
oaExpressService.getRefreshExpress(expressIds);
diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/utils/BestRouteQueryUtil.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/utils/BestRouteQueryUtil.java
new file mode 100644
index 0000000..80a02d5
--- /dev/null
+++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/utils/BestRouteQueryUtil.java
@@ -0,0 +1,158 @@
+package com.ruoyi.oa.utils;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.oa.domain.vo.OaExpressVo;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.Base64;
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.List;
+
+public class BestRouteQueryUtil {
+ // 百世开放平台 partnerID
+ private static final String PARTNER_ID = "7285";
+ // 百世开放平台接口地址(请替换为实际地址)
+ private static final String API_URL = "http://openapi.800best.com/api-server/ky/api/process";
+ // 百世开放平台 partnerKey
+ private static final String PARTNER_KEY = "NMGA0BQI";
+ // 固定参数
+ private static final String SERVICE_TYPE = "KY_TRACE_QUERY";
+
+ /**
+ * 查询百世快递路由
+ * @param mailNo 运单号
+ * @return 查询结果OaExpressVo
+ */
+ public static OaExpressVo queryRoute(String mailNo) {
+ try {
+ // 1. 构造业务数据bizData
+ JSONObject bizData = new JSONObject();
+ bizData.put("logisticCompanyID", "BESTQJT");
+ bizData.put("codes", Arrays.asList(mailNo));
+ String bizDataStr = bizData.toJSONString();
+
+ // 2. 生成签名(签名前不做URLEncode)
+ String sign = genSign(bizDataStr, PARTNER_KEY);
+
+ // 3. 拼接表单参数(此处做URLEncode)
+ StringBuilder formParams = new StringBuilder();
+ formParams.append("partnerID=").append(URLEncoder.encode(PARTNER_ID, "UTF-8"));
+ formParams.append("&serviceType=").append(URLEncoder.encode(SERVICE_TYPE, "UTF-8"));
+ formParams.append("&sign=").append(URLEncoder.encode(sign, "UTF-8"));
+ formParams.append("&bizData=").append(URLEncoder.encode(bizDataStr, "UTF-8"));
+ String formBody = formParams.toString();
+
+ // 4. 发送POST请求(表单格式,UTF-8编码)
+ return doPost(API_URL, formBody);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 百世签名生成方式:MD5(bizData + partnerKey),再转大写
+ */
+ private static String genSign(String bizData, String partnerKey) throws Exception {
+ String toSign = bizData + partnerKey;
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ md5.update(toSign.getBytes("UTF-8"));
+ byte[] digest = md5.digest();
+ StringBuilder sb = new StringBuilder();
+ for (byte b : digest) {
+ sb.append(String.format("%02x", b));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 发送POST请求(表单格式,UTF-8编码)
+ */
+ private static OaExpressVo doPost(String urlStr, String formBody) throws Exception {
+ URL url = new URL(urlStr);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
+ conn.setDoOutput(true);
+ try (OutputStream os = conn.getOutputStream()) {
+ os.write(formBody.getBytes(StandardCharsets.UTF_8));
+ }
+ int code = conn.getResponseCode();
+ if (code == 200) {
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = br.readLine()) != null) {
+ sb.append(line);
+ }
+ return parseData(sb.toString());
+ }
+ } else {
+ throw new RuntimeException("HTTP请求失败,状态码:" + code);
+ }
+ }
+
+ /**
+ * 解析百世快递返回数据,只取第一个轨迹的acceptTime和remark
+ */
+ public static OaExpressVo parseData(String result) {
+ if (StringUtils.isBlank(result)) {
+ System.out.println("返回结果为空");
+ return null;
+ }
+ try {
+ JSONObject json = JSON.parseObject(result);
+ Boolean success = json.getBoolean("result");
+ if (success == null || !success) {
+ System.out.println("查询失败:" + json.getString("resultInfo"));
+ return null;
+ }
+ JSONArray orderTraceList = json.getJSONArray("orderTraceList");
+ if (orderTraceList == null || orderTraceList.isEmpty()) {
+ System.out.println("无轨迹信息");
+ return null;
+ }
+ // 只取第一个轨迹的最后一个步骤
+ JSONObject orderTrace = orderTraceList.getJSONObject(0);
+ JSONArray steps = orderTrace.getJSONArray("steps");
+ if (steps == null || steps.isEmpty()) {
+ System.out.println("无轨迹步骤信息");
+ return null;
+ }
+ JSONObject step = steps.getJSONObject(steps.size() - 1);
+ OaExpressVo vo = new OaExpressVo();
+ vo.setExpressCode(orderTrace.getString("code"));
+ vo.setFirstStatusName(step.getString("remark"));
+ String acceptTimeStr = step.getString("acceptTime");
+ if (acceptTimeStr != null) {
+ // 假设格式为 yyyy-MM-dd HH:mm:ss
+ try {
+ vo.setAcceptTime(java.sql.Timestamp.valueOf(acceptTimeStr));
+ } catch (Exception e) {
+ vo.setAcceptTime(null);
+ }
+ }
+ return vo;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ // main方法测试
+ public static void main(String[] args) {
+ String mailNo = "80004559434";
+ OaExpressVo vo = queryRoute(mailNo);
+ System.out.println(vo);
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/utils/ZtoTrackQueryUtil.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/utils/ZtoTrackQueryUtil.java
new file mode 100644
index 0000000..aff7bb6
--- /dev/null
+++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/utils/ZtoTrackQueryUtil.java
@@ -0,0 +1,122 @@
+package com.ruoyi.oa.utils;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.zto.zop.ZopClient;
+import com.zto.zop.ZopPublicRequest;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.Base64;
+
+import static com.zto.zop.EncryptionType.MD5;
+import com.ruoyi.oa.domain.vo.OaExpressVo;
+import liquibase.pro.packaged.J;
+import org.apache.commons.lang3.StringUtils;
+import com.ruoyi.common.utils.DateUtils;
+import org.aspectj.weaver.ast.Expr;
+
+public class ZtoTrackQueryUtil {
+ // 替换为你的appKey和appSecret
+ private static final String APP_KEY = "1ebf01ae01cc81d78ba9a";
+ private static final String APP_SECRET = "4bded7f302da7f7913b01810e3421b80";
+ // 测试环境
+// private static final String API_URL = "https://japi-test.zto.com/zto.merchant.waybill.track.query";
+ // 正式环境
+ private static final String API_URL = "https://japi.zto.com/zto.merchant.waybill.track.query";
+
+ /**
+ * 查询中通物流轨迹
+ *
+ * @param billCode 运单号
+ * @return 查询结果JSON
+ */
+ public static OaExpressVo queryTrack(String billCode,String phoneNumber) throws IOException {
+
+ ZopClient client = new ZopClient(APP_KEY, APP_SECRET);
+ ZopPublicRequest request = new ZopPublicRequest();
+ request.setBody("{\"billCode\":\""+billCode+"\",\"mobilePhone\":\""+ StringUtils.right(phoneNumber, 4)+"\"}");
+ request.setUrl(API_URL);
+ request.setBase64(true);
+ request.setEncryptionType(MD5);
+ request.setTimestamp(null);
+ String execute = client.execute(request);
+ System.out.println(execute);
+ return parseData(execute);
+ }
+
+ /**
+ * 解析中通返回数据,封装为OaExpressVo
+ */
+ public static OaExpressVo parseData(String result) {
+ if (StringUtils.isBlank(result)) {
+ return null;
+ }
+ try {
+ JSONObject json = JSON.parseObject(result);
+ if (json == null || !"true".equalsIgnoreCase(String.valueOf(json.get("status")))) {
+ return null;
+ }
+ // 取result数组
+ if (!json.containsKey("result")) {
+ return null;
+ }
+ // 只取第一个轨迹(如有多条)
+ Object resultArr = json.get("result");
+ if (!(resultArr instanceof java.util.List)) {
+ return null;
+ }
+ JSONArray arr = json.getJSONArray("result");
+ if (arr == null || arr.isEmpty()) {
+ return null;
+ }
+ JSONObject first = arr.getJSONObject(arr.size() - 1); // 取最后一条为最新
+ if (first == null) {
+ return null;
+ }
+ String scanDate = first.getString("scanDate");
+ String desc = first.getString("desc");
+ OaExpressVo vo = new OaExpressVo();
+ // scanDate为时间戳字符串
+ if (StringUtils.isNotBlank(scanDate)) {
+ try {
+ long time = Long.parseLong(scanDate);
+ vo.setAcceptTime(new java.util.Date(time));
+ } catch (Exception e) {
+ vo.setAcceptTime(null);
+ }
+ }
+ vo.setFirstStatusName(desc);
+ return vo;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static String getPublicIp() {
+ String ip = "";
+ try {
+ URL url = new URL("http://ifconfig.me/ip");
+ BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
+ ip = in.readLine();
+ in.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return ip;
+ }
+
+ // main方法测试
+ public static void main(String[] args) throws IOException {
+ System.out.println("本机公网IP为: " + getPublicIp());
+ String billCode = "78921681808881";
+ OaExpressVo result = queryTrack(billCode,"9553");
+
+ System.out.println(result);
+ }
+}
\ No newline at end of file