From 5205d8c155d529a5e4a7e722edaeddf61df25c8d Mon Sep 17 00:00:00 2001 From: van Date: Sat, 11 Apr 2026 00:48:37 +0800 Subject: [PATCH] 1 --- .../src/main/resources/application-dev.yml | 6 +++ .../src/main/resources/application-prod.yml | 4 ++ .../service/IErpGoofishOrderService.java | 3 ++ .../goofish/GoofishOrderChangeLogger.java | 4 +- .../impl/ErpGoofishOrderServiceImpl.java | 11 +++++ .../service/impl/LogisticsServiceImpl.java | 23 ++++++++++- .../ruoyi/jarvis/task/LogisticsScanTask.java | 32 +++++++++++---- .../wecom/WxSendGoofishNotifyClient.java | 40 ++++++++++++++++++- 8 files changed, 111 insertions(+), 12 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index ff2724f..d947f10 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -204,6 +204,12 @@ jarvis: health-path: /health # 每次定时任务最多处理多少条企微分享链待队列(RPUSH 入队、LPOP 出队) adhoc-pending-batch-size: 50 + # 物流扫描(LogisticsScanTask):轮询 JD 单拉运单 + drain 分享链队列 + scan: + cron: "0 */20 * * * ?" + order-delay-ms: 250 + # 0=不限制;例如 40 可控制单轮最长耗时(余下下轮再扫) + max-orders-per-round: 0 # 获取评论接口服务地址(后端转发,避免前端跨域) fetch-comments: base-url: http://192.168.8.60:5008 diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml index 6036dc5..1b25f07 100644 --- a/ruoyi-admin/src/main/resources/application-prod.yml +++ b/ruoyi-admin/src/main/resources/application-prod.yml @@ -203,6 +203,10 @@ jarvis: fetch-path: /fetch_logistics health-path: /health adhoc-pending-batch-size: 50 + scan: + cron: "0 */20 * * * ?" + order-delay-ms: 250 + max-orders-per-round: 0 # 获取评论接口服务地址(后端转发) fetch-comments: base-url: http://192.168.8.60:5008 diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IErpGoofishOrderService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IErpGoofishOrderService.java index b7d374d..351045a 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IErpGoofishOrderService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IErpGoofishOrderService.java @@ -45,6 +45,9 @@ public interface IErpGoofishOrderService { /** 京东物流服务在写 Redis / 企微货主推送后、触发闲鱼同步前记一笔(source=JD_LOGISTICS_PUSH)。 */ void traceJdLogisticsPushForGoofish(Long jdOrderId, String waybillNo, String summary); + /** 是否存在关联本京东单的闲管家订单(用于物流企微走闲鱼自建应用)。 */ + boolean hasLinkedGoofishOrder(Long jdOrderId); + /** 订单状态 / 物流 / 发货 变更日志(新→旧) */ List listEventLogsByOrderId(Long orderId); diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderChangeLogger.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderChangeLogger.java index ae4e8d9..812db18 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderChangeLogger.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderChangeLogger.java @@ -28,7 +28,9 @@ public class GoofishOrderChangeLogger { public static final String TYPE_LOGISTICS = "LOGISTICS_SYNC"; public static final String TYPE_SHIP = "SHIP"; - /** 京东物流扫描服务:Redis + 企微货主推送链路(见 LogisticsServiceImpl),不再二次走 wxSend 闲鱼应用避免重复打扰 */ + /** + * 京东物流扫描:货主通知已由 LogisticsServiceImpl 走闲鱼自建应用或 PDD;此处仅落库,避免再调 wxSend 重复推送。 + */ public static final String SOURCE_JD_LOGISTICS_PUSH = "JD_LOGISTICS_PUSH"; @Resource diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpGoofishOrderServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpGoofishOrderServiceImpl.java index 55744d1..2af4450 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpGoofishOrderServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpGoofishOrderServiceImpl.java @@ -171,6 +171,17 @@ public class ErpGoofishOrderServiceImpl implements IErpGoofishOrderService { } } + @Override + public boolean hasLinkedGoofishOrder(Long jdOrderId) { + if (jdOrderId == null) { + return false; + } + ErpGoofishOrder query = new ErpGoofishOrder(); + query.setJdOrderId(jdOrderId); + List list = erpGoofishOrderMapper.selectList(query); + return list != null && !list.isEmpty(); + } + @Override public void traceJdLogisticsPushForGoofish(Long jdOrderId, String waybillNo, String summary) { if (jdOrderId == null || goofishOrderChangeLogger == null) { diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/LogisticsServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/LogisticsServiceImpl.java index 576c56d..157afde 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/LogisticsServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/LogisticsServiceImpl.java @@ -9,6 +9,7 @@ import com.ruoyi.jarvis.mapper.WeComShareLinkLogisticsJobMapper; import com.ruoyi.jarvis.service.IErpGoofishOrderService; import com.ruoyi.jarvis.service.ILogisticsService; import com.ruoyi.jarvis.service.IJDOrderService; +import com.ruoyi.jarvis.wecom.WxSendGoofishNotifyClient; import com.ruoyi.system.service.ISysConfigService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -85,6 +86,9 @@ public class LogisticsServiceImpl implements ILogisticsService { @Resource private IErpGoofishOrderService erpGoofishOrderService; + + @Resource + private WxSendGoofishNotifyClient wxSendGoofishNotifyClient; @PostConstruct public void init() { @@ -847,8 +851,23 @@ public class LogisticsServiceImpl implements ILogisticsService { // 运单号 pushContent.append("运单号:").append("\n").append("\n").append("\n").append("\n").append(waybillNo).append("\n"); - - // 调用企业微信推送接口 + boolean useGoofishWecom = erpGoofishOrderService.hasLinkedGoofishOrder(order.getId()) + || (distributionMark != null && distributionMark.contains("闲鱼")); + if (useGoofishWecom) { + String touserGoofish = getTouserByDistributionMark(distributionMark); + String fullText = "JD物流信息推送\n" + pushContent; + logger.info("闲鱼关联或分销含「闲鱼」:尝试企微闲鱼自建应用 - 订单ID: {}, 分销标识: {}, 接收人: {}", + order.getId(), distributionMark, + StringUtils.hasText(touserGoofish) ? touserGoofish : "(jarvis.wecom.goofish-notify-touser)"); + if (wxSendGoofishNotifyClient.pushGoofishAgentText(touserGoofish, fullText)) { + logger.info("企微闲鱼应用推送成功 - 订单ID: {}, 订单号: {}, waybill_no: {}", + order.getId(), order.getOrderId(), waybillNo); + return true; + } + logger.warn("企微闲鱼应用推送失败或未配置 wxSend,回退 PDD 通道 - 订单ID: {}", order.getId()); + } + + // 调用企业微信推送接口(PDD 自建应用) JSONObject pushParam = new JSONObject(); pushParam.put("title", "JD物流信息推送"); pushParam.put("text", pushContent.toString()); diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/task/LogisticsScanTask.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/task/LogisticsScanTask.java index e1843d5..068e906 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/task/LogisticsScanTask.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/task/LogisticsScanTask.java @@ -5,15 +5,18 @@ import com.ruoyi.jarvis.service.IJDOrderService; import com.ruoyi.jarvis.service.ILogisticsService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.util.ArrayList; import java.util.List; /** * 物流信息扫描定时任务 - * 每15分钟扫描一次分销标记为F或PDD的订单(最近30天),获取物流信息并推送;结束后处理企微分享链 adhoc 队列 + * 按配置周期(默认每 20 分钟)扫描分销标记为 F/PDD 等的订单(最近 30 天),拉物流并推送; + * 结束后处理企微分享链 adhoc 队列。 */ @Component public class LogisticsScanTask { @@ -25,11 +28,18 @@ public class LogisticsScanTask { @Resource private ILogisticsService logisticsService; + /** 每条订单处理后的间隔(毫秒),减轻下游物流 HTTP 压力;0 表示不睡眠 */ + @Value("${jarvis.server.logistics.scan.order-delay-ms:250}") + private long orderDelayMs; + + /** 单轮最多处理的订单数,0 表示不限制(候选很多时可限制单轮耗时) */ + @Value("${jarvis.server.logistics.scan.max-orders-per-round:0}") + private int maxOrdersPerRound; + /** - * 定时任务:每15分钟执行一次(与 @Scheduled 中 cron 一致) - * 只扫描最近30天的订单 + * 只扫描最近 30 天的订单(SQL 固定);周期与单轮上限见 jarvis.server.logistics.scan.* */ - @Scheduled(cron = "0 */15 * * * ?") + @Scheduled(cron = "${jarvis.server.logistics.scan.cron:0 */20 * * * ?}") public void scanAndFetchLogistics() { long t0 = System.currentTimeMillis(); int orderCandidates = 0; @@ -47,8 +57,14 @@ public class LogisticsScanTask { if (orders == null || orders.isEmpty()) { logger.info("订单扫描:候选 0 条(最近30天 F/PDD 有物流链)"); } else { + int totalFromDb = orders.size(); + if (maxOrdersPerRound > 0 && orders.size() > maxOrdersPerRound) { + logger.info("订单扫描:库中候选 {} 条,本轮按 max-orders-per-round={} 仅处理前 {} 条(余下轮次继续)", + totalFromDb, maxOrdersPerRound, maxOrdersPerRound); + orders = new ArrayList<>(orders.subList(0, maxOrdersPerRound)); + } orderCandidates = orders.size(); - logger.info("订单扫描:候选 {} 条(最近30天 F/PDD 有物流链)", orderCandidates); + logger.info("订单扫描:本轮处理列表 {} 条(最近30天 F/PDD 有物流链)", orderCandidates); for (JDOrder order : orders) { try { @@ -57,7 +73,7 @@ public class LogisticsScanTask { continue; } - logger.info("订单扫描:处理中 id={} orderId={} mark={}", + logger.debug("订单扫描:处理中 id={} orderId={} mark={}", order.getId(), order.getOrderId(), order.getDistributionMark()); boolean success = logisticsService.fetchLogisticsAndPush(order); @@ -70,7 +86,9 @@ public class LogisticsScanTask { logger.warn("订单扫描:未成功 id={} orderId={}", order.getId(), order.getOrderId()); } - Thread.sleep(500); + if (orderDelayMs > 0) { + Thread.sleep(orderDelayMs); + } } catch (InterruptedException e) { logger.error("定时任务被中断", e); Thread.currentThread().interrupt(); diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/wecom/WxSendGoofishNotifyClient.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/wecom/WxSendGoofishNotifyClient.java index c027ea1..f6b822d 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/wecom/WxSendGoofishNotifyClient.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/wecom/WxSendGoofishNotifyClient.java @@ -70,6 +70,37 @@ public class WxSendGoofishNotifyClient { } } + /** + * 京东物流扫描等:全文发往企微「闲鱼」自建应用(wxSend qywx.app.goofishAgentId)。 + * + * @param optionalToUser 非空时用与 logistics.push.touser.* 相同的成员 ID(逗号/| 亦可);空则用 goofish-notify-touser + * @return HTTP 2xx 为 true;未配置密钥或 toUser 为空返回 false + */ + public boolean pushGoofishAgentText(String optionalToUser, String content) { + if (!StringUtils.hasText(wxsendBaseUrl) || !StringUtils.hasText(goofishPushSecret)) { + log.debug("wxSend 闲鱼应用物流推送跳过:未配置 wxsend-base-url 或 goofish-push-secret"); + return false; + } + String rawTouser = StringUtils.hasText(optionalToUser) ? optionalToUser : goofishNotifyTouser; + String toUser = buildTouserParam(rawTouser); + if (!StringUtils.hasText(toUser)) { + log.warn("wxSend 闲鱼应用物流推送跳过:无接收人(请配置 jarvis.wecom.goofish-notify-touser 或 logistics.push.touser)"); + return false; + } + String text = content != null ? content : ""; + if (text.length() > CONTENT_MAX) { + text = text.substring(0, CONTENT_MAX - 1) + "…"; + } + try { + String base = normalizeBase(wxsendBaseUrl); + String url = base + "/wecom/goofish-active-push"; + return postJsonReturnsOk(url, toUser, text); + } catch (Exception e) { + log.warn("wxSend goofish-active-push 物流全文失败 err={}", e.toString()); + return false; + } + } + private static String buildContent(String orderNo, String eventType, String source, String message) { StringBuilder sb = new StringBuilder(); sb.append("【闲鱼订单】").append(orderNo != null ? orderNo : "-").append("\n"); @@ -120,6 +151,10 @@ public class WxSendGoofishNotifyClient { } private void postJson(String url, String toUser, String content) throws Exception { + postJsonReturnsOk(url, toUser, content); + } + + private boolean postJsonReturnsOk(String url, String toUser, String content) throws Exception { JSONObject body = new JSONObject(); body.put("toUser", toUser); body.put("content", content); @@ -141,9 +176,10 @@ public class WxSendGoofishNotifyClient { String resp = readAll(is); if (code < 200 || code >= 300) { log.warn("wxSend goofish-active-push HTTP {} body={}", code, resp); - } else { - log.debug("wxSend goofish-active-push OK http={} resp={}", code, resp); + return false; } + log.debug("wxSend goofish-active-push OK http={} resp={}", code, resp); + return true; } finally { if (conn != null) { conn.disconnect();