From fd85236a97af3e739dc9b5b5b93a25a5528d0b45 Mon Sep 17 00:00:00 2001 From: 86156 <823267011@qq.com> Date: Wed, 23 Jul 2025 09:18:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=B8=AD=E9=80=9A=E7=99=BE?= =?UTF-8?q?=E4=B8=96=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-oa/pom.xml | 6 + .../oa/controller/OaExpressController.java | 3 +- .../ruoyi/oa/service/IOaExpressService.java | 3 +- .../oa/service/impl/OaExpressServiceImpl.java | 23 ++- .../ruoyi/oa/task/OaExpressRefreshTask.java | 3 +- .../ruoyi/oa/utils/BestRouteQueryUtil.java | 158 ++++++++++++++++++ .../com/ruoyi/oa/utils/ZtoTrackQueryUtil.java | 122 ++++++++++++++ 7 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 ruoyi-oa/src/main/java/com/ruoyi/oa/utils/BestRouteQueryUtil.java create mode 100644 ruoyi-oa/src/main/java/com/ruoyi/oa/utils/ZtoTrackQueryUtil.java 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