最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot集成Redis實現(xiàn)訂單超時管理的詳細步驟

 更新時間:2026年03月10日 09:59:50   作者:悟空碼字  
文章描述了如何使用Redis來處理超時支付訂單,包括引入依賴、配置連接、定義實體類、實現(xiàn)服務和監(jiān)聽器等步驟,文章討論了優(yōu)化方案、注意事項以及Redis的性能和可靠性問題,感興趣的朋友跟隨小編一起看看吧

聽說你要用Redis來處理超時支付訂單?Redis就像一個住在你內存里的閃電俠,它跑得飛快,但記性有點差(斷電就失憶)。它是個鍵值對存儲的社交恐懼癥患者,就喜歡簡單直接的交流。不過對付訂單超時這種“限時任務”,它可是專業(yè)的“時間管理大師”!

為什么選Redis來做這個?

你開了一家網紅奶茶店,顧客下單后30分鐘不付款,訂單就自動取消。你總不能雇個店員盯著每個訂單看30分鐘吧?Redis的過期鍵和發(fā)布訂閱功能,就是那個不知疲倦的“自動取消專員”!

詳細步驟:讓我們開始組裝這個“訂單取消機器人”

第1步:引入Redis依賴包

<!-- pom.xml 里加入這個“能量飲料” -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

第2步:配置Redis連接

# application.yml
spring:
  redis:
    # Redis的地址,默認是本地6379端口
    host: localhost
    port: 6379
    # 密碼(如果設置了的話)
    password: 
    # 數(shù)據庫索引,就像給閃電俠安排的第幾個房間
    database: 0
    lettuce:
      pool:
        # 連接池配置,別讓閃電俠累著了
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: 100ms

第3步:配置RedisTemplate

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // 鍵的序列化 - 字符串序列化
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        // 值的序列化 - JSON序列化
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        mapper.activateDefaultTyping(
            mapper.getPolymorphicTypeValidator(),
            ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.PROPERTY
        );
        serializer.setObjectMapper(mapper);
        template.setValueSerializer(serializer);
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }
}

第4步:訂單實體類

import lombok.Data;
import java.time.LocalDateTime;
@Data
public class Order {
    private String orderId;          // 訂單ID
    private String userId;           // 用戶ID
    private Double amount;           // 訂單金額
    private Integer status;          // 訂單狀態(tài):0-待支付,1-已支付,2-已取消
    private LocalDateTime createTime;// 創(chuàng)建時間
    private LocalDateTime expireTime;// 過期時間
    // 判斷是否已過期
    public boolean isExpired() {
        return LocalDateTime.now().isAfter(expireTime);
    }
}

第5步:Redis服務類

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedisOrderService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    // 訂單前綴,避免鍵沖突
    private static final String ORDER_KEY_PREFIX = "order:pay:";
    private static final String ORDER_EXPIRE_CHANNEL = "order.expire";
    /**
     * 創(chuàng)建訂單并設置30分鐘過期時間
     * 就像給閃電俠說:“盯著這個訂單,30分鐘后提醒我”
     */
    public void createOrderWithExpire(Order order, int expireMinutes) {
        String orderKey = ORDER_KEY_PREFIX + order.getOrderId();
        // 保存訂單到Redis,30分鐘后自動刪除
        redisTemplate.opsForValue().set(
            orderKey, 
            order, 
            expireMinutes, 
            TimeUnit.MINUTES
        );
        // 同時設置一個簡單的標志,用于監(jiān)聽過期事件
        stringRedisTemplate.opsForValue().set(
            orderKey + ":flag", 
            "1", 
            expireMinutes, 
            TimeUnit.MINUTES
        );
        System.out.println("訂單 " + order.getOrderId() + " 已放入Redis,設置" + 
                          expireMinutes + "分鐘后過期");
    }
    /**
     * 用戶支付成功,刪除過期鍵
     * 相當于告訴閃電俠:“不用盯了,顧客付錢了!”
     */
    public void handlePaymentSuccess(String orderId) {
        String orderKey = ORDER_KEY_PREFIX + orderId;
        // 手動刪除訂單和標志
        redisTemplate.delete(orderKey);
        stringRedisTemplate.delete(orderKey + ":flag");
        System.out.println("訂單 " + orderId + " 支付成功,已從Redis移除");
    }
    /**
     * 檢查訂單是否還存在(是否已過期)
     */
    public boolean isOrderExist(String orderId) {
        String orderKey = ORDER_KEY_PREFIX + orderId;
        return Boolean.TRUE.equals(redisTemplate.hasKey(orderKey));
    }
    /**
     * 獲取訂單信息
     */
    public Order getOrder(String orderId) {
        String orderKey = ORDER_KEY_PREFIX + orderId;
        return (Order) redisTemplate.opsForValue().get(orderKey);
    }
}

