diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 7e986ec..64d42df 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -199,6 +199,7 @@ jarvis: logistics: base-url: http://192.168.8.88:5001 fetch-path: /fetch_logistics + health-path: /health # 腾讯文档开放平台配置 # 文档地址:https://docs.qq.com/open/document/app/openapi/v3/sheet/model/spreadsheet.html tencent: diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml index f2e51dd..0dd67ae 100644 --- a/ruoyi-admin/src/main/resources/application-prod.yml +++ b/ruoyi-admin/src/main/resources/application-prod.yml @@ -199,6 +199,7 @@ jarvis: logistics: base-url: http://127.0.0.1:5001 fetch-path: /fetch_logistics + health-path: /health # 腾讯文档开放平台配置 # 文档地址:https://docs.qq.com/open/document/app/openapi/v3/sheet/model/spreadsheet.html tencent: 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 ac8abb4..1709952 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 @@ -29,10 +29,12 @@ public class LogisticsServiceImpl implements ILogisticsService { 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 REDIS_HEALTH_CHECK_ALERT_KEY = "logistics:health:alert:"; 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分钟,防止死锁 + private static final long HEALTH_CHECK_ALERT_INTERVAL_MINUTES = 30; // 健康检查失败提醒间隔30分钟,避免频繁推送 @Value("${jarvis.server.logistics.base-url:http://127.0.0.1:5001}") private String logisticsBaseUrl; @@ -40,7 +42,11 @@ public class LogisticsServiceImpl implements ILogisticsService { @Value("${jarvis.server.logistics.fetch-path:/fetch_logistics}") private String logisticsFetchPath; + @Value("${jarvis.server.logistics.health-path:/health}") + private String logisticsHealthPath; + private String externalApiUrlTemplate; + private String healthCheckUrl; @Resource private StringRedisTemplate stringRedisTemplate; @@ -51,7 +57,9 @@ public class LogisticsServiceImpl implements ILogisticsService { @PostConstruct public void init() { externalApiUrlTemplate = logisticsBaseUrl + logisticsFetchPath + "?tracking_url="; + healthCheckUrl = logisticsBaseUrl + logisticsHealthPath; logger.info("物流服务地址已初始化: {}", externalApiUrlTemplate); + logger.info("物流服务健康检查地址已初始化: {}", healthCheckUrl); } @Override @@ -63,6 +71,114 @@ public class LogisticsServiceImpl implements ILogisticsService { return stringRedisTemplate.hasKey(redisKey); } + /** + * 检查物流服务健康状态 + * @return 是否健康 + */ + private boolean checkLogisticsServiceHealth() { + try { + logger.debug("开始检查物流服务健康状态 - URL: {}", healthCheckUrl); + String healthResult = HttpUtils.sendGet(healthCheckUrl); + + if (healthResult == null || healthResult.trim().isEmpty()) { + logger.warn("物流服务健康检查返回空结果"); + handleHealthCheckFailure("健康检查返回空结果"); + return false; + } + + // 尝试解析JSON响应 + try { + JSONObject response = JSON.parseObject(healthResult); + if (response != null) { + // 检查常见的健康状态字段 + String status = response.getString("status"); + Boolean healthy = response.getBoolean("healthy"); + Integer code = response.getInteger("code"); + + if ("ok".equalsIgnoreCase(status) || "healthy".equalsIgnoreCase(status) || + Boolean.TRUE.equals(healthy) || (code != null && code == 200)) { + logger.debug("物流服务健康检查通过"); + // 清除健康检查失败标记 + stringRedisTemplate.delete(REDIS_HEALTH_CHECK_ALERT_KEY); + return true; + } + } + } catch (Exception e) { + // 如果不是JSON格式,检查是否包含成功标识 + String lowerResult = healthResult.toLowerCase(); + if (lowerResult.contains("ok") || lowerResult.contains("healthy") || lowerResult.contains("success")) { + logger.debug("物流服务健康检查通过(非JSON格式)"); + stringRedisTemplate.delete(REDIS_HEALTH_CHECK_ALERT_KEY); + return true; + } + } + + logger.warn("物流服务健康检查失败 - 响应: {}", healthResult); + handleHealthCheckFailure("健康检查返回异常状态: " + healthResult); + return false; + + } catch (Exception e) { + logger.error("物流服务健康检查异常 - URL: {}, 错误: {}", healthCheckUrl, e.getMessage(), e); + handleHealthCheckFailure("健康检查异常: " + e.getMessage()); + return false; + } + } + + /** + * 处理健康检查失败,推送提醒消息 + * @param reason 失败原因 + */ + private void handleHealthCheckFailure(String reason) { + try { + // 检查是否在提醒间隔内已经推送过 + String alertKey = REDIS_HEALTH_CHECK_ALERT_KEY; + String lastAlertTime = stringRedisTemplate.opsForValue().get(alertKey); + + if (lastAlertTime != null) { + // 如果30分钟内已经推送过,不再重复推送 + logger.debug("健康检查失败提醒已在30分钟内推送过,跳过本次推送"); + return; + } + + // 构建推送消息 + StringBuilder pushContent = new StringBuilder(); + pushContent.append("【物流服务异常提醒】\n"); + pushContent.append("服务地址:").append(healthCheckUrl).append("\n"); + pushContent.append("失败原因:").append(reason).append("\n"); + pushContent.append("时间:").append(new Date()).append("\n"); + pushContent.append("请及时检查服务状态!"); + + JSONObject pushParam = new JSONObject(); + pushParam.put("title", "物流服务健康检查失败"); + pushParam.put("text", pushContent.toString()); + + // 尝试获取系统管理员接收人配置,如果没有则使用默认接收人 + String adminTouser = sysConfigService.selectConfigByKey("logistics.push.touser.ADMIN"); + if (StringUtils.hasText(adminTouser)) { + pushParam.put("touser", adminTouser.trim()); + logger.info("使用管理员接收人配置推送健康检查失败提醒"); + } else { + logger.info("未配置管理员接收人,将使用远程接口默认接收人推送健康检查失败提醒"); + } + + String jsonBody = pushParam.toJSONString(); + String pushResult = sendPostWithHeaders(PUSH_URL, jsonBody, PUSH_TOKEN); + + if (pushResult != null && !pushResult.trim().isEmpty()) { + logger.info("健康检查失败提醒已推送 - 响应: {}", pushResult); + // 记录推送时间,30分钟内不再重复推送 + Long currentTime = System.currentTimeMillis(); + stringRedisTemplate.opsForValue().set(alertKey, currentTime.toString(), + HEALTH_CHECK_ALERT_INTERVAL_MINUTES, TimeUnit.MINUTES); + } else { + logger.warn("健康检查失败提醒推送失败 - 响应为空"); + } + + } catch (Exception e) { + logger.error("推送健康检查失败提醒异常", e); + } + } + @Override public boolean fetchLogisticsAndPush(JDOrder order) { if (order == null || order.getId() == null) { @@ -100,6 +216,14 @@ public class LogisticsServiceImpl implements ILogisticsService { logger.info("订单暂无物流链接,跳过处理 - 订单ID: {}", orderId); return false; } + + // 先进行健康检查 + boolean healthCheckPassed = checkLogisticsServiceHealth(); + if (!healthCheckPassed) { + logger.error("物流服务健康检查失败,跳过处理订单 - 订单ID: {}", orderId); + return false; + } + // 构建外部接口URL String externalUrl = externalApiUrlTemplate + URLEncoder.encode(logisticsLink, "UTF-8"); logger.info("调用外部接口获取物流信息 - 订单ID: {}, URL: {}", orderId, externalUrl);