欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot基于token防止訂單重復創(chuàng)建

 更新時間:2025年06月06日 08:30:41   作者:是一只多多  
本文主要介紹了SpringBoot基于token防止訂單重復創(chuàng)建,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

業(yè)務場景是這樣的

我們在秒殺場景中通常是瘋狂點擊下單

但是最后是只會創(chuàng)建一個訂單

點擊一個下單按鈕是發(fā)一次請求

如何保證一個用戶一次點擊只創(chuàng)建一個訂單呢

首先在此之前 我們需要對用戶的權限進行校驗

我們這邊使用的token實現(xiàn)

簽發(fā)token

每次進入下單界面 會簽發(fā)一個token給瀏覽器 順便寫入redis

然后在下單的時候 客戶端只要帶這個token過來

然后順便服務端校驗就行

這個token使是我們自己簽發(fā)的

我們自己實現(xiàn)的一個發(fā)放和存儲

package cn.hollis.nft.turbo.auth.controller;

import cn.dev33.satoken.stp.StpUtil;
import cn.hollis.nft.turbo.auth.exception.AuthErrorCode;
import cn.hollis.nft.turbo.auth.exception.AuthException;
import cn.hollis.nft.turbo.web.vo.Result;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static cn.hollis.nft.turbo.cache.constant.CacheConstant.CACHE_KEY_SEPARATOR;

/**
 * TokenController 類負責處理與 token 相關的請求,
 * 主要功能是在用戶登錄狀態(tài)下生成并發(fā)放一個基于 UUID 的 token,
 * 并將其存儲到 Redis 中。
 *
 * @author hollis
 */
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("token")
public class TokenController {

    /**
     * 定義 token 鍵的前綴,用于在 Redis 中存儲 token 時標識鍵。
     */
    private static final String TOKEN_PREFIX = "token:";

    /**
     * 注入 Spring Data Redis 提供的 StringRedisTemplate,
     * 用于操作 Redis 中的字符串類型數據。
     */
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 該接口用于在用戶登錄狀態(tài)下,根據傳入的場景信息生成一個唯一的 token,
     * 并將其存儲到 Redis 中,設置 30 分鐘的過期時間。
     *
     * @param scene 生成 token 的場景信息,不能為空。
     * @return 封裝了生成的 token 鍵的統(tǒng)一響應對象 Result。
     * @throws AuthException 若用戶未登錄,拋出用戶未登錄的認證異常。
     */
    @GetMapping("/get")
    public Result<String> get(@NotBlank String scene) {
        // 檢查用戶是否已登錄
        if (StpUtil.isLogin()) {
            // 生成一個基于 UUID 的唯一 token
            String token = UUID.randomUUID().toString();
            // 拼接用于存儲到 Redis 的 token 鍵
            String tokenKey = TOKEN_PREFIX + scene + CACHE_KEY_SEPARATOR + token;
            // 將 token 存儲到 Redis 中,設置 30 分鐘的過期時間
            stringRedisTemplate.opsForValue().set(tokenKey, token, 30, TimeUnit.MINUTES);
            // 返回包含 token 鍵的成功響應
            return Result.success(tokenKey);
        }
        // 若用戶未登錄,拋出用戶未登錄的認證異常
        throw new AuthException(AuthErrorCode.USER_NOT_LOGIN);
    }
}

我們將這個token返回個前端

調用下單接口是把這個token帶來

然后去redis里看一下是不是有效就行

有效放過去

無效的話就返回

執(zhí)行其他校驗鏈

首先基于 Filter 寫過濾器

在請求過來后首先到達的是過濾器 然后才是servlet

Filter 是一個servlet組件

package jakarta.servlet;

import java.io.IOException;

public interface Filter {
    // 過濾器初始化方法,在過濾器實例創(chuàng)建后調用,用于初始化資源
    public default void init(FilterConfig filterConfig) throws ServletException {}

    // 過濾方法,每次請求經過該過濾器時都會調用,用于實現(xiàn)具體的過濾邏輯
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;

    // 過濾器銷毀方法,在過濾器實例銷毀前調用,用于釋放資源
    public default void destroy() {}
}

主要有三個方法

初始化過濾器

過濾方法

過濾器銷毀

我們接下來看這個方法

具體過濾邏輯 重點

package cn.hollis.nft.turbo.web.filter;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.BooleanUtils;
import org.redisson.api.RScript;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Arrays;
import java.util.UUID;

/**
 * @author Hollis
 */
