1
This commit is contained in:
@@ -10,6 +10,7 @@ import com.ruoyi.common.utils.http.HttpUtils;
|
||||
import com.ruoyi.jarvis.domain.JDOrder;
|
||||
import com.ruoyi.jarvis.domain.OrderRows;
|
||||
import com.ruoyi.jarvis.service.IJDOrderService;
|
||||
import com.ruoyi.jarvis.service.ILogisticsService;
|
||||
import com.ruoyi.jarvis.service.IOrderRowsService;
|
||||
import com.ruoyi.jarvis.service.IGiftCouponService;
|
||||
import com.ruoyi.jarvis.domain.GiftCoupon;
|
||||
@@ -41,6 +42,7 @@ public class JDOrderController extends BaseController {
|
||||
private final IOrderRowsService orderRowsService;
|
||||
private final IGiftCouponService giftCouponService;
|
||||
private final ISysConfigService sysConfigService;
|
||||
private final ILogisticsService logisticsService;
|
||||
private static final String CONFIG_KEY_PREFIX = "logistics.push.touser.";
|
||||
private static final java.util.regex.Pattern URL_DETECT_PATTERN = java.util.regex.Pattern.compile(
|
||||
"(https?://[^\\s]+)|(u\\.jd\\.com/[^\\s]+)",
|
||||
@@ -53,11 +55,13 @@ public class JDOrderController extends BaseController {
|
||||
java.util.regex.Pattern.CASE_INSENSITIVE);
|
||||
|
||||
public JDOrderController(IJDOrderService jdOrderService, IOrderRowsService orderRowsService,
|
||||
IGiftCouponService giftCouponService, ISysConfigService sysConfigService) {
|
||||
IGiftCouponService giftCouponService, ISysConfigService sysConfigService,
|
||||
ILogisticsService logisticsService) {
|
||||
this.jdOrderService = jdOrderService;
|
||||
this.orderRowsService = orderRowsService;
|
||||
this.giftCouponService = giftCouponService;
|
||||
this.sysConfigService = sysConfigService;
|
||||
this.logisticsService = logisticsService;
|
||||
}
|
||||
|
||||
private final static String skey = "2192057370ef8140c201079969c956a3";
|
||||
@@ -68,12 +72,6 @@ public class JDOrderController extends BaseController {
|
||||
@Value("${jarvis.server.jarvis-java.jd-api-path:/jd}")
|
||||
private String jdApiPath;
|
||||
|
||||
@Value("${jarvis.server.logistics.base-url:http://127.0.0.1:5001}")
|
||||
private String logisticsBaseUrl;
|
||||
|
||||
@Value("${jarvis.server.logistics.fetch-path:/fetch_logistics}")
|
||||
private String logisticsFetchPath;
|
||||
|
||||
/**
|
||||
* 获取JD接口请求URL
|
||||
*/
|
||||
@@ -947,9 +945,7 @@ public class JDOrderController extends BaseController {
|
||||
logger.info("手动获取物流信息 - 订单ID: {}, 订单号: {}, 分销标识: {}, 物流链接: {}",
|
||||
orderId, order.getOrderId(), distributionMark, logisticsLink);
|
||||
|
||||
// 构建外部接口URL
|
||||
String externalUrl = logisticsBaseUrl + logisticsFetchPath + "?tracking_url=" +
|
||||
java.net.URLEncoder.encode(logisticsLink, "UTF-8");
|
||||
String externalUrl = logisticsService.buildFetchLogisticsRequestUrl(logisticsLink);
|
||||
|
||||
logger.info("准备调用外部接口 - URL: {}", externalUrl);
|
||||
|
||||
|
||||
@@ -200,6 +200,8 @@ jarvis:
|
||||
# 物流接口服务地址
|
||||
logistics:
|
||||
base-url: http://192.168.8.88:5001
|
||||
# 同机多进程多端口时配置逗号分隔列表;非空时仅按下列地址轮询,不再使用 base-url
|
||||
base-urls: http://192.168.8.88:5001,http://192.168.8.88:5002,http://192.168.8.88:5003
|
||||
fetch-path: /fetch_logistics
|
||||
health-path: /health
|
||||
# 每次定时任务最多处理多少条企微分享链待队列(RPUSH 入队、LPOP 出队)
|
||||
|
||||
@@ -200,6 +200,8 @@ jarvis:
|
||||
# 物流接口服务地址
|
||||
logistics:
|
||||
base-url: http://127.0.0.1:5001
|
||||
# 同机多进程多端口时配置逗号分隔列表;非空时仅按下列地址轮询,不再使用 base-url
|
||||
base-urls: http://127.0.0.1:5001,http://127.0.0.1:5002,http://127.0.0.1:5003
|
||||
fetch-path: /fetch_logistics
|
||||
health-path: /health
|
||||
adhoc-pending-batch-size: 50
|
||||
|
||||
@@ -69,6 +69,14 @@ public interface ILogisticsService {
|
||||
*/
|
||||
HealthCheckResult checkHealth();
|
||||
|
||||
/**
|
||||
* 构造调用物流解析服务的完整 GET URL(路径与编码与 {@link #fetchLogisticsAndPush} 一致)。
|
||||
* 配置多个 {@code jarvis.server.logistics.base-urls} 时按轮询选取 base,便于内网多实例并行。
|
||||
*
|
||||
* @param logisticsLink 原始物流追踪链接(未编码)
|
||||
*/
|
||||
String buildFetchLogisticsRequestUrl(String logisticsLink);
|
||||
|
||||
/**
|
||||
* 健康检测结果
|
||||
*/
|
||||
|
||||
@@ -21,13 +21,16 @@ import javax.annotation.Resource;
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.springframework.util.DigestUtils;
|
||||
|
||||
@@ -59,6 +62,10 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
@Value("${jarvis.server.logistics.base-url:http://127.0.0.1:5001}")
|
||||
private String logisticsBaseUrl;
|
||||
|
||||
/** 逗号分隔的多个解析服务 base,非空时优先于此列表轮询;例如 http://10.0.0.1:5001,http://10.0.0.2:5001 */
|
||||
@Value("${jarvis.server.logistics.base-urls:}")
|
||||
private String logisticsBaseUrlsRaw;
|
||||
|
||||
@Value("${jarvis.server.logistics.fetch-path:/fetch_logistics}")
|
||||
private String logisticsFetchPath;
|
||||
|
||||
@@ -71,8 +78,10 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
@Resource
|
||||
private WeComShareLinkLogisticsJobMapper weComShareLinkLogisticsJobMapper;
|
||||
|
||||
private String externalApiUrlTemplate;
|
||||
private String healthCheckUrl;
|
||||
/** 已规范化(无尾部斜杠)的物流解析服务 base 列表,至少一项 */
|
||||
private List<String> logisticsServiceBases = Collections.emptyList();
|
||||
private String logisticsServiceBasesSummary = "";
|
||||
private final AtomicInteger logisticsBaseRoundRobin = new AtomicInteger(0);
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
@@ -88,10 +97,79 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
externalApiUrlTemplate = logisticsBaseUrl + logisticsFetchPath + "?tracking_url=";
|
||||
healthCheckUrl = logisticsBaseUrl + logisticsHealthPath;
|
||||
logger.info("物流服务地址已初始化: {}", externalApiUrlTemplate);
|
||||
logger.info("物流服务健康检查地址已初始化: {}", healthCheckUrl);
|
||||
List<String> list = new ArrayList<>();
|
||||
if (StringUtils.hasText(logisticsBaseUrlsRaw)) {
|
||||
for (String part : logisticsBaseUrlsRaw.split(",")) {
|
||||
String n = normalizeLogisticsBaseUrl(part);
|
||||
if (StringUtils.hasText(n)) {
|
||||
list.add(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
list.add(normalizeLogisticsBaseUrl(logisticsBaseUrl));
|
||||
}
|
||||
logisticsServiceBases = Collections.unmodifiableList(list);
|
||||
logisticsServiceBasesSummary = String.join(", ", logisticsServiceBases);
|
||||
logger.info("物流服务实例 {} 个(轮询): {}", logisticsServiceBases.size(), logisticsServiceBasesSummary);
|
||||
}
|
||||
|
||||
private static String normalizeLogisticsBaseUrl(String base) {
|
||||
if (!StringUtils.hasText(base)) {
|
||||
return "";
|
||||
}
|
||||
String t = base.trim();
|
||||
while (t.endsWith("/")) {
|
||||
t = t.substring(0, t.length() - 1);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
private String pickLogisticsBaseUrl() {
|
||||
int n = logisticsServiceBases.size();
|
||||
if (n == 0) {
|
||||
return normalizeLogisticsBaseUrl(logisticsBaseUrl);
|
||||
}
|
||||
if (n == 1) {
|
||||
return logisticsServiceBases.get(0);
|
||||
}
|
||||
int idx = Math.floorMod(logisticsBaseRoundRobin.getAndIncrement(), n);
|
||||
return logisticsServiceBases.get(idx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildFetchLogisticsRequestUrl(String logisticsLink) {
|
||||
String base = pickLogisticsBaseUrl();
|
||||
try {
|
||||
return base + logisticsFetchPath + "?tracking_url=" + URLEncoder.encode(logisticsLink, "UTF-8");
|
||||
} catch (java.io.UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 与原先单 URL 健康检查语义一致:JSON 字段命中则通过;非 JSON 时仅当正文含 ok/healthy/success 子串则通过。
|
||||
*/
|
||||
private boolean isHealthResponseBodyHealthy(String healthResult) {
|
||||
if (healthResult == null || healthResult.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
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)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
String lowerResult = healthResult.toLowerCase();
|
||||
return lowerResult.contains("ok") || lowerResult.contains("healthy") || lowerResult.contains("success");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,46 +183,26 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
|
||||
@Override
|
||||
public ILogisticsService.HealthCheckResult checkHealth() {
|
||||
try {
|
||||
logger.debug("开始检查物流服务健康状态 - URL: {}", healthCheckUrl);
|
||||
String healthResult = HttpUtils.sendGet(healthCheckUrl);
|
||||
|
||||
if (healthResult == null || healthResult.trim().isEmpty()) {
|
||||
logger.warn("物流服务健康检查返回空结果");
|
||||
return new ILogisticsService.HealthCheckResult(false, "异常", "健康检查返回空结果", healthCheckUrl);
|
||||
}
|
||||
|
||||
// 尝试解析JSON响应
|
||||
List<String> errors = new ArrayList<>();
|
||||
for (String base : logisticsServiceBases) {
|
||||
String url = base + logisticsHealthPath;
|
||||
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("物流服务健康检查通过");
|
||||
return new ILogisticsService.HealthCheckResult(true, "正常", "服务运行正常", healthCheckUrl);
|
||||
}
|
||||
logger.debug("开始检查物流服务健康状态 - URL: {}", url);
|
||||
String healthResult = HttpUtils.sendGet(url);
|
||||
if (isHealthResponseBodyHealthy(healthResult)) {
|
||||
logger.debug("物流服务健康检查通过 - {}", url);
|
||||
return new ILogisticsService.HealthCheckResult(true, "正常", "服务运行正常: " + url,
|
||||
logisticsServiceBasesSummary);
|
||||
}
|
||||
logger.warn("物流服务健康检查失败 - URL: {} 响应: {}", url, healthResult);
|
||||
errors.add(url + " -> " + (healthResult == null || healthResult.isEmpty() ? "空响应" : "状态异常"));
|
||||
} catch (Exception e) {
|
||||
// 如果不是JSON格式,检查是否包含成功标识
|
||||
String lowerResult = healthResult.toLowerCase();
|
||||
if (lowerResult.contains("ok") || lowerResult.contains("healthy") || lowerResult.contains("success")) {
|
||||
logger.debug("物流服务健康检查通过(非JSON格式)");
|
||||
return new ILogisticsService.HealthCheckResult(true, "正常", "服务运行正常", healthCheckUrl);
|
||||
}
|
||||
logger.error("物流服务健康检查异常 - URL: {}, 错误: {}", url, e.getMessage(), e);
|
||||
errors.add(url + " -> " + e.getMessage());
|
||||
}
|
||||
|
||||
logger.warn("物流服务健康检查失败 - 响应: {}", healthResult);
|
||||
return new ILogisticsService.HealthCheckResult(false, "异常", "健康检查返回异常状态: " + healthResult, healthCheckUrl);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("物流服务健康检查异常 - URL: {}, 错误: {}", healthCheckUrl, e.getMessage(), e);
|
||||
return new ILogisticsService.HealthCheckResult(false, "异常", "健康检查异常: " + e.getMessage(), healthCheckUrl);
|
||||
}
|
||||
String msg = errors.isEmpty() ? "未配置物流解析实例" : String.join("; ", errors);
|
||||
return new ILogisticsService.HealthCheckResult(false, "异常", msg, logisticsServiceBasesSummary);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +241,7 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
// 构建推送消息
|
||||
StringBuilder pushContent = new StringBuilder();
|
||||
pushContent.append("【物流服务异常提醒】\n");
|
||||
pushContent.append("服务地址:").append(healthCheckUrl).append("\n");
|
||||
pushContent.append("服务实例:").append(logisticsServiceBasesSummary).append("\n");
|
||||
pushContent.append("失败原因:").append(reason).append("\n");
|
||||
pushContent.append("时间:").append(new Date()).append("\n");
|
||||
pushContent.append("请及时检查服务状态!");
|
||||
@@ -277,8 +335,7 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 构建外部接口URL
|
||||
String externalUrl = externalApiUrlTemplate + URLEncoder.encode(logisticsLink, "UTF-8");
|
||||
String externalUrl = buildFetchLogisticsRequestUrl(logisticsLink);
|
||||
logger.info("调用外部接口获取物流信息 - 订单ID: {}, URL: {}", orderId, externalUrl);
|
||||
|
||||
// 在服务端执行HTTP请求
|
||||
@@ -700,7 +757,7 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
if (debug != null) {
|
||||
debug.put("healthOk", true);
|
||||
}
|
||||
String externalUrl = externalApiUrlTemplate + URLEncoder.encode(url, "UTF-8");
|
||||
String externalUrl = buildFetchLogisticsRequestUrl(url);
|
||||
if (debug != null) {
|
||||
debug.put("requestUrl", externalUrl);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user