From e5c7ce5a9a6702c0bdcbefe3fd0671d9f7d98986 Mon Sep 17 00:00:00 2001 From: tianyongbao Date: Wed, 3 Jun 2026 23:54:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E6=88=90=E5=8A=9F=EF=BC=8C=E6=9C=AA=E6=9B=B4=E6=96=B0=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E6=9C=89=E6=95=88=E6=9C=9F=E9=97=AE=E9=A2=98=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/PayOrderBusinessService.java | 7 + .../impl/PayOrderBusinessServiceImpl.java | 152 ++++++++++++++++-- .../weixin/task/PayOrderCompensationTask.java | 24 ++- 3 files changed, 163 insertions(+), 20 deletions(-) diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/PayOrderBusinessService.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/PayOrderBusinessService.java index e381902..f45e3b2 100644 --- a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/PayOrderBusinessService.java +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/PayOrderBusinessService.java @@ -91,4 +91,11 @@ public interface PayOrderBusinessService { * @return 补偿成功的订单数量 */ int compensateUnpaidOrders(); + + /** + * 修复已支付订单中设备有效期未落库的异常数据。 + * + * @return 修复的设备数量 + */ + int repairPaidOrderDeviceDeadTime(); } diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/impl/PayOrderBusinessServiceImpl.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/impl/PayOrderBusinessServiceImpl.java index 4d4b410..a81ed75 100644 --- a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/impl/PayOrderBusinessServiceImpl.java +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/service/impl/PayOrderBusinessServiceImpl.java @@ -23,6 +23,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; import java.text.SimpleDateFormat; import java.util.*; @@ -44,6 +45,7 @@ public class PayOrderBusinessServiceImpl implements PayOrderBusinessService { private final DeviceMapper deviceMapper; private final WxPayItemProperties wxPayItemProperties; private final WxPayService wxPayService; + private final TransactionTemplate transactionTemplate; /** * 订单处理锁,防止并发处理同一订单 @@ -175,7 +177,6 @@ public class PayOrderBusinessServiceImpl implements PayOrderBusinessService { } @Override - @Transactional(rollbackFor = Exception.class) public void closeUnpaidOrders(Long userId, String openId) { // 查询用户所有未完成的订单 List unpaidOrders = payOrderMapper.selectList( @@ -208,9 +209,34 @@ public class PayOrderBusinessServiceImpl implements PayOrderBusinessService { compensateSingleOrder(order, resultJson); continue; } + if ("CLOSED".equalsIgnoreCase(tradeState)) { + payOrderMapper.update(null, + new LambdaUpdateWrapper() + .eq(PayOrder::getId, order.getId()) + .set(PayOrder::getOrderStatus, PayOrderStatus.CLOSED) + .set(PayOrder::getTradeState, "CLOSED") + .set(PayOrder::getTradeStateDescription, "微信侧已关闭") + ); + log.info("旧订单微信侧已关闭,同步本地状态: orderId={}, outTradeNumber={}", + order.getId(), order.getOutTradeNumber()); + continue; + } + if ("USERPAYING".equalsIgnoreCase(tradeState)) { + log.info("旧订单仍在支付中,跳过关闭: orderId={}, outTradeNumber={}", + order.getId(), order.getOutTradeNumber()); + continue; + } } } catch (Exception e) { - log.warn("关闭订单前查询微信失败,直接关闭: orderId={}, error={}", order.getId(), e.getMessage()); + log.warn("关闭订单前查询微信失败,跳过关闭避免误关已支付订单: orderId={}, error={}", order.getId(), e.getMessage()); + continue; + } + + boolean closed = wxPayService.closeOrder(order.getOutTradeNumber()); + if (!closed) { + log.warn("微信关单失败,跳过本地关闭: orderId={}, outTradeNumber={}", + order.getId(), order.getOutTradeNumber()); + continue; } } @@ -246,15 +272,13 @@ public class PayOrderBusinessServiceImpl implements PayOrderBusinessService { } // 3. 并发控制:检查订单是否正在处理 - if (ORDER_PROCESSING_LOCK.containsKey(order.getId())) { - log.warn("订单正在处理中,跳过: orderId={}, outTradeNumber={}", order.getId(), outTradeNumber); - return false; + Long previousLockTime = ORDER_PROCESSING_LOCK.putIfAbsent(order.getId(), System.currentTimeMillis()); + if (previousLockTime != null) { + log.warn("订单正在处理中,等待微信下次通知或补偿任务处理: orderId={}, outTradeNumber={}", order.getId(), outTradeNumber); + return true; } try { - // 4. 加锁 - ORDER_PROCESSING_LOCK.put(order.getId(), System.currentTimeMillis()); - // 5. 检查金额是否一致 if (!order.getTotalAmount().equals(payerTotal)) { log.error("订单金额不匹配: orderId={}, orderAmount={}, payAmount={}", @@ -267,9 +291,10 @@ public class PayOrderBusinessServiceImpl implements PayOrderBusinessService { // 7. 更新订单状态 Integer orderStatus = PayOrderStatusUtil.convertTradeStateToOrderStatus(tradeState); - payOrderMapper.update(null, + int updateRows = payOrderMapper.update(null, new LambdaUpdateWrapper() .eq(PayOrder::getId, order.getId()) + .ne(PayOrder::getOrderStatus, PayOrderStatus.SUCCESS) .set(PayOrder::getTransactionId, transactionId) .set(PayOrder::getPayerTotal, payerTotal) .set(PayOrder::getPayerCurrency, payerCurrency) @@ -280,6 +305,16 @@ public class PayOrderBusinessServiceImpl implements PayOrderBusinessService { .set(PayOrder::getOrderStatus, orderStatus) ); + if (updateRows <= 0) { + PayOrder latestOrder = payOrderMapper.selectById(order.getId()); + if (latestOrder != null && PayOrderStatus.SUCCESS.equals(latestOrder.getOrderStatus())) { + log.info("订单已由其他回调处理成功: orderId={}, outTradeNumber={}", order.getId(), outTradeNumber); + return true; + } + log.warn("订单状态更新失败: orderId={}, outTradeNumber={}", order.getId(), outTradeNumber); + return false; + } + // 8. 如果支付成功,更新设备到期时间 if (PayOrderStatus.SUCCESS.equals(orderStatus)) { updateDeviceDeadTime(order.getId()); @@ -370,8 +405,7 @@ public class PayOrderBusinessServiceImpl implements PayOrderBusinessService { ); if (device == null) { - log.warn("设备不存在: serialNum={}", payDevice.getSerialNum()); - continue; + throw new ServiceException("设备不存在: " + payDevice.getSerialNum()); } // 计算新的到期时间 @@ -384,11 +418,21 @@ public class PayOrderBusinessServiceImpl implements PayOrderBusinessService { : 0; // 更新设备到期时间,并同步清除到期告警位 - deviceMapper.update(null, + int updateRows = TenantHelper.ignore(() -> deviceMapper.update(null, new LambdaUpdateWrapper() .eq(Device::getId, device.getId()) .set(Device::getDeadTime, newDeadTime) .set(Device::getWarnCode, newWarnCode) + )); + + if (updateRows <= 0) { + throw new ServiceException("更新设备有效期失败: " + payDevice.getSerialNum()); + } + + payDeviceMapper.update(null, + new LambdaUpdateWrapper() + .eq(PayDevice::getId, payDevice.getId()) + .set(PayDevice::getDeadTime, newDeadTime) ); log.info("续费成功,更新设备到期时间并清除到期告警: deviceId={}, serialNum={}, newDeadTime={}, oldWarnCode={}, newWarnCode={}", @@ -420,17 +464,18 @@ public class PayOrderBusinessServiceImpl implements PayOrderBusinessService { @Override public int compensateUnpaidOrders() { - // 查询状态为未支付/支付中/已关闭,已有prepayId,且创建时间在5分钟前到24小时内的订单 + // 查询状态为未支付/支付中/支付失败/已关闭,已有prepayId,且创建时间在1分钟前到48小时内的订单 // 包含CLOSED是因为closeUnpaidOrders可能在微信支付成功前就关闭了订单(网络延迟导致查单返回NOTPAY) Date now = new Date(); - Date startTime = new Date(now.getTime() - 24L * 60 * 60 * 1000); // 24小时前 - Date endTime = new Date(now.getTime() - 5L * 60 * 1000); // 5分钟前 + Date startTime = new Date(now.getTime() - 48L * 60 * 60 * 1000); // 48小时前 + Date endTime = new Date(now.getTime() - 1L * 60 * 1000); // 1分钟前 List pendingOrders = payOrderMapper.selectList( new LambdaQueryWrapper() .in(PayOrder::getOrderStatus, Arrays.asList( PayOrderStatus.NOTPAY, PayOrderStatus.USERPAYING, + PayOrderStatus.PAYERROR, PayOrderStatus.CLOSED )) .isNotNull(PayOrder::getPrepayId) @@ -512,7 +557,7 @@ public class PayOrderBusinessServiceImpl implements PayOrderBusinessService { return false; } - return handlePaymentSuccess( + Boolean success = transactionTemplate.execute(status -> handlePaymentSuccess( order.getOutTradeNumber(), transactionId, payerTotal, @@ -521,6 +566,81 @@ public class PayOrderBusinessServiceImpl implements PayOrderBusinessService { tradeState, tradeStateDesc, bankType + )); + return Boolean.TRUE.equals(success); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int repairPaidOrderDeviceDeadTime() { + Date now = new Date(); + Date startTime = new Date(now.getTime() - 30L * 24 * 60 * 60 * 1000); // 30天内 + + List paidOrders = payOrderMapper.selectList( + new LambdaQueryWrapper() + .eq(PayOrder::getOrderStatus, PayOrderStatus.SUCCESS) + .ge(PayOrder::getCreateTime, startTime) ); + + if (paidOrders == null || paidOrders.isEmpty()) { + return 0; + } + + int repairCount = 0; + for (PayOrder order : paidOrders) { + List payDevices = queryPayDevicesByOrderId(order.getId()); + if (payDevices == null || payDevices.isEmpty()) { + log.warn("[支付有效期修复] 支付成功订单没有设备充值记录: orderId={}", order.getId()); + continue; + } + + for (PayDevice payDevice : payDevices) { + if (payDevice.getDeadTime() == null) { + log.warn("[支付有效期修复] 设备充值记录没有目标到期时间: payDeviceId={}, orderId={}", + payDevice.getId(), order.getId()); + continue; + } + + Device device = TenantHelper.ignore(() -> + deviceMapper.selectOne( + new LambdaQueryWrapper() + .eq(Device::getSerialNum, payDevice.getSerialNum()) + ) + ); + + if (device == null) { + log.warn("[支付有效期修复] 设备不存在: orderId={}, serialNum={}", + order.getId(), payDevice.getSerialNum()); + continue; + } + + if (device.getDeadTime() != null && !device.getDeadTime().before(payDevice.getDeadTime())) { + continue; + } + + Integer oldWarnCode = device.getWarnCode(); + Integer newWarnCode = oldWarnCode != null + ? (oldWarnCode & ~DefineDeviceWarnCode.DeviceTimeDead) + : 0; + + int updateRows = TenantHelper.ignore(() -> deviceMapper.update(null, + new LambdaUpdateWrapper() + .eq(Device::getId, device.getId()) + .set(Device::getDeadTime, payDevice.getDeadTime()) + .set(Device::getWarnCode, newWarnCode) + )); + + if (updateRows > 0) { + repairCount++; + log.warn("[支付有效期修复] 已修复设备有效期: orderId={}, deviceId={}, serialNum={}, oldDeadTime={}, targetDeadTime={}", + order.getId(), device.getId(), device.getSerialNum(), device.getDeadTime(), payDevice.getDeadTime()); + } + } + } + + if (repairCount > 0) { + log.warn("[支付有效期修复] 本次修复 {} 台设备", repairCount); + } + return repairCount; } } diff --git a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/task/PayOrderCompensationTask.java b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/task/PayOrderCompensationTask.java index 898c492..8c5a977 100644 --- a/intc-modules/intc-weixin/src/main/java/com/intc/weixin/task/PayOrderCompensationTask.java +++ b/intc-modules/intc-weixin/src/main/java/com/intc/weixin/task/PayOrderCompensationTask.java @@ -9,7 +9,7 @@ import org.springframework.stereotype.Component; /** * 支付订单补偿定时任务 - * 定期扫描状态为"未支付/支付中"但已有prepayId的订单, + * 定期扫描状态为"未支付/支付中/支付失败"但已有prepayId的订单, * 主动向微信支付查询实际状态,对已支付成功但回调丢失的订单进行补偿处理。 * * 解决问题:用户实际已付款成功,但因网络/服务器等原因未收到微信支付回调通知, @@ -26,11 +26,11 @@ public class PayOrderCompensationTask { private final PayOrderBusinessService payOrderBusinessService; /** - * 每5分钟执行一次订单补偿扫描 - * 查询已创建超过5分钟但未超过24小时的未完成订单, + * 每1分钟执行一次订单补偿扫描 + * 查询已创建超过1分钟但未超过48小时的未完成订单, * 向微信主动查单,补偿漏掉的回调。 */ - @Scheduled(fixedDelay = 300000) + @Scheduled(fixedDelay = 60000, initialDelay = 60000) public void compensateOrders() { try { int count = payOrderBusinessService.compensateUnpaidOrders(); @@ -41,4 +41,20 @@ public class PayOrderCompensationTask { log.error("[支付补偿定时任务] 执行异常: {}", e.getMessage(), e); } } + + /** + * 每10分钟修复一次已支付订单的设备有效期。 + * 用于兜底处理历史数据或极端情况下订单成功但设备有效期未落库的问题。 + */ + @Scheduled(fixedDelay = 600000, initialDelay = 120000) + public void repairDeviceDeadTime() { + try { + int count = payOrderBusinessService.repairPaidOrderDeviceDeadTime(); + if (count > 0) { + log.warn("[支付有效期修复任务] 本次修复了 {} 台设备", count); + } + } catch (Exception e) { + log.error("[支付有效期修复任务] 执行异常: {}", e.getMessage(), e); + } + } }