public class TokenFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(TokenFilter.class);

    public static final ThreadLocal<String> tokenThreadLocal = new ThreadLocal<>();

    public static final ThreadLocal<Boolean> stressThreadLocal = new ThreadLocal<>();

    private RedissonClient redissonClient;

    // 選擇構造器注入bean的方式 是spring官方推薦的注入方式
    public TokenFilter(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 過濾器初始化,可選實現(xiàn)
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            // 從請求頭中獲取Token
            String token = httpRequest.getHeader("Authorization");
            // 原來的邏輯是從redis里獲取并且驗證token
            // 如果是壓測環(huán)境 那么直接生成一個UUID作為Token
            Boolean isStress = BooleanUtils.toBoolean(httpRequest.getHeader("isStress"));

            if (token == null || "null".equals(token) || "undefined".equals(token)) {
                httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                httpResponse.getWriter().write("No Token Found ...");
                logger.error("no token found in header , pls check!");
                return;
            }
            // 校驗Token的有效性
            boolean isValid = checkTokenValidity(token, isStress);
            // Token無效
            if (!isValid) {
                httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                httpResponse.getWriter().write("Invalid or expired token");
                logger.error("token validate failed , pls check!");
                return;
            }
            // Token有效,繼續(xù)執(zhí)行其他過濾器鏈
            chain.doFilter(request, response);
        } finally {
            // ThreadLocal 可能會存在內存泄漏的問題
            tokenThreadLocal.remove();
            stressThreadLocal.remove();
        }
    }

    private boolean checkTokenValidity(String token, Boolean isStress) {
        // 獲取指定鍵的值
        // 刪除這個鍵
        // 返回獲取到的數值
        String luaScript = """
                local value = redis.call('GET', KEYS[1])
                redis.call('DEL', KEYS[1])
                return value""";
        // 6.2.3以上可以直接使用GETDEL命令
        // String value = (String) redisTemplate.opsForValue().getAndDelete(token);
        String result = (String) redissonClient.getScript().eval(RScript.Mode.READ_WRITE,
                luaScript,
                RScript.ReturnType.STATUS,
                Arrays.asList(token));
        if (isStress) {
            //如果是壓測,則生成一個隨機數,模擬 token
            result = UUID.randomUUID().toString();
            stressThreadLocal.set(isStress);
        }
        tokenThreadLocal.set(result);
        return result != null;

    }

    @Override
    public void destroy() {
        // 過濾器銷毀,可選實現(xiàn)
    }
}

注入的是 redissonClient 即redis的客戶端

同樣我們要指定log 用于打印日志

還維護一個ThreadLocal 首先是保證線程安全 其次是在組裝訂單字段的時候 把token放進去做一個冪等校驗

請求進來后就到了這邊

首先 通過 mvc提供的 httpRequest從請求頭里面取出token
取出來后我們進行校驗

調用checkTokenValidity方法進行校驗

用LUA腳本去redis里拿這個token 移除 保證原子性

如果成功了 最后放到ThreadLocal后 繼續(xù)執(zhí)行其他校驗鏈

疑問 為什么要基于Filter寫過濾器

使用過濾器能將 Token 校驗邏輯集中管理,避免在每個需要校驗 Token 的業(yè)務方法里重復編寫校驗代碼。例如,若有多個接口都需要進行 Token 校驗,只需配置過濾器攔截這些接口,就能統(tǒng)一進行校驗,而不用在每個接口方法中重復寫校驗邏輯。

接著是在 Spring MVC 處入口配置

只有先配置了 過濾器才能生效

我們是在這里添加token的校驗 URL配置等

可以理解成注冊 filter 過濾器

"注冊" 在這段代碼中有兩層含義:

一. 是把對象注冊到 Spring 容器進行管理;

二. 是將 Servlet 過濾器注冊到 Servlet 容器,使其能在請求處理流程中發(fā)揮作用。

通過 FilterRegistrationBean,將 TokenFilter 過濾器注冊到 Servlet 容器中,Servlet 容器會在處理請求時,按照配置的規(guī)則調用 TokenFilter 進行過濾操作。

package cn.hollis.nft.turbo.web.configuration;

import cn.hollis.nft.turbo.web.filter.TokenFilter;
import cn.hollis.nft.turbo.web.handler.GlobalWebExceptionHandler;
import org.redisson.api.RedissonClient;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author Hollis
 * 這是所有mvc入口的一個配置 實現(xiàn)了 WebMvcConfigurer接口
 * AutoConfiguration注解 標記此類為自動配置類
 * ConditionalOnWebApplication 條件注釋 表示在web環(huán)境下 配置類生效
 * 注冊了一系列過濾器
 */
@AutoConfiguration
@ConditionalOnWebApplication
public class WebConfiguration implements WebMvcConfigurer {

