diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java index f3374fb..b97b980 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java @@ -5,6 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.Environment; +import org.springframework.scheduling.annotation.EnableScheduling; /** * 启动程序 @@ -12,6 +13,7 @@ import org.springframework.core.env.Environment; * @author ruoyi */ @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) +@EnableScheduling public class RuoYiApplication { public static void main(String[] args) diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/JDOrderMapper.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/JDOrderMapper.java index 6c8e45f..71f7a82 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/JDOrderMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/JDOrderMapper.java @@ -45,6 +45,12 @@ public interface JDOrderMapper { * 批量删除(根据主键ID) */ int deleteJDOrderByIds(Long[] ids); + + /** + * 查询分销标记为F或PDD且有物流链接的订单列表 + * @return 订单列表 + */ + List selectJDOrderListByDistributionMarkFOrPDD(); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IJDOrderService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IJDOrderService.java index 21c3b65..fe493c1 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IJDOrderService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IJDOrderService.java @@ -42,6 +42,9 @@ public interface IJDOrderService { /** 批量删除(根据主键ID) */ int deleteJDOrderByIds(Long[] ids); + + /** 查询分销标记为F或PDD且有物流链接的订单列表 */ + java.util.List selectJDOrderListByDistributionMarkFOrPDD(); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/ILogisticsService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/ILogisticsService.java new file mode 100644 index 0000000..3857239 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/ILogisticsService.java @@ -0,0 +1,23 @@ +package com.ruoyi.jarvis.service; + +import com.ruoyi.jarvis.domain.JDOrder; + +/** + * 物流信息服务接口 + */ +public interface ILogisticsService { + /** + * 获取物流信息并推送(如果获取到运单号) + * @param order 订单信息 + * @return 是否成功获取并推送 + */ + boolean fetchLogisticsAndPush(JDOrder order); + + /** + * 检查订单是否已处理过(Redis中是否有运单号) + * @param orderId 订单ID + * @return 如果已处理返回true,否则返回false + */ + boolean isOrderProcessed(Long orderId); +} + diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/JDOrderServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/JDOrderServiceImpl.java index d4267d4..52ee544 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/JDOrderServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/JDOrderServiceImpl.java @@ -66,6 +66,11 @@ public class JDOrderServiceImpl implements IJDOrderService { } return jdOrderMapper.deleteJDOrderByIds(ids); } + + @Override + public List selectJDOrderListByDistributionMarkFOrPDD() { + return jdOrderMapper.selectJDOrderListByDistributionMarkFOrPDD(); + } } 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 new file mode 100644 index 0000000..af55a14 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/LogisticsServiceImpl.java @@ -0,0 +1,213 @@ +package com.ruoyi.jarvis.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.utils.http.HttpUtils; +import com.ruoyi.jarvis.domain.JDOrder; +import com.ruoyi.jarvis.service.ILogisticsService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.net.URLEncoder; +import java.util.concurrent.TimeUnit; + +/** + * 物流信息服务实现类 + */ +@Service +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 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"; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + @Override + public boolean isOrderProcessed(Long orderId) { + if (orderId == null) { + return false; + } + String redisKey = REDIS_WAYBILL_KEY_PREFIX + orderId; + return stringRedisTemplate.hasKey(redisKey); + } + + @Override + public boolean fetchLogisticsAndPush(JDOrder order) { + if (order == null || order.getId() == null) { + logger.warn("订单信息为空,无法获取物流信息"); + return false; + } + + // 检查物流链接 + String logisticsLink = order.getLogisticsLink(); + if (logisticsLink == null || logisticsLink.trim().isEmpty()) { + logger.info("订单暂无物流链接,跳过处理 - 订单ID: {}", order.getId()); + return false; + } + + try { + // 构建外部接口URL + String externalUrl = EXTERNAL_API_URL + URLEncoder.encode(logisticsLink, "UTF-8"); + logger.info("调用外部接口获取物流信息 - 订单ID: {}, URL: {}", order.getId(), externalUrl); + + // 在服务端执行HTTP请求 + String result = HttpUtils.sendGet(externalUrl); + + if (result == null || result.trim().isEmpty()) { + logger.warn("外部接口返回空结果 - 订单ID: {}", order.getId()); + return false; + } + + logger.info("外部接口调用成功 - 订单ID: {}, 返回数据长度: {}", order.getId(), result.length()); + + // 解析返回结果 + JSONObject parsedData = null; + try { + Object parsed = JSON.parse(result); + if (parsed instanceof JSONObject) { + parsedData = (JSONObject) parsed; + } else { + logger.warn("返回数据不是JSON对象格式 - 订单ID: {}", order.getId()); + return false; + } + } catch (Exception e) { + logger.warn("解析返回数据失败 - 订单ID: {}, 错误: {}", order.getId(), e.getMessage()); + return false; + } + + // 检查waybill_no + JSONObject dataObj = parsedData.getJSONObject("data"); + if (dataObj == null) { + logger.info("返回数据中没有data字段 - 订单ID: {}", order.getId()); + return false; + } + + String waybillNo = dataObj.getString("waybill_no"); + if (waybillNo == null || waybillNo.trim().isEmpty()) { + logger.info("waybill_no为空,无需处理 - 订单ID: {}", order.getId()); + return false; + } + + logger.info("检测到waybill_no: {} - 订单ID: {}", waybillNo, order.getId()); + + // 保存运单号到Redis(避免重复处理) + String redisKey = REDIS_WAYBILL_KEY_PREFIX + order.getId(); + stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS); + + // 调用企业应用推送 + sendEnterprisePushNotification(order, waybillNo); + + logger.info("物流信息获取并推送成功 - 订单ID: {}, waybill_no: {}", order.getId(), waybillNo); + return true; + + } catch (Exception e) { + logger.error("获取物流信息失败 - 订单ID: {}, 错误: {}", order.getId(), e.getMessage(), e); + return false; + } + } + + /** + * 调用企业应用推送逻辑 + * @param order 订单信息 + * @param waybillNo 运单号 + */ + private void sendEnterprisePushNotification(JDOrder order, String waybillNo) { + try { + // 构建推送消息内容(只包含:型号、收货地址、运单号) + StringBuilder pushContent = new StringBuilder(); + pushContent.append("型号:").append(order.getModelNumber() != null ? order.getModelNumber() : "无").append("\n"); + pushContent.append("收货地址:").append(order.getAddress() != null ? order.getAddress() : "无").append("\n"); + pushContent.append("运单号:").append(waybillNo).append("\n"); + + // 调用企业微信推送接口 + JSONObject pushParam = new JSONObject(); + pushParam.put("title", "JD物流信息推送"); + pushParam.put("text", pushContent.toString()); + + // 使用支持自定义header的HTTP请求 + String pushResult = sendPostWithHeaders(PUSH_URL, pushParam.toJSONString(), PUSH_TOKEN); + logger.info("企业应用推送调用结果 - 订单ID: {}, waybill_no: {}, 推送结果: {}", + order.getId(), waybillNo, pushResult); + + } catch (Exception e) { + logger.error("调用企业应用推送失败 - 订单ID: {}, waybill_no: {}, 错误: {}", + order.getId(), waybillNo, e.getMessage(), e); + // 不抛出异常,避免影响主流程 + } + } + + /** + * 发送POST请求,支持自定义header(用于企业微信推送) + * @param url 请求URL + * @param jsonBody JSON请求体 + * @param token 认证token + * @return 响应结果 + */ + private String sendPostWithHeaders(String url, String jsonBody, String token) { + java.io.BufferedReader in = null; + java.io.PrintWriter out = null; + StringBuilder result = new StringBuilder(); + try { + java.net.URL realUrl = new java.net.URL(url); + java.net.URLConnection conn = realUrl.openConnection(); + + // 设置请求头 + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("vanToken", token); + conn.setRequestProperty("source", "XZJ_UBUNTU"); + + conn.setDoOutput(true); + conn.setDoInput(true); + + // 发送请求体 + out = new java.io.PrintWriter(conn.getOutputStream()); + out.print(jsonBody); + out.flush(); + + // 读取响应 + in = new java.io.BufferedReader(new java.io.InputStreamReader(conn.getInputStream(), java.nio.charset.StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + + logger.info("企业微信推送请求成功 - URL: {}, 响应: {}", url, result.toString()); + } catch (java.net.ConnectException e) { + logger.error("企业微信推送连接失败 - URL: {}", url, e); + throw new RuntimeException("推送连接失败: " + e.getMessage(), e); + } catch (java.net.SocketTimeoutException e) { + logger.error("企业微信推送超时 - URL: {}", url, e); + throw new RuntimeException("推送请求超时: " + e.getMessage(), e); + } catch (java.io.IOException e) { + logger.error("企业微信推送IO异常 - URL: {}", url, e); + throw new RuntimeException("推送IO异常: " + e.getMessage(), e); + } catch (Exception e) { + logger.error("企业微信推送异常 - URL: {}", url, e); + throw new RuntimeException("推送异常: " + e.getMessage(), e); + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (java.io.IOException ex) { + logger.error("关闭流异常", ex); + } + } + return result.toString(); + } +} + diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/task/LogisticsScanTask.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/task/LogisticsScanTask.java new file mode 100644 index 0000000..ebf5fc8 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/task/LogisticsScanTask.java @@ -0,0 +1,101 @@ +package com.ruoyi.jarvis.task; + +import com.ruoyi.jarvis.domain.JDOrder; +import com.ruoyi.jarvis.service.IJDOrderService; +import com.ruoyi.jarvis.service.ILogisticsService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 物流信息扫描定时任务 + * 每1小时扫描一次分销标记为F或PDD的订单,获取物流信息并推送 + */ +@Component +public class LogisticsScanTask { + private static final Logger logger = LoggerFactory.getLogger(LogisticsScanTask.class); + + @Resource + private IJDOrderService jdOrderService; + + @Resource + private ILogisticsService logisticsService; + + /** + * 定时任务:每1小时执行一次 + * Cron表达式:0 0 * * * ? 表示每小时的第0分钟执行 + */ + @Scheduled(cron = "0 0 * * * ?") + public void scanAndFetchLogistics() { + logger.info("========== 开始执行物流信息扫描定时任务 =========="); + + try { + // 查询分销标记为F或PDD且有物流链接的订单 + List orders = jdOrderService.selectJDOrderListByDistributionMarkFOrPDD(); + + if (orders == null || orders.isEmpty()) { + logger.info("未找到需要处理的订单"); + return; + } + + logger.info("找到 {} 个需要处理的订单", orders.size()); + + int processedCount = 0; + int skippedCount = 0; + int successCount = 0; + int failedCount = 0; + + // 串行处理订单(避免并发调用接口) + for (JDOrder order : orders) { + try { + // 检查Redis中是否已处理过(避免重复处理) + if (logisticsService.isOrderProcessed(order.getId())) { + logger.debug("订单已处理过,跳过 - 订单ID: {}", order.getId()); + skippedCount++; + continue; + } + + logger.info("开始处理订单 - 订单ID: {}, 订单号: {}, 分销标识: {}", + order.getId(), order.getOrderId(), order.getDistributionMark()); + + // 获取物流信息并推送(串行执行,不并发) + boolean success = logisticsService.fetchLogisticsAndPush(order); + + if (success) { + successCount++; + logger.info("订单处理成功 - 订单ID: {}, 订单号: {}", order.getId(), order.getOrderId()); + } else { + failedCount++; + logger.warn("订单处理失败 - 订单ID: {}, 订单号: {}", order.getId(), order.getOrderId()); + } + + processedCount++; + + // 添加短暂延迟,避免请求过于频繁 + Thread.sleep(500); // 每次请求间隔500毫秒 + + } catch (InterruptedException e) { + logger.error("定时任务被中断", e); + Thread.currentThread().interrupt(); + break; + } catch (Exception e) { + failedCount++; + logger.error("处理订单时发生异常 - 订单ID: {}, 错误: {}", order.getId(), e.getMessage(), e); + // 继续处理下一个订单 + } + } + + logger.info("========== 物流信息扫描定时任务执行完成 =========="); + logger.info("总订单数: {}, 已处理: {}, 跳过: {}, 成功: {}, 失败: {}", + orders.size(), processedCount, skippedCount, successCount, failedCount); + + } catch (Exception e) { + logger.error("执行物流信息扫描定时任务时发生异常", e); + } + } +} + diff --git a/ruoyi-system/src/main/resources/mapper/jarvis/JDOrderMapper.xml b/ruoyi-system/src/main/resources/mapper/jarvis/JDOrderMapper.xml index eed3751..e2608e0 100644 --- a/ruoyi-system/src/main/resources/mapper/jarvis/JDOrderMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/jarvis/JDOrderMapper.xml @@ -161,6 +161,16 @@ + +