Parcourir la source

订单处理优化

skyline il y a 2 mois
Parent
commit
0556a52195

+ 98 - 42
haha-miniapp/src/main/java/com/haha/miniapp/controller/CallbackController.java

@@ -16,6 +16,7 @@ import com.haha.common.enums.NotifyType;
 import com.haha.common.enums.RecognizeActionType;
 import com.haha.common.enums.RecognizeConsumeType;
 import com.haha.entity.Order;
+import com.haha.sdk.HahaClient;
 import com.haha.service.DeviceService;
 import com.haha.service.OrderService;
 import lombok.extern.slf4j.Slf4j;
@@ -71,6 +72,9 @@ public class CallbackController {
     @Autowired
     private StringRedisTemplate redisTemplate;
 
+    @Autowired
+    private HahaClient hahaClient;
+
     @Value("${haha.api.app-secret}")
     private String appSecret;
 
@@ -310,6 +314,7 @@ public class CallbackController {
      * 处理AI识别结果通知 (ORC_RESULT) - 最重要的回调
      *
      * 用户关门后,AI识别完成,哈哈平台推送识别结果
+     * 包含重复回调检测和并发安全性处理
      *
      * @param params 通知参数
      */
@@ -318,11 +323,7 @@ public class CallbackController {
             String activityId = (String) params.get("activity_id");
             String deviceId = (String) params.get("device_id");
             String userId = (String) params.get("out_user_id");
-
-            // 安全转换 nobuy 字段(兼容字符串和整型)
             Integer nobuy = parseInteger(params.get("nobuy"));
-
-            // 解析识别结果
             String resultStr = (String) params.get("result");
             String skuListStr = (String) params.get("sku_list");
             String resourceInfoStr = (String) params.get("resource_info");
@@ -330,7 +331,7 @@ public class CallbackController {
             log.info("AI识别结果通知 - 设备: {}, 活动: {}, 是否消费: {}",
                 deviceId, activityId, RecognizeConsumeType.isNoConsume(nobuy) ? "无消费" : "有消费");
 
-            // 保存识别结果到Redis,供小程序轮询查询
+            // 保存识别结果到Redis(始终更新缓存)
             String resultKey = RECOGNIZE_RESULT_KEY + activityId;
             Map<String, String> recognizeData = new HashMap<>();
             recognizeData.put("activityId", activityId != null ? activityId : "");
@@ -350,6 +351,33 @@ public class CallbackController {
                 return;
             }
 
+            // 解析 confidence
+            BigDecimal confidence = null;
+            if (params.get("confidence") != null) {
+                try {
+                    confidence = new BigDecimal(params.get("confidence").toString());
+                } catch (NumberFormatException ignored) {}
+            }
+
+            // 重复回调检测:检查是否已存在该activityId的订单
+            Order existingOrder = orderService.getOrderByActivityId(activityId);
+            if (existingOrder != null) {
+                log.info("识别结果回调重复,订单已存在 - activityId: {}, orderId: {}", activityId, existingOrder.getId());
+                // 更新Redis中的订单信息
+                String orderKey = ORDER_INFO_KEY + activityId;
+                Map<String, String> orderData = new HashMap<>();
+                orderData.put("orderId", existingOrder.getOrderNo() != null ? existingOrder.getOrderNo() : "");
+                orderData.put("activityId", activityId != null ? activityId : "");
+                orderData.put("deviceId", existingOrder.getDeviceId() != null ? existingOrder.getDeviceId() : "");
+                orderData.put("userId", existingOrder.getUserId() != null ? existingOrder.getUserId().toString() : "");
+                orderData.put("totalAmount", existingOrder.getTotalAmount() != null ? existingOrder.getTotalAmount().toString() : "0");
+                orderData.put("timestamp", String.valueOf(System.currentTimeMillis()));
+                redisTemplate.opsForHash().putAll(orderKey, orderData);
+                redisTemplate.expire(orderKey, 30, TimeUnit.MINUTES);
+                return;
+            }
+
+            // 解析识别结果详情(保持原有逻辑)
             if (resultStr != null && !resultStr.isEmpty()) {
                 JSONObject result = JSON.parseObject(resultStr);
                 String type = result.getString("type");
@@ -379,15 +407,7 @@ public class CallbackController {
                 log.info("视频URL: {}", videoUrl);
             }
 
-            // 解析 confidence
-            BigDecimal confidence = null;
-            if (params.get("confidence") != null) {
-                try {
-                    confidence = new BigDecimal(params.get("confidence").toString());
-                } catch (NumberFormatException ignored) {}
-            }
-
-            // 委托 Service 层创建订单
+            // 委托 Service 层创建订单(Service层已有幂等性检查)
             Order order = orderService.createOrderFromRecognition(
                 activityId, deviceId, userId, skuListStr, resourceInfoStr, confidence);
 
@@ -395,12 +415,6 @@ public class CallbackController {
                 log.info("AI识别订单创建成功 - 订单ID: {}", order.getId());
             }
 
-            // 3. 优惠信息处理
-            // TODO: 根据业务需求处理优惠券、折扣等
-
-            // 4. 触发支付流程
-            // TODO: 根据业务需求触发支付
-
         } catch (Exception e) {
             log.error("处理AI识别结果通知失败", e);
         }
@@ -445,17 +459,47 @@ public class CallbackController {
             log.info("订单信息 - 订单号: {}, 设备: {}, 活动: {}, 用户: {}, 金额: {}",
                 orderId, deviceId, activityId, userId, orderMoney);
 
-            // 3. 查找本地订单记录
-            Order localOrder = orderService.lambdaQuery()
-                .eq(Order::getUserId, Long.parseLong(userId))
-                .eq(Order::getDeviceId, deviceId)
-                .eq(Order::getStatus, OrderConstants.STATUS_PENDING_PAYMENT)
-                .orderByDesc(Order::getCreateTime)
-                .last("LIMIT 1")
-                .one();
+            // 3. 查找本地订单记录(优先使用activityId查询)
+            Order localOrder = null;
+            if (activityId != null && !activityId.isEmpty()) {
+                localOrder = orderService.getOrderByActivityId(activityId);
+            }
+
+            if (localOrder == null && userId != null && deviceId != null) {
+                // 订单不存在,可能是订单回调先于识别回调到达
+                // 主动调用SDK获取识别结果
+                log.info("订单不存在,主动查询识别结果 - activityId: {}", activityId);
+                try {
+                    Map<String, Object> recognizeResult = hahaClient.getOrderApi().getRecognizeResult(null, activityId);
+                    if (recognizeResult != null) {
+                        // 解析识别结果并创建订单
+                        String skuListStr = (String) recognizeResult.get("sku_list");
+                        String resourceInfoStr = (String) recognizeResult.get("resource_info");
+                        Object confidenceObj = recognizeResult.get("confidence");
+                        BigDecimal confidence = confidenceObj != null ? new BigDecimal(confidenceObj.toString()) : null;
+                        
+                        localOrder = orderService.createOrderFromRecognition(
+                            activityId, deviceId, userId, skuListStr, resourceInfoStr, confidence);
+                        
+                        if (localOrder != null) {
+                            log.info("主动创建订单成功 - orderId: {}, activityId: {}", localOrder.getId(), activityId);
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("主动查询识别结果失败 - activityId: {}", activityId, e);
+                }
+            }
 
             if (localOrder == null) {
-                log.warn("未找到对应的本地订单记录,用户: {}, 设备: {}", userId, deviceId);
+                log.warn("无法创建订单 - 缺少必要信息: userId={}, deviceId={}, activityId={}", userId, deviceId, activityId);
+                return "success";
+            }
+
+            // 检查是否已经处理过该订单(幂等性)
+            if (orderId != null && orderId.equals(localOrder.getOrderNo())) {
+                log.info("订单已处理过,跳过更新 - orderId: {}", orderId);
+                // 仍然更新Redis缓存
+                saveOrderInfoToRedis(localOrder, activityId, orderId, orderMoney, orderDetail);
                 return "success";
             }
 
@@ -481,19 +525,7 @@ public class CallbackController {
             }
 
             // 保存订单信息到Redis,供小程序轮询查询
-            String orderKey = ORDER_INFO_KEY + activityId;
-            Map<String, String> orderData = new HashMap<>();
-            orderData.put("orderId", orderId != null ? orderId : "");
-            orderData.put("activityId", activityId != null ? activityId : "");
-            orderData.put("deviceId", deviceId != null ? deviceId : "");
-            orderData.put("userId", userId != null ? userId : "");
-            orderData.put("orderName", orderName != null ? orderName : "");
-            orderData.put("totalAmount", orderMoney != null ? orderMoney.toString() : "0");
-            orderData.put("products", orderDetail != null ? orderDetail : "");
-            orderData.put("timestamp", String.valueOf(System.currentTimeMillis()));
-
-            redisTemplate.opsForHash().putAll(orderKey, orderData);
-            redisTemplate.expire(orderKey, 30, TimeUnit.MINUTES);
+            saveOrderInfoToRedis(localOrder, activityId, orderId, orderMoney, orderDetail);
 
             // 5. TODO: 根据业务需要进行后续处理
             // - 触发支付流程
@@ -602,4 +634,28 @@ public class CallbackController {
         // 4. MD5加密
         // 5. 比对签名
     }
+
+    /**
+     * 保存订单信息到Redis
+     *
+     * @param order 订单对象
+     * @param activityId 活动ID
+     * @param orderId 订单号
+     * @param orderMoney 订单金额
+     * @param orderDetail 订单明细
+     */
+    private void saveOrderInfoToRedis(Order order, String activityId, String orderId, Object orderMoney, String orderDetail) {
+        String orderKey = ORDER_INFO_KEY + activityId;
+        Map<String, String> orderData = new HashMap<>();
+        orderData.put("orderId", orderId != null ? orderId : "");
+        orderData.put("activityId", activityId != null ? activityId : "");
+        orderData.put("deviceId", order.getDeviceId() != null ? order.getDeviceId() : "");
+        orderData.put("userId", order.getUserId() != null ? order.getUserId().toString() : "");
+        orderData.put("totalAmount", orderMoney != null ? orderMoney.toString() : "0");
+        orderData.put("products", orderDetail != null ? orderDetail : "");
+        orderData.put("timestamp", String.valueOf(System.currentTimeMillis()));
+        
+        redisTemplate.opsForHash().putAll(orderKey, orderData);
+        redisTemplate.expire(orderKey, 30, TimeUnit.MINUTES);
+    }
 }

+ 11 - 0
haha-miniapp/src/main/resources/sql/order.sql

@@ -106,3 +106,14 @@ ALTER TABLE t_order ADD COLUMN service_id VARCHAR(64) DEFAULT NULL COMMENT '支
 ALTER TABLE t_order ADD COLUMN service_start_time DATETIME DEFAULT NULL COMMENT '服务开始时间';
 ALTER TABLE t_order ADD COLUMN service_end_time DATETIME DEFAULT NULL COMMENT '服务结束时间';
 ALTER TABLE t_order ADD INDEX idx_pay_score_state (pay_score_state);
+
+-- =============================================
+-- 订单幂等性:为activity_id添加唯一索引
+-- 确保同一activity_id不会创建多个订单
+-- =============================================
+-- 注意:如果已存在重复的activity_id数据,需要先清理后再添加索引
+-- 可以使用以下查询检查重复数据:
+-- SELECT activity_id, COUNT(*) as cnt FROM t_order WHERE activity_id IS NOT NULL GROUP BY activity_id HAVING COUNT(*) > 1;
+
+ALTER TABLE `t_order`
+ADD UNIQUE INDEX `uk_activity_id` (`activity_id`);

+ 8 - 0
haha-service/src/main/java/com/haha/service/OrderService.java

@@ -56,6 +56,14 @@ public interface OrderService extends IService<Order> {
     Order getOrderByOutTradeNo(String outTradeNo);
     Order getOrderBydeviceId(String deviceId);
     boolean updatePayStatus(String orderNo, String payStatus);
+    
+    /**
+     * 根据活动ID查询订单
+     *
+     * @param activityId 活动ID
+     * @return 订单,如果不存在返回null
+     */
+    Order getOrderByActivityId(String activityId);
 
     /**
      * 根据AI识别结果创建订单

+ 17 - 0
haha-service/src/main/java/com/haha/service/impl/OrderServiceImpl.java

@@ -201,6 +201,14 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
         return lambdaQuery().eq(Order::getDeviceId, deviceId).orderByDesc(Order::getCreateTime).one();
     }
 
+    @Override
+    public Order getOrderByActivityId(String activityId) {
+        if (activityId == null || activityId.isEmpty()) {
+            return null;
+        }
+        return lambdaQuery().eq(Order::getActivityId, activityId).one();
+    }
+
     @Override
     public boolean updatePayStatus(String orderNo, String payStatus) {
         return lambdaUpdate().eq(Order::getOrderNo, orderNo).set(Order::getPayStatus, payStatus).update();
@@ -210,6 +218,15 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
     @Transactional
     public Order createOrderFromRecognition(String activityId, String deviceId, String userId,
                                              String skuListStr, String resourceInfoStr, BigDecimal confidence) {
+        // 幂等性检查:如果activityId已存在订单,直接返回
+        if (activityId != null && !activityId.isEmpty()) {
+            Order existingOrder = getOrderByActivityId(activityId);
+            if (existingOrder != null) {
+                log.info("订单已存在,跳过创建 - activityId: {}, orderId: {}", activityId, existingOrder.getId());
+                return existingOrder;
+            }
+        }
+        
         // 1. 计算订单金额(从 skuListStr 解析 JSONArray,遍历计算 price * quantity)
         BigDecimal totalAmount = BigDecimal.ZERO;
         JSONArray skuList = null;