    @Bean
    @ConditionalOnMissingBean
    GlobalWebExceptionHandler globalWebExceptionHandler() {
        return new GlobalWebExceptionHandler();
    }

    /**
     * 注冊token過濾器
     *
     * @param redissonClient
     * @return
     */
    @Bean
    public FilterRegistrationBean<TokenFilter> tokenFilter(RedissonClient redissonClient) {
        FilterRegistrationBean<TokenFilter> registrationBean = new FilterRegistrationBean<>();
        // 設置要注冊的過濾器為 TokenFilter 實例,并傳入 RedissonClient 對象。
        registrationBean.setFilter(new TokenFilter(redissonClient));
        // 設置過濾器需要攔截的 URL 路徑,只有請求路徑匹配這些模式時,TokenFilter 才會處理該請求。
        registrationBean.addUrlPatterns("/trade/buy","/trade/newBuy","/trade/normalBuy");
        // 設置過濾器的執(zhí)行順序,數字越小,執(zhí)行優(yōu)先級越高。
        registrationBean.setOrder(10);

        return registrationBean;
    }

}

到此這篇關于SpringBoot基于token防止訂單重復創(chuàng)建的文章就介紹到這了,更多相關SpringBoot token防止訂單重復內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家! 

相關文章

  • java時間格式的簡單整理

    java時間格式的簡單整理

    這篇文章主要介紹了java時間格式的簡單整理,文中通過示例介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考一下
    2019-06-06
  • Mybatis使用typeHandler加密的實現(xiàn)

    Mybatis使用typeHandler加密的實現(xiàn)

    本文詳細介紹了如何在Mybatis中使用typeHandler對特定字段進行加密處理,涵蓋了從引入依賴、配置Mybatis,到實現(xiàn)typeHandler繼承類和配置mapper層的詳細步驟,為需要在項目中實現(xiàn)字段加密的開發(fā)者提供了參考和借鑒
    2024-09-09
  • Java?17新特性詳細講解與代碼實例

    Java?17新特性詳細講解與代碼實例

    這篇文章主要給大家介紹了關于Java?17新特性詳細講解與代碼實例的相關資料,Java 17是2021年9月發(fā)布的最新版本,其中包含了很多新特性和改進,這些新特性和改進將進一步提高 Java 語言的性能和可用性,需要的朋友可以參考下
    2023-09-09
  • Java?超詳細講解SpringMVC攔截器

    Java?超詳細講解SpringMVC攔截器

    Spring?MVC?的攔截器(Interceptor)與?Java?Servlet?的過濾器(Filter)類似,它主要用于攔截用戶的請求并做相應的處理,通常應用在權限驗證、記錄請求信息的日志、判斷用戶是否登錄等功能上。本文將代碼演示和文字描述詳解攔截器的使用
    2022-04-04
  • MyBatis版本升級導致OffsetDateTime入參解析異常問題復盤

    MyBatis版本升級導致OffsetDateTime入參解析異常問題復盤

    這篇文章主要介紹了MyBatis版本升級導致OffsetDateTime入參解析異常問題復盤,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-08-08
  • IDEA中將SpringBoot項目提交到git倉庫的方法步驟

    IDEA中將SpringBoot項目提交到git倉庫的方法步驟

    本文主要介紹了IDEA中將SpringBoot項目提交到git倉庫的方法步驟,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • Spring事務管理配置文件問題排查

    Spring事務管理配置文件問題排查

    這篇文章主要介紹了Spring事務管理配置文件問題排查,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-05-05
  • SpringBoot項目配置postgresql數據庫完整步驟(配置多數據源)

    SpringBoot項目配置postgresql數據庫完整步驟(配置多數據源)

    PostgreSQL是一種特性非常齊全的自由軟件的對象-關系型數據庫管理系統(tǒng)(ORDBMS),下面這篇文章主要給大家介紹了關于SpringBoot項目配置postgresql數據庫(配置多數據源)的相關資料,需要的朋友可以參考下
    2023-05-05
  • java中Servlet監(jiān)聽器的工作原理及示例詳解

    java中Servlet監(jiān)聽器的工作原理及示例詳解

    這篇文章主要介紹了java中Servlet監(jiān)聽器的工作原理及示例詳解。Servlet監(jiān)聽器用于監(jiān)聽一些重要事件的發(fā)生,監(jiān)聽器對象可以在事情發(fā)生前、發(fā)生后可以做一些必要的處理。感興趣的可以來了解一下
    2020-07-07
  • SpringBoot WebService服務端&客戶端使用案例教程

    SpringBoot WebService服務端&客戶端使用案例教程

    這篇文章主要介紹了SpringBoot WebService服務端&客戶端使用案例教程,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2023-10-10

最新評論