Bläddra i källkod

refactor: 简化支付分订单清理逻辑,去除过度设计的暴力扫描

用户说得对,生产环境下回调正常运转,不需要复杂的暴力扫描。
- 保留 DB 进行中订单清理(已有的本地订单)
- 保留 Redis SET 追踪预创建订单(防止成为孤儿)
- 移除暴力扫描、DoorRecordService 依赖、重试逻辑
- 清理逻辑精简到约60行

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 2 veckor sedan
förälder
incheckning
2fbdb29544

+ 21 - 157
haha-service/src/main/java/com/haha/service/payment/payscore/impl/PayScoreServiceImpl.java

@@ -9,10 +9,8 @@ import com.haha.common.enums.PayStatus;
 import com.haha.common.enums.PaymentChannel;
 import com.haha.entity.Order;
 import com.haha.entity.User;
-import com.haha.entity.DoorRecord;
 import com.haha.service.OrderService;
 import com.haha.service.UserService;
-import com.haha.service.DoorRecordService;
 import com.haha.service.payment.config.WxPayConfig;
 import com.haha.service.payment.payscore.*;
 import com.haha.service.payment.payscore.PayScoreCreateRequest.PostPayment;
@@ -49,9 +47,6 @@ public class PayScoreServiceImpl implements PayScoreService {
     @Autowired
     private UserService userService;
 
-    @Autowired
-    private DoorRecordService doorRecordService;
-
     @Autowired(required = false)
     private WxPayConfig wxPayConfig;
 
@@ -750,8 +745,8 @@ public class PayScoreServiceImpl implements PayScoreService {
             return errorData;
         }
 
-        // 取消用户旧的进行中支付分订单(DB + Redis追踪的 + 暴力清理
-        cancelUserInProgressPayScoreOrders(userId, deviceId);
+        // 清理该用户旧的进行中支付分订单(DB中的 + 同设备Redis追踪的
+        cancelStalePayScoreOrders(userId, deviceId);
 
         // 如果没有传 openId,从用户表中获取
         if (openId == null || openId.isEmpty()) {
@@ -765,20 +760,6 @@ public class PayScoreServiceImpl implements PayScoreService {
             openId = user.getOpenid();
         }
 
-        // 尝试创建,失败时如果是"进行中订单过多"则暴力清理后重试
-        Map<String, Object> result = doCreatePayScoreOrder(userId, deviceId, openId);
-        if (!Boolean.TRUE.equals(result.get("success"))) {
-            String errorMsg = (String) result.get("errorMsg");
-            if (errorMsg != null && (errorMsg.contains("进行中") || errorMsg.contains("过多"))) {
-                log.warn("[支付分服务] 首次创建失败(进行中订单过多),尝试暴力清理 - userId: {}", userId);
-                bruteForceCancelRecentOrders(userId, deviceId);
-                result = doCreatePayScoreOrder(userId, deviceId, openId);
-            }
-        }
-        return result;
-    }
-
-    private Map<String, Object> doCreatePayScoreOrder(Long userId, String deviceId, String openId) {
         String outOrderNo = generatePreAuthOutOrderNo(deviceId);
 
         PayScoreCreateRequest request = new PayScoreCreateRequest();
@@ -795,7 +776,8 @@ public class PayScoreServiceImpl implements PayScoreService {
         if (result.isSuccess()) {
             trackPreAuthOrder(userId, outOrderNo);
             storePreAuthToRedis(deviceId, userId, result);
-            untrackPreAuthOrdersByDevice(userId, deviceId, outOrderNo);
+            // 取消同设备旧的追踪订单(已被本次新订单替代)
+            cancelOldDeviceOrders(userId, deviceId, outOrderNo);
 
             Map<String, Object> data = new HashMap<>();
             data.put("success", true);
@@ -871,10 +853,11 @@ public class PayScoreServiceImpl implements PayScoreService {
     }
 
     /**
-     * 取消用户所有进行中的支付分订单(DB订单 + Redis追踪的预创建订单 + 暴力扫描)
+     * 清理用户旧的进行中支付分订单
+     * 生产环境下回调正常运转后,此方法只在用户多次快速扫码(测试/异常)时触发
      */
-    private void cancelUserInProgressPayScoreOrders(Long userId, String deviceId) {
-        // 1. 取消 DB 中的进行中支付分订单
+    private void cancelStalePayScoreOrders(Long userId, String deviceId) {
+        // 1. 取消 DB 中该用户进行中的支付分订单
         try {
             List<Order> inProgressOrders = orderService.lambdaQuery()
                     .eq(Order::getUserId, userId)
@@ -885,27 +868,25 @@ public class PayScoreServiceImpl implements PayScoreService {
                     .list();
 
             if (inProgressOrders != null && !inProgressOrders.isEmpty()) {
-                log.info("[支付分服务] 发现用户 {} 有 {} 笔DB中的进行中支付分订单,开始清理",
-                        userId, inProgressOrders.size());
+                log.info("[支付分服务] 清理用户 {} 的 {} 笔DB进行中订单", userId, inProgressOrders.size());
                 for (Order order : inProgressOrders) {
-                    cancelSingleByOutOrderNo(order.getPayScoreOrderId(), order.getId());
+                    cancelPayScoreOrderByOutOrderNo(order.getPayScoreOrderId(), order.getId());
                 }
             }
         } catch (Exception e) {
             log.error("[支付分服务] 清理DB进行中订单失败 - userId: {}", userId, e);
         }
 
-        // 2. 取消 Redis 追踪 SET 中记录的预创建订单
+        // 2. 取消 Redis 追踪的预创建订单(之前未完成购物流程留下的)
         if (stringRedisTemplate != null) {
             try {
                 String trackingKey = REDIS_TRACKING_KEY + userId;
                 java.util.Set<String> trackedOrders = stringRedisTemplate.opsForSet().members(trackingKey);
                 if (trackedOrders != null && !trackedOrders.isEmpty()) {
-                    log.info("[支付分服务] 发现用户 {} 有 {} 笔Redis追踪的预创建订单,开始清理",
-                            userId, trackedOrders.size());
+                    log.info("[支付分服务] 清理用户 {} 的 {} 笔追踪预创建订单", userId, trackedOrders.size());
                     for (String outOrderNo : trackedOrders) {
                         if (outOrderNo != null && !outOrderNo.isEmpty()) {
-                            cancelSingleByOutOrderNo(outOrderNo, null);
+                            cancelPayScoreOrderByOutOrderNo(outOrderNo, null);
                             stringRedisTemplate.opsForSet().remove(trackingKey, outOrderNo);
                         }
                     }
@@ -914,22 +895,17 @@ public class PayScoreServiceImpl implements PayScoreService {
                 log.error("[支付分服务] 清理Redis追踪订单失败 - userId: {}", userId, e);
             }
         }
-
     }
 
-    /**
-     * 通过 outOrderNo 调用微信API取消单个支付分订单
-     */
-    private void cancelSingleByOutOrderNo(String outOrderNo, Long localOrderId) {
+    private void cancelPayScoreOrderByOutOrderNo(String outOrderNo, Long localOrderId) {
         try {
             PayScoreCancelRequest cancelReq = new PayScoreCancelRequest();
             cancelReq.setOutOrderNo(outOrderNo);
-            cancelReq.setReason("自动取消:创建新订单前清理旧订单");
+            cancelReq.setReason("新订单创建前自动取消旧订单");
             PayScoreResult cancelResult = payScoreStrategy.cancelServiceOrder(cancelReq);
 
             if (cancelResult.isSuccess()) {
-                log.info("[支付分服务] 取消成功 - outOrderNo: {}, localOrderId: {}", outOrderNo, localOrderId);
-                // 如果有本地订单,同步更新状态
+                log.info("[支付分服务] 取消成功 - outOrderNo: {}", outOrderNo);
                 if (localOrderId != null) {
                     Order order = orderService.getById(localOrderId);
                     if (order != null) {
@@ -937,126 +913,14 @@ public class PayScoreServiceImpl implements PayScoreService {
                         orderService.updateById(order);
                     }
                 }
-            } else {
-                // ORDER_NOT_EXIST 表示微信侧已不存在,忽略
-                if (cancelResult.getErrorMsg() != null && cancelResult.getErrorMsg().contains("ORDER_NOT_EXIST")) {
-                    log.debug("[支付分服务] 订单在微信侧已不存在 - outOrderNo: {}", outOrderNo);
-                } else {
-                    log.debug("[支付分服务] 取消失败 - outOrderNo: {}, error: {}",
-                            outOrderNo, cancelResult.getErrorMsg());
-                }
-            }
-        } catch (Exception e) {
-            log.debug("[支付分服务] 取消异常 - outOrderNo: {}, error: {}", outOrderNo, e.getMessage());
-        }
-    }
-
-    /**
-     * 暴力扫描并取消最近一段时间内的预创建订单
-     * 当微信返回"进行中订单过多"时,尝试用不同时间戳构造 outOrderNo 并取消
-     * 扫描该用户所有历史设备 + 当前设备,时间窗口 4 小时
-     */
-    private void bruteForceCancelRecentOrders(Long userId, String deviceId) {
-        if (payScoreStrategy == null) {
-            return;
-        }
-
-        // 收集所有可能的设备ID后缀
-        java.util.Set<String> suffixes = new java.util.HashSet<>();
-        addSuffix(suffixes, deviceId);
-
-        // 从 DB 中查找该用户关联过的所有设备
-        try {
-            java.util.Set<String> dbDevices = new java.util.HashSet<>();
-            List<Order> orders = orderService.lambdaQuery()
-                    .eq(Order::getUserId, userId)
-                    .select(Order::getDeviceId)
-                    .isNotNull(Order::getDeviceId)
-                    .list();
-            if (orders != null) {
-                for (Order o : orders) {
-                    if (o.getDeviceId() != null) {
-                        dbDevices.add(o.getDeviceId());
-                    }
-                }
-            }
-            List<DoorRecord> records = doorRecordService.lambdaQuery()
-                    .eq(DoorRecord::getUserId, userId)
-                    .select(DoorRecord::getDeviceId)
-                    .isNotNull(DoorRecord::getDeviceId)
-                    .list();
-            if (records != null) {
-                for (DoorRecord r : records) {
-                    if (r.getDeviceId() != null) {
-                        dbDevices.add(r.getDeviceId());
-                    }
-                }
-            }
-            for (String devId : dbDevices) {
-                addSuffix(suffixes, devId);
             }
         } catch (Exception e) {
-            log.debug("[支付分服务] 查询用户设备列表异常: {}", e.getMessage());
-        }
-
-        log.info("[支付分服务] 暴力扫描 {} 个设备后缀", suffixes.size());
-
-        long nowSec = System.currentTimeMillis() / 1000;
-        int cancelledCount = 0;
-        int totalAttempts = 0;
-
-        // 高精度扫描最近5分钟(每1秒),覆盖密集重试场景
-        // 之后每15秒扫描到1小时,覆盖较早的孤儿订单
-        int[][] scanStages = {
-            {0, 300, 1},      // 0-5min, 每1秒 (300次/设备)
-            {300, 3600, 15},   // 5min-1h, 每15秒 (220次/设备)
-        };
-
-        for (int[] stage : scanStages) {
-            int startSec = stage[0];
-            int endSec = stage[1];
-            int interval = stage[2];
-
-            for (int s = startSec; s < endSec; s += interval) {
-                long ts = nowSec - s;
-                for (String suffix : suffixes) {
-                    totalAttempts++;
-                    String outOrderNo = "PS" + ts + suffix;
-                    try {
-                        PayScoreCancelRequest cancelReq = new PayScoreCancelRequest();
-                        cancelReq.setOutOrderNo(outOrderNo);
-                        cancelReq.setReason("自动清理孤儿订单");
-                        PayScoreResult cancelResult = payScoreStrategy.cancelServiceOrder(cancelReq);
-                        if (cancelResult.isSuccess()) {
-                            cancelledCount++;
-                            if (cancelledCount <= 5) {
-                                log.info("[支付分服务] 暴力清理成功 - outOrderNo: {}", outOrderNo);
-                            }
-                        }
-                    } catch (Exception ignored) {
-                    }
-                }
-            }
-        }
-        if (cancelledCount > 0) {
-            log.info("[支付分服务] 暴力清理完成 - 成功取消 {} 笔孤儿订单 (总尝试 {} 次)",
-                    cancelledCount, totalAttempts);
-        } else {
-            log.info("[支付分服务] 暴力清理完成 - 未找到孤儿订单 (总尝试 {} 次)", totalAttempts);
-        }
-    }
-
-    private void addSuffix(java.util.Set<String> suffixes, String deviceId) {
-        if (deviceId == null || deviceId.isEmpty()) {
-            return;
+            log.debug("[支付分服务] 取消订单异常(可能微信侧已不存在) - outOrderNo: {}", outOrderNo);
         }
-        String safe = deviceId.replaceAll("[^A-Za-z0-9]", "");
-        String suffix = safe.length() > 10 ? safe.substring(safe.length() - 10) : safe;
-        suffixes.add(suffix);
     }
 
     /**
-     * 将预创建的 outOrderNo 记录到 Redis SET 中,防止成为孤儿订单
+     * 将预创建的 outOrderNo 记录到 Redis SET,回调正常关联后自动移除
      */
     private void trackPreAuthOrder(Long userId, String outOrderNo) {
         if (stringRedisTemplate == null) {
@@ -1072,9 +936,9 @@ public class PayScoreServiceImpl implements PayScoreService {
     }
 
     /**
-     * 清除同一设备旧的追踪记录(同一设备的新订单会替代旧订单
+     * 取消同设备旧的预创建订单(已被本次新订单替代
      */
-    private void untrackPreAuthOrdersByDevice(Long userId, String deviceId, String currentOutOrderNo) {
+    private void cancelOldDeviceOrders(Long userId, String deviceId, String currentOutOrderNo) {
         if (stringRedisTemplate == null) {
             return;
         }
@@ -1086,7 +950,7 @@ public class PayScoreServiceImpl implements PayScoreService {
             if (tracked != null) {
                 for (String outOrderNo : tracked) {
                     if (outOrderNo != null && outOrderNo.endsWith(suffix) && !outOrderNo.equals(currentOutOrderNo)) {
-                        cancelSingleByOutOrderNo(outOrderNo, null);
+                        cancelPayScoreOrderByOutOrderNo(outOrderNo, null);
                         stringRedisTemplate.opsForSet().remove(trackingKey, outOrderNo);
                     }
                 }