Compare commits
5 Commits
7581cc02a9
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
632b9f7eb1 | ||
|
|
eb53915bcd | ||
|
|
4dd3e9dd70 | ||
|
|
9206824efb | ||
|
|
2524461ff4 |
@@ -44,6 +44,10 @@ public class SuperAdmin extends BaseEntity
|
||||
@Excel(name = "是否参与订单统计", readConverterExp = "0=否,1=是")
|
||||
private Integer isCount;
|
||||
|
||||
/** 接收人(企业微信用户ID,多个用逗号分隔) */
|
||||
@Excel(name = "接收人")
|
||||
private String touser;
|
||||
|
||||
/** 创建时间 */
|
||||
@Excel(name = "创建时间")
|
||||
private Date createdAt;
|
||||
@@ -151,4 +155,14 @@ public class SuperAdmin extends BaseEntity
|
||||
{
|
||||
this.isCount = isCount;
|
||||
}
|
||||
|
||||
public String getTouser()
|
||||
{
|
||||
return touser;
|
||||
}
|
||||
|
||||
public void setTouser(String touser)
|
||||
{
|
||||
this.touser = touser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -24,10 +26,12 @@ 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 REDIS_LOCK_KEY_PREFIX = "logistics:lock: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";
|
||||
private static final String CONFIG_KEY_PREFIX = "logistics.push.touser.";
|
||||
private static final long LOCK_EXPIRE_SECONDS = 300; // 锁过期时间5分钟,防止死锁
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
@@ -51,27 +55,49 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查物流链接
|
||||
String logisticsLink = order.getLogisticsLink();
|
||||
if (logisticsLink == null || logisticsLink.trim().isEmpty()) {
|
||||
logger.info("订单暂无物流链接,跳过处理 - 订单ID: {}", order.getId());
|
||||
return false;
|
||||
Long orderId = order.getId();
|
||||
|
||||
// 双重检查:先检查是否已处理过
|
||||
if (isOrderProcessed(orderId)) {
|
||||
logger.info("订单已处理过,跳过 - 订单ID: {}", orderId);
|
||||
return true; // 返回true表示已处理,避免重复处理
|
||||
}
|
||||
|
||||
// 获取分布式锁,防止并发处理同一订单
|
||||
String lockKey = REDIS_LOCK_KEY_PREFIX + orderId;
|
||||
Boolean lockAcquired = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "locked", LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
||||
|
||||
if (Boolean.FALSE.equals(lockAcquired)) {
|
||||
logger.warn("订单正在被其他线程处理,跳过 - 订单ID: {}", orderId);
|
||||
return false; // 其他线程正在处理,返回false让调用方稍后重试
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取锁后再次检查是否已处理(双重检查锁定模式)
|
||||
if (isOrderProcessed(orderId)) {
|
||||
logger.info("订单在获取锁后检查发现已处理,跳过 - 订单ID: {}", orderId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查物流链接
|
||||
String logisticsLink = order.getLogisticsLink();
|
||||
if (logisticsLink == null || logisticsLink.trim().isEmpty()) {
|
||||
logger.info("订单暂无物流链接,跳过处理 - 订单ID: {}", orderId);
|
||||
return false;
|
||||
}
|
||||
// 构建外部接口URL
|
||||
String externalUrl = EXTERNAL_API_URL + URLEncoder.encode(logisticsLink, "UTF-8");
|
||||
logger.info("调用外部接口获取物流信息 - 订单ID: {}, URL: {}", order.getId(), externalUrl);
|
||||
logger.info("调用外部接口获取物流信息 - 订单ID: {}, URL: {}", orderId, externalUrl);
|
||||
|
||||
// 在服务端执行HTTP请求
|
||||
String result = HttpUtils.sendGet(externalUrl);
|
||||
|
||||
if (result == null || result.trim().isEmpty()) {
|
||||
logger.warn("外部接口返回空结果 - 订单ID: {}", order.getId());
|
||||
logger.warn("外部接口返回空结果 - 订单ID: {}", orderId);
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.info("外部接口调用成功 - 订单ID: {}, 返回数据长度: {}", order.getId(), result.length());
|
||||
logger.info("外部接口调用成功 - 订单ID: {}, 返回数据长度: {}", orderId, result.length());
|
||||
|
||||
// 解析返回结果
|
||||
JSONObject parsedData = null;
|
||||
@@ -80,46 +106,97 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
if (parsed instanceof JSONObject) {
|
||||
parsedData = (JSONObject) parsed;
|
||||
} else {
|
||||
logger.warn("返回数据不是JSON对象格式 - 订单ID: {}", order.getId());
|
||||
logger.warn("返回数据不是JSON对象格式 - 订单ID: {}", orderId);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("解析返回数据失败 - 订单ID: {}, 错误: {}", order.getId(), e.getMessage());
|
||||
logger.warn("解析返回数据失败 - 订单ID: {}, 错误: {}", orderId, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查waybill_no
|
||||
JSONObject dataObj = parsedData.getJSONObject("data");
|
||||
if (dataObj == null) {
|
||||
logger.info("返回数据中没有data字段 - 订单ID: {}", order.getId());
|
||||
logger.info("返回数据中没有data字段 - 订单ID: {}", orderId);
|
||||
return false;
|
||||
}
|
||||
|
||||
String waybillNo = dataObj.getString("waybill_no");
|
||||
if (waybillNo == null || waybillNo.trim().isEmpty()) {
|
||||
logger.info("waybill_no为空,无需处理 - 订单ID: {}", order.getId());
|
||||
logger.info("waybill_no为空,无需处理 - 订单ID: {}", orderId);
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.info("检测到waybill_no: {} - 订单ID: {}", waybillNo, order.getId());
|
||||
logger.info("检测到waybill_no: {} - 订单ID: {}", waybillNo, orderId);
|
||||
|
||||
// 兼容处理:检查Redis中是否已有该订单的运单号记录
|
||||
// 如果存在且运单号一致,说明之前已经推送过了(可能是之前没有配置接收人但推送成功的情况)
|
||||
String redisKey = REDIS_WAYBILL_KEY_PREFIX + orderId;
|
||||
String existingWaybillNo = stringRedisTemplate.opsForValue().get(redisKey);
|
||||
|
||||
if (existingWaybillNo != null && existingWaybillNo.trim().equals(waybillNo.trim())) {
|
||||
// 运单号一致,说明之前已经推送过了,直接标记为已处理,跳过推送
|
||||
logger.info("订单运单号已存在且一致,说明之前已推送过,跳过重复推送 - 订单ID: {}, waybill_no: {}", orderId, waybillNo);
|
||||
// 更新过期时间,确保记录不会过期
|
||||
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 兼容处理:如果Redis中没有记录,但订单创建时间在30天之前,且获取到了运单号
|
||||
// 说明可能是之前推送过但没标记的情况(比如之前没有配置接收人但推送成功,响应解析失败)
|
||||
// 这种情况下,直接标记为已处理,跳过推送,避免重复推送旧订单
|
||||
if (existingWaybillNo == null && order.getCreateTime() != null) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.DAY_OF_MONTH, -30); // 30天前
|
||||
Date thresholdDate = calendar.getTime();
|
||||
|
||||
if (order.getCreateTime().before(thresholdDate)) {
|
||||
// 订单创建时间在30天之前,且Redis中没有记录,但获取到了运单号
|
||||
// 视为之前已推送过但未标记,直接标记为已处理,跳过推送
|
||||
logger.info("订单创建时间较早({}),且Redis中无记录但已获取到运单号,视为之前已推送过,直接标记为已处理,跳过推送 - 订单ID: {}, waybill_no: {}",
|
||||
order.getCreateTime(), orderId, waybillNo);
|
||||
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果Redis中有记录但运单号不一致,记录警告
|
||||
if (existingWaybillNo != null && !existingWaybillNo.trim().equals(waybillNo.trim())) {
|
||||
logger.warn("订单运单号发生变化 - 订单ID: {}, 旧运单号: {}, 新运单号: {}, 将重新推送",
|
||||
orderId, existingWaybillNo, waybillNo);
|
||||
}
|
||||
|
||||
// 调用企业应用推送,只有推送成功才记录状态
|
||||
boolean pushSuccess = sendEnterprisePushNotification(order, waybillNo);
|
||||
if (!pushSuccess) {
|
||||
logger.warn("企业微信推送未确认成功,稍后将重试 - 订单ID: {}, waybill_no: {}", order.getId(), waybillNo);
|
||||
logger.warn("企业微信推送未确认成功,稍后将重试 - 订单ID: {}, waybill_no: {}", orderId, waybillNo);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 保存运单号到Redis(避免重复处理)
|
||||
String redisKey = REDIS_WAYBILL_KEY_PREFIX + order.getId();
|
||||
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||
// 保存运单号到Redis(避免重复处理)- 使用原子操作确保只写入一次
|
||||
Boolean setSuccess = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||
|
||||
logger.info("物流信息获取并推送成功 - 订单ID: {}, waybill_no: {}", order.getId(), waybillNo);
|
||||
if (Boolean.FALSE.equals(setSuccess)) {
|
||||
// 如果Redis中已存在,说明可能被其他线程处理了,记录警告但不算失败
|
||||
logger.warn("订单运单号已存在(可能被并发处理),但推送已成功 - 订单ID: {}, waybill_no: {}", orderId, waybillNo);
|
||||
// 更新过期时间,确保记录不会过期
|
||||
stringRedisTemplate.opsForValue().set(redisKey, waybillNo, 30, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
logger.info("物流信息获取并推送成功 - 订单ID: {}, waybill_no: {}", orderId, waybillNo);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("获取物流信息失败 - 订单ID: {}, 错误: {}", order.getId(), e.getMessage(), e);
|
||||
logger.error("获取物流信息失败 - 订单ID: {}, 错误: {}", orderId, e.getMessage(), e);
|
||||
return false;
|
||||
} finally {
|
||||
// 释放分布式锁
|
||||
try {
|
||||
stringRedisTemplate.delete(lockKey);
|
||||
logger.debug("释放订单处理锁 - 订单ID: {}", orderId);
|
||||
} catch (Exception e) {
|
||||
logger.warn("释放订单处理锁失败 - 订单ID: {}, 错误: {}", orderId, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +239,8 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
logger.info("企业微信推送设置接收人 - 订单ID: {}, 分销标识: {}, 接收人: {}",
|
||||
order.getId(), distributionMark, touser);
|
||||
} else {
|
||||
logger.warn("未找到分销标识对应的接收人配置 - 订单ID: {}, 分销标识: {}",
|
||||
// 未配置接收人时,使用远程接口的默认接收人,这是正常情况
|
||||
logger.info("未找到分销标识对应的接收人配置,将使用远程接口默认接收人 - 订单ID: {}, 分销标识: {}",
|
||||
order.getId(), distributionMark);
|
||||
}
|
||||
|
||||
@@ -238,31 +316,89 @@ public class LogisticsServiceImpl implements ILogisticsService {
|
||||
* @return 是否成功
|
||||
*/
|
||||
private boolean isPushResponseSuccess(String pushResult) {
|
||||
if (pushResult == null || pushResult.trim().isEmpty()) {
|
||||
logger.warn("推送响应为空,视为失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject response = JSON.parseObject(pushResult);
|
||||
if (response == null) {
|
||||
logger.warn("推送响应解析为null,视为失败。原始响应: {}", pushResult);
|
||||
return false;
|
||||
}
|
||||
|
||||
Integer code = response.getInteger("code");
|
||||
Boolean successFlag = response.getBoolean("success");
|
||||
String status = response.getString("status");
|
||||
String message = response.getString("msg");
|
||||
String errcode = response.getString("errcode");
|
||||
|
||||
// 记录完整的响应信息用于调试
|
||||
logger.debug("推送响应解析 - code: {}, success: {}, status: {}, msg: {}, errcode: {}",
|
||||
code, successFlag, status, message, errcode);
|
||||
|
||||
// 检查错误码(errcode为0表示成功)
|
||||
if (errcode != null && "0".equals(errcode)) {
|
||||
logger.info("推送成功(通过errcode=0判断)");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查code字段(0或200表示成功)
|
||||
if (code != null && (code == 0 || code == 200)) {
|
||||
logger.info("推送成功(通过code={}判断)", code);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查success字段
|
||||
if (Boolean.TRUE.equals(successFlag)) {
|
||||
logger.info("推送成功(通过success=true判断)");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查status字段
|
||||
if (status != null && ("success".equalsIgnoreCase(status) || "ok".equalsIgnoreCase(status))) {
|
||||
logger.info("推送成功(通过status={}判断)", status);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查message字段(某些接口可能用message表示成功)
|
||||
if (message != null && ("success".equalsIgnoreCase(message) || "ok".equalsIgnoreCase(message))) {
|
||||
logger.info("推送成功(通过message={}判断)", message);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否包含明确的错误标识
|
||||
String responseStr = pushResult.toLowerCase();
|
||||
boolean hasErrorKeyword = responseStr.contains("\"error\"") || responseStr.contains("\"fail\"") ||
|
||||
responseStr.contains("\"failed\"") || responseStr.contains("\"errmsg\"");
|
||||
|
||||
// 如果包含错误标识,检查是否有明确的错误码
|
||||
if (hasErrorKeyword) {
|
||||
if (code != null && code < 0) {
|
||||
logger.warn("推送失败(检测到错误标识和负错误码) - code: {}, 完整响应: {}", code, pushResult);
|
||||
return false;
|
||||
}
|
||||
if (errcode != null && !"0".equals(errcode) && !errcode.isEmpty()) {
|
||||
logger.warn("推送失败(检测到错误标识和非零错误码) - errcode: {}, 完整响应: {}", errcode, pushResult);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果响应是有效的JSON,且没有明确的错误标识,视为成功
|
||||
// 因为远程接口有默认接收人,即使没有配置接收人,推送也应该成功
|
||||
// 如果响应格式特殊(不是标准格式),只要没有错误,也视为成功
|
||||
if (!hasErrorKeyword) {
|
||||
logger.info("推送成功(响应格式有效且无错误标识,使用默认接收人) - 完整响应: {}", pushResult);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果所有判断都失败,记录详细信息
|
||||
logger.warn("推送响应未确认成功 - code: {}, success: {}, status: {}, msg: {}, errcode: {}, 完整响应: {}",
|
||||
code, successFlag, status, message, errcode, pushResult);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
logger.warn("解析企业应用推送响应失败,将视为未成功: {}", e.getMessage());
|
||||
logger.error("解析企业应用推送响应失败,将视为未成功。原始响应: {}, 错误: {}", pushResult, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,13 @@
|
||||
<result property="secretKey" column="secret_key"/>
|
||||
<result property="isActive" column="is_active"/>
|
||||
<result property="isCount" column="is_count"/>
|
||||
<result property="touser" column="touser"/>
|
||||
<result property="createdAt" column="created_at"/>
|
||||
<result property="updatedAt" column="updated_at"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectSuperAdminVo">
|
||||
select id, wxid, name, union_id, app_key, secret_key, is_active, is_count, created_at, updated_at from super_admin
|
||||
select id, wxid, name, union_id, app_key, secret_key, is_active, is_count, touser, created_at, updated_at from super_admin
|
||||
</sql>
|
||||
|
||||
<select id="selectSuperAdminList" parameterType="SuperAdmin" resultMap="SuperAdminResult">
|
||||
@@ -51,6 +52,8 @@
|
||||
<if test="appKey != null and appKey != ''">app_key,</if>
|
||||
<if test="secretKey != null and secretKey != ''">secret_key,</if>
|
||||
<if test="isActive != null">is_active,</if>
|
||||
<if test="isCount != null">is_count,</if>
|
||||
<if test="touser != null and touser != ''">touser,</if>
|
||||
created_at,
|
||||
updated_at,
|
||||
</trim>
|
||||
@@ -61,6 +64,8 @@
|
||||
<if test="appKey != null and appKey != ''">#{appKey},</if>
|
||||
<if test="secretKey != null and secretKey != ''">#{secretKey},</if>
|
||||
<if test="isActive != null">#{isActive},</if>
|
||||
<if test="isCount != null">#{isCount},</if>
|
||||
<if test="touser != null and touser != ''">#{touser},</if>
|
||||
now(),
|
||||
now(),
|
||||
</trim>
|
||||
@@ -76,6 +81,7 @@
|
||||
<if test="secretKey != null and secretKey != ''">secret_key = #{secretKey},</if>
|
||||
<if test="isActive != null">is_active = #{isActive},</if>
|
||||
<if test="isCount != null">is_count = #{isCount},</if>
|
||||
<if test="touser != null">touser = #{touser},</if>
|
||||
updated_at = now(),
|
||||
</trim>
|
||||
where id = #{id}
|
||||
|
||||
4
sql/add_super_admin_touser_field.sql
Normal file
4
sql/add_super_admin_touser_field.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
-- 为超级管理员表添加接收人字段
|
||||
-- 字段说明:touser 存储企业微信用户ID,多个用逗号分隔
|
||||
ALTER TABLE super_admin ADD COLUMN touser VARCHAR(500) DEFAULT NULL COMMENT '接收人(企业微信用户ID,多个用逗号分隔)';
|
||||
|
||||
Reference in New Issue
Block a user