Selaa lähdekoodia

订单到期改状态

jyc 1 kuukausi sitten
vanhempi
commit
0d543c5714

+ 33 - 0
alien-store/src/main/java/shop/alien/store/config/RedisKeyExpirationConfig.java

@@ -0,0 +1,33 @@
+package shop.alien.store.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.listener.RedisMessageListenerContainer;
+
+/**
+ * Redis Key過期監聽配置
+ * 
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Configuration
+public class RedisKeyExpirationConfig {
+
+    /**
+     * 配置Redis消息監聽容器,用於監聽key過期事件
+     * 
+     * @param connectionFactory Redis連接工廠
+     * @return Redis消息監聽容器
+     */
+    @Bean
+    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
+        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
+        container.setConnectionFactory(connectionFactory);
+        log.info("Redis Key過期監聽容器初始化完成");
+        return container;
+    }
+}
+

+ 90 - 0
alien-store/src/main/java/shop/alien/store/listener/RedisKeyExpirationHandler.java

@@ -0,0 +1,90 @@
+package shop.alien.store.listener;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * Redis Key過期事件處理器
+ * 支持註冊不同key前綴的處理邏輯
+ * 
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Component
+public class RedisKeyExpirationHandler {
+
+    /**
+     * 存儲不同key前綴對應的處理器
+     * key: key前綴(如 "order:pay:")
+     * value: 處理邏輯
+     */
+    private final Map<String, Consumer<String>> handlers = new ConcurrentHashMap<>();
+
+    /**
+     * 處理過期的key
+     * 
+     * @param expiredKey 過期的key
+     */
+    public void handleExpiration(String expiredKey) {
+        log.info("開始處理過期Key: {}", expiredKey);
+        
+        // 遍歷所有註冊的處理器,找到匹配的前綴
+        for (Map.Entry<String, Consumer<String>> entry : handlers.entrySet()) {
+            String prefix = entry.getKey();
+            if (expiredKey.startsWith(prefix)) {
+                try {
+                    log.info("找到匹配的處理器,前綴: {}, key: {}", prefix, expiredKey);
+                    entry.getValue().accept(expiredKey);
+                } catch (Exception e) {
+                    log.error("執行過期處理邏輯失敗,前綴: {}, key: {}", prefix, expiredKey, e);
+                }
+                return;
+            }
+        }
+        
+        log.warn("未找到匹配的處理器,key: {}", expiredKey);
+    }
+
+    /**
+     * 註冊key過期處理器
+     * 
+     * @param keyPrefix key前綴(如 "order:pay:")
+     * @param handler 處理邏輯
+     */
+    public void registerHandler(String keyPrefix, Consumer<String> handler) {
+        if (keyPrefix == null || keyPrefix.isEmpty()) {
+            throw new IllegalArgumentException("key前綴不能為空");
+        }
+        if (handler == null) {
+            throw new IllegalArgumentException("處理器不能為null");
+        }
+        
+        handlers.put(keyPrefix, handler);
+        log.info("註冊Redis Key過期處理器,前綴: {}", keyPrefix);
+    }
+
+    /**
+     * 移除key過期處理器
+     * 
+     * @param keyPrefix key前綴
+     */
+    public void unregisterHandler(String keyPrefix) {
+        handlers.remove(keyPrefix);
+        log.info("移除Redis Key過期處理器,前綴: {}", keyPrefix);
+    }
+
+    /**
+     * 獲取所有已註冊的前綴
+     * 
+     * @return 前綴集合
+     */
+    public java.util.Set<String> getRegisteredPrefixes() {
+        return handlers.keySet();
+    }
+}
+

+ 47 - 0
alien-store/src/main/java/shop/alien/store/listener/RedisKeyExpirationListener.java

@@ -0,0 +1,47 @@
+package shop.alien.store.listener;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.connection.Message;
+import org.springframework.data.redis.connection.MessageListener;
+import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
+import org.springframework.data.redis.listener.RedisMessageListenerContainer;
+import org.springframework.stereotype.Component;
+
+/**
+ * Redis Key過期事件監聽器
+ * 
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Component
+public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
+
+    private final RedisKeyExpirationHandler expirationHandler;
+
+    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer,
+                                      RedisKeyExpirationHandler expirationHandler) {
+        super(listenerContainer);
+        this.expirationHandler = expirationHandler;
+    }
+
+    /**
+     * 當Redis key過期時觸發此方法
+     * 
+     * @param message 過期消息,包含過期的key
+     * @param pattern 匹配的模式
+     */
+    @Override
+    public void onMessage(Message message, byte[] pattern) {
+        String expiredKey = message.toString();
+        log.info("檢測到Redis Key過期: {}", expiredKey);
+        
+        try {
+            // 處理過期事件
+            expirationHandler.handleExpiration(expiredKey);
+        } catch (Exception e) {
+            log.error("處理Redis Key過期事件失敗, key: {}", expiredKey, e);
+        }
+    }
+}
+

+ 35 - 0
alien-store/src/main/java/shop/alien/store/service/OrderExpirationService.java

@@ -0,0 +1,35 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.store.LawyerConsultationOrder;
+
+/**
+ * 訂單過期處理服務接口
+ * 
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface OrderExpirationService {
+
+    /**
+     * 處理訂單支付超時
+     * 
+     * @param orderId 訂單ID
+     */
+    void handleOrderPaymentTimeout(Integer orderId);
+
+    /**
+     * 設置訂單支付超時監聽
+     * 
+     * @param orderId 訂單ID
+     * @param timeoutSeconds 超時時間(秒),默認30分鐘
+     */
+    void setOrderPaymentTimeout(Integer orderId, long timeoutSeconds);
+
+    /**
+     * 取消訂單支付超時監聽(當訂單已支付時調用)
+     * 
+     * @param orderId 訂單ID
+     */
+    void cancelOrderPaymentTimeout(Integer orderId);
+}
+

