1
This commit is contained in:
@@ -5,6 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动程序
|
* 启动程序
|
||||||
@@ -12,6 +13,7 @@ import org.springframework.core.env.Environment;
|
|||||||
* @author ruoyi
|
* @author ruoyi
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
|
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
|
||||||
|
@EnableScheduling
|
||||||
public class RuoYiApplication
|
public class RuoYiApplication
|
||||||
{
|
{
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ public interface JDOrderMapper {
|
|||||||
* 批量删除(根据主键ID)
|
* 批量删除(根据主键ID)
|
||||||
*/
|
*/
|
||||||
int deleteJDOrderByIds(Long[] ids);
|
int deleteJDOrderByIds(Long[] ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询分销标记为F或PDD且有物流链接的订单列表
|
||||||
|
* @return 订单列表
|
||||||
|
*/
|
||||||
|
List<JDOrder> selectJDOrderListByDistributionMarkFOrPDD();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ public interface IJDOrderService {
|
|||||||
|
|
||||||
/** 批量删除(根据主键ID) */
|
/** 批量删除(根据主键ID) */
|
||||||
int deleteJDOrderByIds(Long[] ids);
|
int deleteJDOrderByIds(Long[] ids);
|
||||||
|
|
||||||
|
/** 查询分销标记为F或PDD且有物流链接的订单列表 */
|
||||||
|
java.util.List<JDOrder> selectJDOrderListByDistributionMarkFOrPDD();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -66,6 +66,11 @@ public class JDOrderServiceImpl implements IJDOrderService {
|
|||||||
}
|
}
|
||||||
return jdOrderMapper.deleteJDOrderByIds(ids);
|
return jdOrderMapper.deleteJDOrderByIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<JDOrder> selectJDOrderListByDistributionMarkFOrPDD() {
|
||||||
|
return jdOrderMapper.selectJDOrderListByDistributionMarkFOrPDD();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<JDOrder> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -161,6 +161,16 @@
|
|||||||
</foreach>
|
</foreach>
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
|
<select id="selectJDOrderListByDistributionMarkFOrPDD" resultMap="JDOrderResult">
|
||||||
|
<include refid="selectJDOrderBase"/>
|
||||||
|
<where>
|
||||||
|
(distribution_mark = 'F' OR distribution_mark = 'PDD')
|
||||||
|
AND logistics_link IS NOT NULL
|
||||||
|
AND logistics_link != ''
|
||||||
|
</where>
|
||||||
|
ORDER BY create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user