From 2524461ff4866ba3ab9a45acdd1199b736a3004f Mon Sep 17 00:00:00 2001 From: Leo Date: Fri, 5 Dec 2025 22:16:25 +0800 Subject: [PATCH] 1 --- .../service/impl/LogisticsServiceImpl.java | 110 ++++++++++++++---- 1 file changed, 90 insertions(+), 20 deletions(-) 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 b8e6682..c9b4d84 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 @@ -24,10 +24,12 @@ public class LogisticsServiceImpl implements ILogisticsService { private static final Logger logger = LoggerFactory.getLogger(LogisticsServiceImpl.class); private static final String REDIS_WAYBILL_KEY_PREFIX = "logistics:waybill:order:"; + private static final String REDIS_LOCK_KEY_PREFIX = "logistics:lock:order:"; private static final String EXTERNAL_API_URL = "http://192.168.8.88:5001/fetch_logistics?tracking_url="; private static final String PUSH_URL = "https://wxts.van333.cn/wx/send/pdd"; private static final String PUSH_TOKEN = "super_token_b62190c26"; private static final String CONFIG_KEY_PREFIX = "logistics.push.touser."; + private static final long LOCK_EXPIRE_SECONDS = 300; // 锁过期时间5分钟,防止死锁 @Resource private StringRedisTemplate stringRedisTemplate; @@ -51,27 +53,49 @@ public class LogisticsServiceImpl implements ILogisticsService { return false; } - // 检查物流链接 - String logisticsLink = order.getLogisticsLink(); - if (logisticsLink == null || logisticsLink.trim().isEmpty()) { - logger.info("订单暂无物流链接,跳过处理 - 订单ID: {}", order.getId()); - return false; + Long orderId = order.getId(); + + // 双重检查:先检查是否已处理过 + if (isOrderProcessed(orderId)) { + logger.info("订单已处理过,跳过 - 订单ID: {}", orderId); + return true; // 返回true表示已处理,避免重复处理 + } + + // 获取分布式锁,防止并发处理同一订单 + String lockKey = REDIS_LOCK_KEY_PREFIX + orderId; + Boolean lockAcquired = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "locked", LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS); + + if (Boolean.FALSE.equals(lockAcquired)) { + logger.warn("订单正在被其他线程处理,跳过 - 订单ID: {}", orderId); + return false; // 其他线程正在处理,返回false让调用方稍后重试 } try { + // 获取锁后再次检查是否已处理(双重检查锁定模式) + if (isOrderProcessed(orderId)) { + logger.info("订单在获取锁后检查发现已处理,跳过 - 订单ID: {}", orderId); + return true; + } + + // 检查物流链接 + String logisticsLink = order.getLogisticsLink(); + if (logisticsLink == null || logisticsLink.trim().isEmpty()) { + logger.info("订单暂无物流链接,跳过处理 - 订单ID: {}", orderId); + return false; + } // 构建外部接口URL String externalUrl = EXTERNAL_API_URL + URLEncoder.encode(logisticsLink, "UTF-8"); - logger.info("调用外部接口获取物流信息 - 订单ID: {}, URL: {}", order.getId(), externalUrl); + logger.info("调用外部接口获取物流信息 - 订单ID: {}, URL: {}", orderId, externalUrl); // 在服务端执行HTTP请求 String result = HttpUtils.sendGet(externalUrl); if (result == null || result.trim().isEmpty()) { - logger.warn("外部接口返回空结果 - 订单ID: {}", order.getId()); + logger.warn("外部接口返回空结果 - 订单ID: {}", orderId); return false; } - logger.info("外部接口调用成功 - 订单ID: {}, 返回数据长度: {}", order.getId(), result.length()); + logger.info("外部接口调用成功 - 订单ID: {}, 返回数据长度: {}", orderId, result.length()); // 解析返回结果 JSONObject parsedData = null; @@ -80,46 +104,59 @@ public class LogisticsServiceImpl implements ILogisticsService { if (parsed instanceof JSONObject) { parsedData = (JSONObject) parsed; } else { - logger.warn("返回数据不是JSON对象格式 - 订单ID: {}", order.getId()); + logger.warn("返回数据不是JSON对象格式 - 订单ID: {}", orderId); return false; } } catch (Exception e) { - logger.warn("解析返回数据失败 - 订单ID: {}, 错误: {}", order.getId(), e.getMessage()); + logger.warn("解析返回数据失败 - 订单ID: {}, 错误: {}", orderId, e.getMessage()); return false; } // 检查waybill_no JSONObject dataObj = parsedData.getJSONObject("data"); if (dataObj == null) { - logger.info("返回数据中没有data字段 - 订单ID: {}", order.getId()); + logger.info("返回数据中没有data字段 - 订单ID: {}", orderId); return false; } String waybillNo = dataObj.getString("waybill_no"); if (waybillNo == null || waybillNo.trim().isEmpty()) { - logger.info("waybill_no为空,无需处理 - 订单ID: {}", order.getId()); + logger.info("waybill_no为空,无需处理 - 订单ID: {}", orderId); return false; } - logger.info("检测到waybill_no: {} - 订单ID: {}", waybillNo, order.getId()); + logger.info("检测到waybill_no: {} - 订单ID: {}", waybillNo, orderId); // 调用企业应用推送,只有推送成功才记录状态 boolean pushSuccess = sendEnterprisePushNotification(order, waybillNo); if (!pushSuccess) { - logger.warn("企业微信推送未确认成功,稍后将重试 - 订单ID: {}, waybill_no: {}", order.getId(), waybillNo); + logger.warn("企业微信推送未确认成功,稍后将重试 - 订单ID: {}, waybill_no: {}", orderId, waybillNo); return false; } - // 保存运单号到Redis(避免重复处理) - String redisKey = REDIS_WAYBILL_KEY_PREFIX + order.getId(); - stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS); + // 保存运单号到Redis(避免重复处理)- 使用原子操作确保只写入一次 + String redisKey = REDIS_WAYBILL_KEY_PREFIX + orderId; + Boolean setSuccess = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, waybillNo, 30, TimeUnit.DAYS); - logger.info("物流信息获取并推送成功 - 订单ID: {}, waybill_no: {}", order.getId(), waybillNo); + if (Boolean.FALSE.equals(setSuccess)) { + // 如果Redis中已存在,说明可能被其他线程处理了,记录警告但不算失败 + logger.warn("订单运单号已存在(可能被并发处理),但推送已成功 - 订单ID: {}, waybill_no: {}", orderId, waybillNo); + } + + logger.info("物流信息获取并推送成功 - 订单ID: {}, waybill_no: {}", orderId, waybillNo); return true; } catch (Exception e) { - logger.error("获取物流信息失败 - 订单ID: {}, 错误: {}", order.getId(), e.getMessage(), e); + logger.error("获取物流信息失败 - 订单ID: {}, 错误: {}", orderId, e.getMessage(), e); return false; + } finally { + // 释放分布式锁 + try { + stringRedisTemplate.delete(lockKey); + logger.debug("释放订单处理锁 - 订单ID: {}", orderId); + } catch (Exception e) { + logger.warn("释放订单处理锁失败 - 订单ID: {}, 错误: {}", orderId, e.getMessage()); + } } } @@ -238,31 +275,64 @@ public class LogisticsServiceImpl implements ILogisticsService { * @return 是否成功 */ private boolean isPushResponseSuccess(String pushResult) { + if (pushResult == null || pushResult.trim().isEmpty()) { + logger.warn("推送响应为空,视为失败"); + return false; + } + try { JSONObject response = JSON.parseObject(pushResult); if (response == null) { + logger.warn("推送响应解析为null,视为失败。原始响应: {}", pushResult); return false; } + Integer code = response.getInteger("code"); Boolean successFlag = response.getBoolean("success"); String status = response.getString("status"); String message = response.getString("msg"); + String errcode = response.getString("errcode"); + // 记录完整的响应信息用于调试 + logger.debug("推送响应解析 - code: {}, success: {}, status: {}, msg: {}, errcode: {}", + code, successFlag, status, message, errcode); + + // 检查错误码(errcode为0表示成功) + if (errcode != null && "0".equals(errcode)) { + logger.info("推送成功(通过errcode=0判断)"); + return true; + } + + // 检查code字段(0或200表示成功) if (code != null && (code == 0 || code == 200)) { + logger.info("推送成功(通过code={}判断)", code); return true; } + + // 检查success字段 if (Boolean.TRUE.equals(successFlag)) { + logger.info("推送成功(通过success=true判断)"); return true; } + + // 检查status字段 if (status != null && ("success".equalsIgnoreCase(status) || "ok".equalsIgnoreCase(status))) { + logger.info("推送成功(通过status={}判断)", status); return true; } + + // 检查message字段(某些接口可能用message表示成功) if (message != null && ("success".equalsIgnoreCase(message) || "ok".equalsIgnoreCase(message))) { + logger.info("推送成功(通过message={}判断)", message); return true; } + + // 如果所有判断都失败,记录详细信息 + logger.warn("推送响应未确认成功 - code: {}, success: {}, status: {}, msg: {}, errcode: {}, 完整响应: {}", + code, successFlag, status, message, errcode, pushResult); return false; } catch (Exception e) { - logger.warn("解析企业应用推送响应失败,将视为未成功: {}", e.getMessage()); + logger.error("解析企业应用推送响应失败,将视为未成功。原始响应: {}, 错误: {}", pushResult, e.getMessage(), e); return false; } }