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.JDOrder;
|
||||||
import com.ruoyi.jarvis.domain.OrderRows;
|
import com.ruoyi.jarvis.domain.OrderRows;
|
||||||
import com.ruoyi.jarvis.service.IJDOrderService;
|
import com.ruoyi.jarvis.service.IJDOrderService;
|
||||||
|
import com.ruoyi.jarvis.service.ILogisticsService;
|
||||||
import com.ruoyi.jarvis.service.IOrderRowsService;
|
import com.ruoyi.jarvis.service.IOrderRowsService;
|
||||||
import com.ruoyi.jarvis.service.IGiftCouponService;
|
import com.ruoyi.jarvis.service.IGiftCouponService;
|
||||||
import com.ruoyi.jarvis.domain.GiftCoupon;
|
import com.ruoyi.jarvis.domain.GiftCoupon;
|
||||||
@@ -41,6 +42,7 @@ public class JDOrderController extends BaseController {
|
|||||||
private final IOrderRowsService orderRowsService;
|
private final IOrderRowsService orderRowsService;
|
||||||
private final IGiftCouponService giftCouponService;
|
private final IGiftCouponService giftCouponService;
|
||||||
private final ISysConfigService sysConfigService;
|
private final ISysConfigService sysConfigService;
|
||||||
|
private final ILogisticsService logisticsService;
|
||||||
private static final String CONFIG_KEY_PREFIX = "logistics.push.touser.";
|
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(
|
private static final java.util.regex.Pattern URL_DETECT_PATTERN = java.util.regex.Pattern.compile(
|
||||||
"(https?://[^\\s]+)|(u\\.jd\\.com/[^\\s]+)",
|
"(https?://[^\\s]+)|(u\\.jd\\.com/[^\\s]+)",
|
||||||
@@ -53,11 +55,13 @@ public class JDOrderController extends BaseController {
|
|||||||
java.util.regex.Pattern.CASE_INSENSITIVE);
|
java.util.regex.Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
public JDOrderController(IJDOrderService jdOrderService, IOrderRowsService orderRowsService,
|
public JDOrderController(IJDOrderService jdOrderService, IOrderRowsService orderRowsService,
|
||||||
IGiftCouponService giftCouponService, ISysConfigService sysConfigService) {
|
IGiftCouponService giftCouponService, ISysConfigService sysConfigService,
|
||||||
|
ILogisticsService logisticsService) {
|
||||||
this.jdOrderService = jdOrderService;
|
this.jdOrderService = jdOrderService;
|
||||||
this.orderRowsService = orderRowsService;
|
this.orderRowsService = orderRowsService;
|
||||||
this.giftCouponService = giftCouponService;
|
this.giftCouponService = giftCouponService;
|
||||||
this.sysConfigService = sysConfigService;
|
this.sysConfigService = sysConfigService;
|
||||||
|
this.logisticsService = logisticsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static String skey = "2192057370ef8140c201079969c956a3";
|
private final static String skey = "2192057370ef8140c201079969c956a3";
|
||||||
@@ -68,12 +72,6 @@ public class JDOrderController extends BaseController {
|
|||||||
@Value("${jarvis.server.jarvis-java.jd-api-path:/jd}")
|
@Value("${jarvis.server.jarvis-java.jd-api-path:/jd}")
|
||||||
private String jdApiPath;
|
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
|
* 获取JD接口请求URL
|
||||||
*/
|
*/
|
||||||
@@ -947,9 +945,7 @@ public class JDOrderController extends BaseController {
|
|||||||
logger.info("手动获取物流信息 - 订单ID: {}, 订单号: {}, 分销标识: {}, 物流链接: {}",
|
logger.info("手动获取物流信息 - 订单ID: {}, 订单号: {}, 分销标识: {}, 物流链接: {}",
|
||||||
orderId, order.getOrderId(), distributionMark, logisticsLink);
|
orderId, order.getOrderId(), distributionMark, logisticsLink);
|
||||||
|
|
||||||
// 构建外部接口URL
|
String externalUrl = logisticsService.buildFetchLogisticsRequestUrl(logisticsLink);
|
||||||
String externalUrl = logisticsBaseUrl + logisticsFetchPath + "?tracking_url=" +
|
|
||||||
java.net.URLEncoder.encode(logisticsLink, "UTF-8");
|
|
||||||
|
|
||||||
logger.info("准备调用外部接口 - URL: {}", externalUrl);
|
logger.info("准备调用外部接口 - URL: {}", externalUrl);
|
||||||
|
|
||||||
|
|||||||
@@ -200,6 +200,8 @@ jarvis:
|
|||||||
# 物流接口服务地址
|
# 物流接口服务地址
|
||||||
logistics:
|
logistics:
|
||||||
base-url: http://192.168.8.88:5001
|
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
|
fetch-path: /fetch_logistics
|
||||||
health-path: /health
|
health-path: /health
|
||||||
# 每次定时任务最多处理多少条企微分享链待队列(RPUSH 入队、LPOP 出队)
|
# 每次定时任务最多处理多少条企微分享链待队列(RPUSH 入队、LPOP 出队)
|
||||||
|
|||||||
@@ -200,6 +200,8 @@ jarvis:
|
|||||||
# 物流接口服务地址
|
# 物流接口服务地址
|
||||||
logistics:
|
logistics:
|
||||||
base-url: http://127.0.0.1:5001
|
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
|
fetch-path: /fetch_logistics
|
||||||
health-path: /health
|
health-path: /health
|
||||||
adhoc-pending-batch-size: 50
|
adhoc-pending-batch-size: 50
|
||||||
|
|||||||
@@ -68,6 +68,14 @@ public interface ILogisticsService {
|
|||||||
* @return 健康状态信息,包含是否健康、状态描述等
|
* @return 健康状态信息,包含是否健康、状态描述等
|
||||||
*/
|
*/
|
||||||
HealthCheckResult checkHealth();
|
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 javax.annotation.PostConstruct;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.springframework.util.DigestUtils;
|
import org.springframework.util.DigestUtils;
|
||||||
|
|
||||||
@@ -58,6 +61,10 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
|
|
||||||
@Value("${jarvis.server.logistics.base-url:http://127.0.0.1:5001}")
|
@Value("${jarvis.server.logistics.base-url:http://127.0.0.1:5001}")
|
||||||
private String logisticsBaseUrl;
|
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}")
|
@Value("${jarvis.server.logistics.fetch-path:/fetch_logistics}")
|
||||||
private String logisticsFetchPath;
|
private String logisticsFetchPath;
|
||||||
@@ -71,8 +78,10 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
@Resource
|
@Resource
|
||||||
private WeComShareLinkLogisticsJobMapper weComShareLinkLogisticsJobMapper;
|
private WeComShareLinkLogisticsJobMapper weComShareLinkLogisticsJobMapper;
|
||||||
|
|
||||||
private String externalApiUrlTemplate;
|
/** 已规范化(无尾部斜杠)的物流解析服务 base 列表,至少一项 */
|
||||||
private String healthCheckUrl;
|
private List<String> logisticsServiceBases = Collections.emptyList();
|
||||||
|
private String logisticsServiceBasesSummary = "";
|
||||||
|
private final AtomicInteger logisticsBaseRoundRobin = new AtomicInteger(0);
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private StringRedisTemplate stringRedisTemplate;
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
@@ -88,10 +97,79 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
externalApiUrlTemplate = logisticsBaseUrl + logisticsFetchPath + "?tracking_url=";
|
List<String> list = new ArrayList<>();
|
||||||
healthCheckUrl = logisticsBaseUrl + logisticsHealthPath;
|
if (StringUtils.hasText(logisticsBaseUrlsRaw)) {
|
||||||
logger.info("物流服务地址已初始化: {}", externalApiUrlTemplate);
|
for (String part : logisticsBaseUrlsRaw.split(",")) {
|
||||||
logger.info("物流服务健康检查地址已初始化: {}", healthCheckUrl);
|
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
|
@Override
|
||||||
@@ -105,46 +183,26 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ILogisticsService.HealthCheckResult checkHealth() {
|
public ILogisticsService.HealthCheckResult checkHealth() {
|
||||||
try {
|
List<String> errors = new ArrayList<>();
|
||||||
logger.debug("开始检查物流服务健康状态 - URL: {}", healthCheckUrl);
|
for (String base : logisticsServiceBases) {
|
||||||
String healthResult = HttpUtils.sendGet(healthCheckUrl);
|
String url = base + logisticsHealthPath;
|
||||||
|
|
||||||
if (healthResult == null || healthResult.trim().isEmpty()) {
|
|
||||||
logger.warn("物流服务健康检查返回空结果");
|
|
||||||
return new ILogisticsService.HealthCheckResult(false, "异常", "健康检查返回空结果", healthCheckUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试解析JSON响应
|
|
||||||
try {
|
try {
|
||||||
JSONObject response = JSON.parseObject(healthResult);
|
logger.debug("开始检查物流服务健康状态 - URL: {}", url);
|
||||||
if (response != null) {
|
String healthResult = HttpUtils.sendGet(url);
|
||||||
// 检查常见的健康状态字段
|
if (isHealthResponseBodyHealthy(healthResult)) {
|
||||||
String status = response.getString("status");
|
logger.debug("物流服务健康检查通过 - {}", url);
|
||||||
Boolean healthy = response.getBoolean("healthy");
|
return new ILogisticsService.HealthCheckResult(true, "正常", "服务运行正常: " + url,
|
||||||
Integer code = response.getInteger("code");
|
logisticsServiceBasesSummary);
|
||||||
|
|
||||||
if ("ok".equalsIgnoreCase(status) || "healthy".equalsIgnoreCase(status) ||
|
|
||||||
Boolean.TRUE.equals(healthy) || (code != null && code == 200)) {
|
|
||||||
logger.debug("物流服务健康检查通过");
|
|
||||||
return new ILogisticsService.HealthCheckResult(true, "正常", "服务运行正常", healthCheckUrl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
logger.warn("物流服务健康检查失败 - URL: {} 响应: {}", url, healthResult);
|
||||||
|
errors.add(url + " -> " + (healthResult == null || healthResult.isEmpty() ? "空响应" : "状态异常"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 如果不是JSON格式,检查是否包含成功标识
|
logger.error("物流服务健康检查异常 - URL: {}, 错误: {}", url, e.getMessage(), e);
|
||||||
String lowerResult = healthResult.toLowerCase();
|
errors.add(url + " -> " + e.getMessage());
|
||||||
if (lowerResult.contains("ok") || lowerResult.contains("healthy") || lowerResult.contains("success")) {
|
|
||||||
logger.debug("物流服务健康检查通过(非JSON格式)");
|
|
||||||
return new ILogisticsService.HealthCheckResult(true, "正常", "服务运行正常", healthCheckUrl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
StringBuilder pushContent = new StringBuilder();
|
||||||
pushContent.append("【物流服务异常提醒】\n");
|
pushContent.append("【物流服务异常提醒】\n");
|
||||||
pushContent.append("服务地址:").append(healthCheckUrl).append("\n");
|
pushContent.append("服务实例:").append(logisticsServiceBasesSummary).append("\n");
|
||||||
pushContent.append("失败原因:").append(reason).append("\n");
|
pushContent.append("失败原因:").append(reason).append("\n");
|
||||||
pushContent.append("时间:").append(new Date()).append("\n");
|
pushContent.append("时间:").append(new Date()).append("\n");
|
||||||
pushContent.append("请及时检查服务状态!");
|
pushContent.append("请及时检查服务状态!");
|
||||||
@@ -277,8 +335,7 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建外部接口URL
|
String externalUrl = buildFetchLogisticsRequestUrl(logisticsLink);
|
||||||
String externalUrl = externalApiUrlTemplate + URLEncoder.encode(logisticsLink, "UTF-8");
|
|
||||||
logger.info("调用外部接口获取物流信息 - 订单ID: {}, URL: {}", orderId, externalUrl);
|
logger.info("调用外部接口获取物流信息 - 订单ID: {}, URL: {}", orderId, externalUrl);
|
||||||
|
|
||||||
// 在服务端执行HTTP请求
|
// 在服务端执行HTTP请求
|
||||||
@@ -700,7 +757,7 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
|||||||
if (debug != null) {
|
if (debug != null) {
|
||||||
debug.put("healthOk", true);
|
debug.put("healthOk", true);
|
||||||
}
|
}
|
||||||
String externalUrl = externalApiUrlTemplate + URLEncoder.encode(url, "UTF-8");
|
String externalUrl = buildFetchLogisticsRequestUrl(url);
|
||||||
if (debug != null) {
|
if (debug != null) {
|
||||||
debug.put("requestUrl", externalUrl);
|
debug.put("requestUrl", externalUrl);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user