第6步:Redis過期監(jiān)聽配置

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.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
@Configuration
public class RedisExpireConfig {
    @Bean
    public RedisMessageListenerContainer container(
            RedisConnectionFactory connectionFactory,
            MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 監(jiān)聽所有key過期事件
        container.addMessageListener(listenerAdapter, 
            new PatternTopic("__keyevent@0__:expired"));
        return container;
    }
    @Bean
    public MessageListenerAdapter listenerAdapter(RedisKeyExpireListener receiver) {
        return new MessageListenerAdapter(receiver, "handleMessage");
    }
}

第7步:過期事件監(jiān)聽器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
@Component
public class RedisKeyExpireListener extends MessageListenerAdapter {
    @Autowired
    private OrderService orderService;
    /**
     * 當Redis鍵過期時,這個方法會被調用
     * 閃電俠會喊:“嘿!那個訂單過期了!”
     */
    @Override
    public void handleMessage(Message message, byte[] pattern) {
        String expiredKey = new String(message.getBody(), StandardCharsets.UTF_8);
        // 只處理我們的訂單過期鍵
        if (expiredKey.startsWith("order:pay:")) {
            // 去掉":flag"后綴獲取訂單ID
            String orderId = expiredKey
                .replace("order:pay:", "")
                .replace(":flag", "");
            System.out.println("Redis報告:訂單 " + orderId + " 已超時!");
            // 處理訂單超時邏輯
            orderService.cancelExpiredOrder(orderId);
        }
    }
}

第8步:訂單服務層

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
    @Autowired
    private RedisOrderService redisOrderService;
    @Autowired
    private OrderRepository orderRepository;
    /**
     * 創(chuàng)建訂單
     */
    @Transactional
    public Order createOrder(String userId, Double amount) {
        Order order = new Order();
        order.setOrderId(generateOrderId());
        order.setUserId(userId);
        order.setAmount(amount);
        order.setStatus(0); // 待支付
        order.setCreateTime(LocalDateTime.now());
        order.setExpireTime(LocalDateTime.now().plusMinutes(30));
        // 保存到數(shù)據庫
        orderRepository.save(order);
        // 保存到Redis并設置30分鐘過期
        redisOrderService.createOrderWithExpire(order, 30);
        return order;
    }
    /**
     * 處理支付回調
     */
    @Transactional
    public void handlePaymentCallback(String orderId) {
        // 檢查訂單是否已過期
        if (!redisOrderService.isOrderExist(orderId)) {
            throw new RuntimeException("訂單已超時,請重新下單");
        }
        // 更新訂單狀態(tài)為已支付
        orderRepository.updateOrderStatus(orderId, 1);
        // 從Redis移除過期鍵
        redisOrderService.handlePaymentSuccess(orderId);
        System.out.println("訂單 " + orderId + " 支付處理完成");
    }
    /**
     * 取消超時訂單
     */
    @Transactional
    public void cancelExpiredOrder(String orderId) {
        // 再次檢查,防止重復處理
        Order order = orderRepository.findById(orderId);
        if (order != null && order.getStatus() == 0) {
            order.setStatus(2); // 已取消
            orderRepository.save(order);
            // 可以在這里添加其他邏輯,比如釋放庫存、發(fā)送通知等
            System.out.println("訂單 " + orderId + " 因超時未支付已被自動取消");
            // 發(fā)送取消通知
            sendCancelNotification(order);
        }
    }
    /**
     * 發(fā)送取消通知(模擬)
     */
    private void sendCancelNotification(Order order) {
        // 這里可以集成消息隊列、郵件、短信等
        System.out.println("發(fā)送通知:親愛的用戶" + order.getUserId() + 
                          ",您的訂單" + order.getOrderId() + "因超時未支付已取消");
    }
    private String generateOrderId() {
        return "ORD" + System.currentTimeMillis() + 
               (int)(Math.random() * 1000);
    }
}