+ 144 - 0
alien-store/src/main/java/shop/alien/store/service/impl/OrderExpirationServiceImpl.java

@@ -0,0 +1,144 @@
+package shop.alien.store.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.LawyerConsultationOrder;
+import shop.alien.store.config.BaseRedisService;
+import shop.alien.store.listener.RedisKeyExpirationHandler;
+import shop.alien.store.service.OrderExpirationService;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * 訂單過期處理服務實現類
+ * 
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class OrderExpirationServiceImpl implements OrderExpirationService, CommandLineRunner {
+
+    private final BaseRedisService redisService;
+    private final RedisKeyExpirationHandler expirationHandler;
+    private final LawyerConsultationOrderServiceImpl orderService;
+
+    /**
+     * Redis key前綴:訂單支付超時
+     */
+    private static final String ORDER_PAYMENT_TIMEOUT_PREFIX = "lawyer:order:payment:timeout:";
+
+    /**
+     * 默認超時時間:30分鐘
+     */
+    private static final long DEFAULT_TIMEOUT_SECONDS = 30 * 60;
+
+    /**
+     * 初始化時註冊訂單支付超時處理器
+     */
+    @PostConstruct
+    public void init() {
+        // 註冊訂單支付超時處理器
+        expirationHandler.registerHandler(ORDER_PAYMENT_TIMEOUT_PREFIX, this::handleExpiredOrderKey);
+        log.info("訂單支付超時處理器註冊完成,前綴: {}", ORDER_PAYMENT_TIMEOUT_PREFIX);
+    }
+
+    @Override
+    public void run(String... args) {
+        log.info("OrderExpirationService 初始化完成");
+    }
+
+    /**
+     * 處理過期的訂單key
+     * 
+     * @param expiredKey 過期的key,格式:order:payment:timeout:{orderId}
+     */
+    private void handleExpiredOrderKey(String expiredKey) {
+        try {
+            // 從key中提取訂單ID
+            String orderIdStr = expiredKey.replace(ORDER_PAYMENT_TIMEOUT_PREFIX, "");
+            Integer orderId = Integer.parseInt(orderIdStr);
+            
+            log.info("檢測到訂單支付超時,訂單ID: {}", orderId);
+            
+            // 處理訂單支付超時
+            handleOrderPaymentTimeout(orderId);
+        } catch (Exception e) {
+            log.error("處理過期訂單key失敗,key: {}", expiredKey, e);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void handleOrderPaymentTimeout(Integer orderId) {
+        log.info("開始處理訂單支付超時,訂單ID: {}", orderId);
+        
+        try {
+            // 查詢訂單
+            LawyerConsultationOrder order = orderService.getById(orderId);
+            if (order == null) {
+                log.warn("訂單不存在,訂單ID: {}", orderId);
+                return;
+            }
+
+            // 只處理待支付狀態的訂單
+            if (order.getOrderStatus() != null && order.getOrderStatus() == 0) {
+                log.info("訂單處於待支付狀態,開始取消訂單,訂單ID: {}", orderId);
+                
+                // 更新訂單狀態為已取消
+                order.setOrderStatus(4); // 4:已取消
+                order.setUpdatedTime(new java.util.Date());
+                
+                boolean updated = orderService.updateById(order);
+                if (updated) {
+                    log.info("訂單支付超時,已自動取消訂單,訂單ID: {}", orderId);
+                } else {
+                    log.error("取消訂單失敗,訂單ID: {}", orderId);
+                }
+            } else {
+                log.info("訂單狀態不是待支付,無需處理,訂單ID: {}, 當前狀態: {}", orderId, order.getOrderStatus());
+            }
+        } catch (Exception e) {
+            log.error("處理訂單支付超時失敗,訂單ID: {}", orderId, e);
+            throw e;
+        }
+    }
+
+    @Override
+    public void setOrderPaymentTimeout(Integer orderId, long timeoutSeconds) {
+        if (orderId == null) {
+            log.warn("訂單ID為null,無法設置支付超時監聽");
+            return;
+        }
+        
+        String key = ORDER_PAYMENT_TIMEOUT_PREFIX + orderId;
+        long timeout = timeoutSeconds > 0 ? timeoutSeconds : DEFAULT_TIMEOUT_SECONDS;
+        
+        // 設置Redis key,帶過期時間
+        // 當key過期時,會觸發RedisKeyExpirationListener
+        redisService.setString(key, String.valueOf(orderId), timeout);
+        
+        log.info("設置訂單支付超時監聽,訂單ID: {}, 超時時間: {}秒, key: {}", orderId, timeout, key);
+    }
+
+    /**
+     * 取消訂單支付超時監聽(當訂單已支付時調用)
+     * 
+     * @param orderId 訂單ID
+     */
+    public void cancelOrderPaymentTimeout(Integer orderId) {
+        if (orderId == null) {
+            return;
+        }
+        
+        String key = ORDER_PAYMENT_TIMEOUT_PREFIX + orderId;
+        redisService.delete(key);
+        
+        log.info("取消訂單支付超時監聽,訂單ID: {}, key: {}", orderId, key);
+    }
+}
+