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

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

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

業(yè)務(wù)場(chǎng)景是這樣的

我們?cè)诿霘?chǎng)景中通常是瘋狂點(diǎn)擊下單

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

點(diǎn)擊一個(gè)下單按鈕是發(fā)一次請(qǐng)求

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

首先在此之前 我們需要對(duì)用戶的權(quán)限進(jìn)行校驗(yàn)

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

簽發(fā)token

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

然后在下單的時(shí)候 客戶端只要帶這個(gè)token過來

然后順便服務(wù)端校驗(yàn)就行

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

我們自己實(shí)現(xiàn)的一個(gè)發(fā)放和存儲(chǔ)

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 類負(fù)責(zé)處理與 token 相關(guān)的請(qǐng)求,
 * 主要功能是在用戶登錄狀態(tài)下生成并發(fā)放一個(gè)基于 UUID 的 token,
 * 并將其存儲(chǔ)到 Redis 中。
 *
 * @author hollis
 */
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("token")
public class TokenController {

    /**
     * 定義 token 鍵的前綴,用于在 Redis 中存儲(chǔ) token 時(shí)標(biāo)識(shí)鍵。
     */
    private static final String TOKEN_PREFIX = "token:";

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

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

我們將這個(gè)token返回個(gè)前端

調(diào)用下單接口是把這個(gè)token帶來

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

有效放過去

無效的話就返回

執(zhí)行其他校驗(yàn)鏈

首先基于 Filter 寫過濾器

在請(qǐng)求過來后首先到達(dá)的是過濾器 然后才是servlet

Filter 是一個(gè)servlet組件

package jakarta.servlet;

import java.io.IOException;

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

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

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

主要有三個(gè)方法

初始化過濾器

過濾方法

過濾器銷毀

我們接下來看這個(gè)方法

具體過濾邏輯 重點(diǎn)

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;

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

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

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            // 從請(qǐng)求頭中獲取Token
            String token = httpRequest.getHeader("Authorization");
            // 原來的邏輯是從redis里獲取并且驗(yàn)證token
            // 如果是壓測(cè)環(huán)境 那么直接生成一個(gè)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;
            }
            // 校驗(yàn)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 可能會(huì)存在內(nèi)存泄漏的問題
            tokenThreadLocal.remove();
            stressThreadLocal.remove();
        }
    }

    private boolean checkTokenValidity(String token, Boolean isStress) {
        // 獲取指定鍵的值
        // 刪除這個(gè)鍵
        // 返回獲取到的數(shù)值
        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) {
            //如果是壓測(cè),則生成一個(gè)隨機(jī)數(shù),模擬 token
            result = UUID.randomUUID().toString();
            stressThreadLocal.set(isStress);
        }
        tokenThreadLocal.set(result);
        return result != null;

    }

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

注入的是 redissonClient 即redis的客戶端

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

還維護(hù)一個(gè)ThreadLocal 首先是保證線程安全 其次是在組裝訂單字段的時(shí)候 把token放進(jìn)去做一個(gè)冪等校驗(yàn)

請(qǐng)求進(jìn)來后就到了這邊

首先 通過 mvc提供的 httpRequest從請(qǐng)求頭里面取出token
取出來后我們進(jìn)行校驗(yàn)

調(diào)用checkTokenValidity方法進(jìn)行校驗(yàn)

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

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

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

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

接著是在 Spring MVC 處入口配置

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

我們是在這里添加token的校驗(yàn) URL配置等

可以理解成注冊(cè) filter 過濾器

"注冊(cè)" 在這段代碼中有兩層含義:

一. 是把對(duì)象注冊(cè)到 Spring 容器進(jìn)行管理;

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

通過 FilterRegistrationBean,將 TokenFilter 過濾器注冊(cè)到 Servlet 容器中,Servlet 容器會(huì)在處理請(qǐng)求時(shí),按照配置的規(guī)則調(diào)用 TokenFilter 進(jìn)行過濾操作。

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入口的一個(gè)配置 實(shí)現(xiàn)了 WebMvcConfigurer接口
 * AutoConfiguration注解 標(biāo)記此類為自動(dòng)配置類
 * ConditionalOnWebApplication 條件注釋 表示在web環(huán)境下 配置類生效
 * 注冊(cè)了一系列過濾器
 */
@AutoConfiguration
@ConditionalOnWebApplication
public class WebConfiguration implements WebMvcConfigurer {

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

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

        return registrationBean;
    }

}

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

相關(guān)文章

  • java時(shí)間格式的簡(jiǎn)單整理

    java時(shí)間格式的簡(jiǎn)單整理

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

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

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

    Java?17新特性詳細(xì)講解與代碼實(shí)例

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

    Java?超詳細(xì)講解SpringMVC攔截器

    Spring?MVC?的攔截器(Interceptor)與?Java?Servlet?的過濾器(Filter)類似,它主要用于攔截用戶的請(qǐng)求并做相應(yīng)的處理,通常應(yīng)用在權(quán)限驗(yàn)證、記錄請(qǐng)求信息的日志、判斷用戶是否登錄等功能上。本文將代碼演示和文字描述詳解攔截器的使用
    2022-04-04
  • MyBatis版本升級(jí)導(dǎo)致OffsetDateTime入?yún)⒔馕霎惓栴}復(fù)盤

    MyBatis版本升級(jí)導(dǎo)致OffsetDateTime入?yún)⒔馕霎惓栴}復(fù)盤

    這篇文章主要介紹了MyBatis版本升級(jí)導(dǎo)致OffsetDateTime入?yún)⒔馕霎惓栴}復(fù)盤,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • IDEA中將SpringBoot項(xiàng)目提交到git倉庫的方法步驟

    IDEA中將SpringBoot項(xiàng)目提交到git倉庫的方法步驟

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

    Spring事務(wù)管理配置文件問題排查

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

    SpringBoot項(xiàng)目配置postgresql數(shù)據(jù)庫完整步驟(配置多數(shù)據(jù)源)

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

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

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

    SpringBoot WebService服務(wù)端&客戶端使用案例教程

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

最新評(píng)論