Compare commits

...

2 Commits

Author SHA1 Message Date
Leo
9206824efb 1 2025-12-05 22:20:17 +08:00
Leo
2524461ff4 1 2025-12-05 22:16:25 +08:00

View File

@@ -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());
}
}
}
@@ -162,7 +199,8 @@ public class LogisticsServiceImpl implements ILogisticsService {
logger.info("企业微信推送设置接收人 - 订单ID: {}, 分销标识: {}, 接收人: {}",
order.getId(), distributionMark, touser);
} else {
logger.warn("未找到分销标识对应的接收人配置 - 订单ID: {}, 分销标识: {}",
// 未配置接收人时,使用远程接口的默认接收人,这是正常情况
logger.info("未找到分销标识对应的接收人配置,将使用远程接口默认接收人 - 订单ID: {}, 分销标识: {}",
order.getId(), distributionMark);
}
@@ -238,31 +276,89 @@ 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;
}
// 检查是否包含明确的错误标识
String responseStr = pushResult.toLowerCase();
boolean hasErrorKeyword = responseStr.contains("\"error\"") || responseStr.contains("\"fail\"") ||
responseStr.contains("\"failed\"") || responseStr.contains("\"errmsg\"");
// 如果包含错误标识,检查是否有明确的错误码
if (hasErrorKeyword) {
if (code != null && code < 0) {
logger.warn("推送失败(检测到错误标识和负错误码) - code: {}, 完整响应: {}", code, pushResult);
return false;
}
if (errcode != null && !"0".equals(errcode) && !errcode.isEmpty()) {
logger.warn("推送失败(检测到错误标识和非零错误码) - errcode: {}, 完整响应: {}", errcode, pushResult);
return false;
}
}
// 如果响应是有效的JSON且没有明确的错误标识视为成功
// 因为远程接口有默认接收人,即使没有配置接收人,推送也应该成功
// 如果响应格式特殊(不是标准格式),只要没有错误,也视为成功
if (!hasErrorKeyword) {
logger.info("推送成功(响应格式有效且无错误标识,使用默认接收人) - 完整响应: {}", pushResult);
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;
}
}