第9步:控制器層

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/orders")
public class OrderController {
    @Autowired
    private OrderService orderService;
    @Autowired
    private RedisOrderService redisOrderService;
    /**
     * 創(chuàng)建訂單
     */
    @PostMapping("/create")
    public ApiResult createOrder(@RequestParam String userId, 
                                @RequestParam Double amount) {
        try {
            Order order = orderService.createOrder(userId, amount);
            return ApiResult.success("訂單創(chuàng)建成功", order);
        } catch (Exception e) {
            return ApiResult.error("訂單創(chuàng)建失?。? + e.getMessage());
        }
    }
    /**
     * 模擬支付
     */
    @PostMapping("/pay")
    public ApiResult payOrder(@RequestParam String orderId) {
        try {
            // 模擬支付處理時間
            Thread.sleep(1000);
            orderService.handlePaymentCallback(orderId);
            return ApiResult.success("支付成功");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return ApiResult.error("支付處理中斷");
        } catch (Exception e) {
            return ApiResult.error("支付失?。? + e.getMessage());
        }
    }
    /**
     * 檢查訂單狀態(tài)
     */
    @GetMapping("/status/{orderId}")
    public ApiResult checkOrderStatus(@PathVariable String orderId) {
        boolean exists = redisOrderService.isOrderExist(orderId);
        if (exists) {
            return ApiResult.success("訂單有效,請盡快支付");
        } else {
            return ApiResult.success("訂單已超時或不存在");
        }
    }
}
// 簡單的返回結果類
class ApiResult {
    private boolean success;
    private String message;
    private Object data;
    // 構造方法和getter/setter省略...
    public static ApiResult success(String message) {
        return new ApiResult(true, message, null);
    }
    public static ApiResult success(String message, Object data) {
        return new ApiResult(true, message, data);
    }
    public static ApiResult error(String message) {
        return new ApiResult(false, message, null);
    }
}

第10步:別忘了開啟Redis的鍵空間通知(重要?。?/h3>

在Redis配置文件(redis.conf)中或通過Redis命令行開啟:

# 方式1:配置文件
notify-keyspace-events "Ex"
# 方式2:命令行(臨時生效)
redis-cli config set notify-keyspace-events Ex

或者在你的Spring Boot應用啟動時自動配置:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class RedisConfigRunner implements CommandLineRunner {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public void run(String... args) {
        // 開啟鍵過期事件通知
        stringRedisTemplate.getConnectionFactory()
            .getConnection()
            .serverCommands()
            .configSet("notify-keyspace-events", "Ex");
        System.out.println("Redis鍵空間通知已開啟");
    }
}

完整的工作流程

  1. 顧客下單POST /orders/create → 訂單存入數(shù)據庫和Redis,開始30分鐘倒計時
  2. Redis盯梢:閃電俠開始計時,30分鐘寸步不離
  3. 顧客支付
    • 30分鐘內支付:POST /orders/pay → Redis刪除訂單,交易完成
    • 超過30分鐘:Redis鍵自動過期 → 觸發(fā)過期事件 → 自動取消訂單
  4. 系統(tǒng)通知:給顧客發(fā)送“訂單已取消”的貼心小提示

一些高級玩法

方案優(yōu)化:使用Redisson的延遲隊列(更可靠)

import org.redisson.api.RBlockingDeque;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
@Service
public class RedissonOrderService {
    @Autowired
    private RedissonClient redissonClient;
    private RBlockingDeque<String> orderQueue;
    private RDelayedQueue<String> delayedQueue;
    @PostConstruct
    public void init() {
        orderQueue = redissonClient.getBlockingDeque("orderDelayQueue");
        delayedQueue = redissonClient.getDelayedQueue(orderQueue);
        // 啟動消費者線程
        new Thread(this::consumeExpiredOrders).start();
    }
    /**
     * 添加延遲訂單
     */
    public void addDelayOrder(String orderId, long delay, TimeUnit unit) {
        delayedQueue.offer(orderId, delay, unit);
        System.out.println("訂單 " + orderId + " 已加入延遲隊列,"
            + delay + " " + unit + "后過期");
    }
    /**
     * 消費過期訂單
     */
    private void consumeExpiredOrders() {
        while (true) {
            try {
                // 阻塞獲取過期訂單
                String orderId = orderQueue.take();
                System.out.println("延遲隊列報告:訂單 " + orderId + " 已過期");
                // 處理訂單取消邏輯...
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

注意事項:防重復處理(冪等性)

// 在OrderService中添加防重復處理
@Transactional
public void cancelExpiredOrder(String orderId) {
    // 使用Redis分布式鎖,防止多個實例同時處理同一個訂單
    String lockKey = "order:cancel:lock:" + orderId;
    Boolean locked = redisTemplate.opsForValue()
        .setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
    if (Boolean.TRUE.equals(locked)) {
        try {
            // 再次檢查訂單狀態(tài)(雙重校驗)
            Order order = orderRepository.findById(orderId);
            if (order != null && order.getStatus() == 0) {
                // 更新訂單狀態(tài)
                order.setStatus(2);
                orderRepository.save(order);
                System.out.println("訂單 " + orderId + " 已取消");
            }
        } finally {
            // 釋放鎖
            redisTemplate.delete(lockKey);
        }
    }
}

總結

  1. 性能爆表:Redis基于內存操作,處理速度堪比閃電俠跑步
  2. 精準定時:Redis的過期機制精準可靠,誤差極小
  3. 解耦神器:業(yè)務邏輯和定時任務分離,代碼清爽不油膩
  4. 擴展性強:輕松應對高并發(fā),加個Redis集群就能撐起雙11
  5. 資源友好:不需要額外的定時任務中間件,省心省力

但也要注意這些“坑”

  1. Redis持久化:記得配置RDB/AOF,不然閃電俠“失憶”就麻煩了
  2. 網絡波動:Redis掛了怎么辦?要有降級方案
  3. 事件丟失:Redis的過期事件可能丟失,重要業(yè)務要有補償機制
  4. 時鐘同步:多服務器時間要同步,別自己人跟自己人“打架”

最后

想象一下:

  • 沒有Redis時:你的數(shù)據庫被定時任務掃得氣喘吁吁,每次都要問:“哪些訂單超時了?”
  • 有了Redis后:Redis主動報告:“嘿!這幾個訂單超時了,快處理!”

這就好比從“挨家挨戶查水表”變成了“水表自己打電話報警”,效率提升不是一點點!好的架構,就是讓合適的工具做合適的事。Redis就是這個場景下的“時間管理大師”!

到此這篇關于別再用定時任務掃庫了!SpringBoot集成Redis實現(xiàn)訂單超時管理的文章就介紹到這了,更多相關SpringBoot集成Redis內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java static 與 final關鍵字實例詳解

    Java static 與 final關鍵字實例詳解

    本文詳細介紹了Java中的static和final關鍵字,包括它們的本質、內存分配、線程安全問題以及在類加載過程中的內存變化,通過舉例和解釋,感興趣的朋友跟隨小編一起看看吧
    2026-01-01
  • 一文詳解Java屬性為什么不能是is開頭的boolean

    一文詳解Java屬性為什么不能是is開頭的boolean

    在Java實體類定義中,boolean類型的屬性命名常引發(fā)爭議,阿里巴巴Java開發(fā)手冊建議避免使用is作為布爾類型屬性的前綴,原因在于當實體類被序列化或反序列化時,基于JavaBean規(guī)范的框架可能會移除或忽略is,導致不一致的字段名,文中介紹的非常詳細,需要的朋友可以參考下
    2024-10-10
  • SpringMVC實現(xiàn)賬號只能在一處登陸

    SpringMVC實現(xiàn)賬號只能在一處登陸

    這篇文章主要為大家詳細介紹了SpringMVC如何實現(xiàn)賬號只能在一處登陸,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • Java多線程中synchronized的工作原理

    Java多線程中synchronized的工作原理

    這篇文章主要介紹了Java多線程中synchronized的工作原理,本期講解 synchronized 工作的原理以及常見的鎖優(yōu)化機制,相信大家在看完這篇博文后對 synchronized 工作流程有一定的理解,需要的朋友可以參考下
    2023-07-07
  • Spring中七種事務傳播機制詳解

    Spring中七種事務傳播機制詳解

    這篇文章主要介紹了Spring中七種事務傳播機制詳解,Spring在TransactionDefinition接口中規(guī)定了7種類型的事務傳播行為,Propagation枚舉則引用了這些類型,開發(fā)過程中我們一般直接用Propagation枚舉,需要的朋友可以參考下
    2024-01-01
  • Java發(fā)送郵件遇到的常見需求匯總

    Java發(fā)送郵件遇到的常見需求匯總

    這篇文章主要介紹了Java發(fā)送郵件遇到的常見需求匯總的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-06-06
  • Java基于Spire.XLS實現(xiàn)從Excel到PDF的發(fā)票自動化生成

    Java基于Spire.XLS實現(xiàn)從Excel到PDF的發(fā)票自動化生成

    隨著業(yè)務數(shù)字化進程的不斷推進,許多企業(yè)開始嘗試用自動化手段替代手工開票,本文將基于 Spire.XLS for Java 介紹如何快速實現(xiàn)從 Excel 到 PDF的發(fā)票生成自動化方法,感興趣的小伙伴可以了解下
    2025-06-06
  • Java8中日期時間API的避坑指南與最佳實踐指南

    Java8中日期時間API的避坑指南與最佳實踐指南

    在Java 8之前,處理日期時間我們常用Date、Calendar和SimpleDateFormat,本文將通過實際案例,剖析老API的典型陷阱,并展示如何使用新API優(yōu)雅避坑,希望對大家有所幫助
    2026-04-04
  • Spring Bean配置方式總結

    Spring Bean配置方式總結

    定義Spring Bcan的3種方式分別是:基于XML 的方式配置、基于注解掃播方式配置、基于元數(shù)據類的配置,本文就通過代碼示例給大家詳細講講這三種配置方式,需要的朋友可以參考下
    2023-12-12
  • MyBatis框架關聯(lián)映射實例詳解

    MyBatis框架關聯(lián)映射實例詳解

    這篇文章主要介紹了MyBatis框架關聯(lián)映射,關系映射主要處理復雜的SQl查詢,如子查詢,多表聯(lián)查等復雜查詢,應用此種需求時可以考慮使用,需要的朋友可以參考下
    2022-11-11

最新評論

海兴县| 富蕴县| 西充县| 夏邑县| 历史| 青神县| 桦甸市| 岳普湖县| 新密市| 台北市| 长汀县| 靖远县| 滕州市| 织金县| 图木舒克市| 重庆市| 肇源县| 宁化县| 罗山县| 焦作市| 广东省| 琼中| 前郭尔| 新河县| 巴里| 遂溪县| 慈利县| 柳州市| 宁远县| 湖口县| 北票市| 沙雅县| 隆子县| 大方县| 南漳县| 灵璧县| 襄汾县| 司法| 大渡口区| 呼伦贝尔市| 门源|