订单防重复提交:token 发放以及校验

订单防重复提交:token 发放以及校验

  • 1. 基于Token校验避免订单重复提交

1. 基于Token校验避免订单重复提交

在很多秒杀场景中,用户为了能下单成功,会频繁的点击下单按钮,这时候如果没有做好控制的话,就可能会给一个用户创建重复订单。

如何防止这个问题呢?

其实有一个好办法,那就是用户在下单的时候,带一个 token 过来,我们校验这个 token 的有效性,如果 token 有效,则允许下单,如果无效,则不允许用户下单。

注意注意注意:这里的 token 和 sa-token(鉴权token) 这个框架中的 token 不是一回事儿,也没有任何关系。
sa-token 里面的那个 token是用于登录鉴权的。
而这里的 token 是用来防止订单重复提交的,他俩不是一个 token,这里的 token 也不是 sa-token 发放的,而是我们自己实现的一个发放和存储,以及后续的校验,都是我们自己做的。

在这里插入图片描述

那么,这个 token 是如何发放和校验的的呢?
token 的发放比较简单,我们定义一个 controller,在下单页面渲染的时候从接口中获取一下就行了。

package cn.hollis.nft.turbo.web.filter;import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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;public class TokenFilter implements Filter {private static final Logger logger = LoggerFactory.getLogger(TokenFilter.class);public static final ThreadLocal<String> tokenThreadLocal = new ThreadLocal<>();private RedissonClient redissonClient;public TokenFilter(RedissonClient redissonClient) {this.redissonClient = redissonClient;}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// 过滤器初始化,可选实现}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {try {HttpServletRequest httpRequest = (HttpServletRequest) request;HttpServletResponse httpResponse = (HttpServletResponse) response;// 从请求头中获取TokenString token = httpRequest.getHeader("Authorization");logger.info("TokenFilter::doFilter,httpRequest:{}", httpRequest);logger.info("TokenFilter::doFilter,token:{}", token);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);if (!isValid) {httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);httpResponse.getWriter().write("Invalid or expired token");logger.error("token validate failed , pls check!");return;}// Token有效,继续执行其他过滤器链chain.doFilter(request, response);} finally {tokenThreadLocal.remove();}}/*** 检查Token的有效性* 通过Redis判断Token是否存在,并将其删除* 这个方法用于确保Token的单次使用,增强安全性** @param token 要检查的Token* @return 如果Token在Redis中存在,则返回true;否则返回false*/private boolean checkTokenValidity(String token) {// Lua脚本,用于获取并删除Redis中的Token// 这样做是为了保证Token的单次使用,增强安全性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);// 使用Redisson客户端执行Lua脚本,判断Token是否存在并将其删除// KEYS[1]表示脚本中的Token键// getScript这个方法用于获取一个脚本对象(通常用于处理 Redis 中的脚本相关操作)。// 在 Redis 中,可以使用 Lua 脚本进行复杂的操作,例如原子性地执行多个命令等。Redisson 通过这个方法提供了对脚本操作的支持。// eval 具体来说,它会将脚本发送到 Redis 服务器端执行,并且可以根据脚本的逻辑在// Redis 中进行数据操作(如读取、写入、修改数据等),脚本执行的结果会被返回给调用者(在 Java 中就是返回给执行eval()方法的地方)。// 例如,如果脚本是一个用于计算某个键对应的值的两倍的 Lua 脚本,eval()方法执行这个脚本后就会得到计算后的结果并返回。String result = (String) redissonClient.getScript().eval(RScript.Mode.READ_WRITE,luaScript,RScript.ReturnType.STATUS,Arrays.asList(token));// 将Redis中的返回值存储到ThreadLocal中,以便在当前线程内其他地方使用tokenThreadLocal.set(result);// 如果result不为空,说明Token在Redis中存在过,即Token有效return result != null;}@Overridepublic void destroy() {}
}

主要实现在doFilter方法中,主要是判断请求中是否携带了 token,如果携带了,通过 redis 校验 token 是否有效,如果有效,则把这个 token 删除,并且放过请求。如果无效,则直接拒绝请求。

这里的token 校验及移除,我们是通过 lua 脚本实现的,保证原子性。

有了这个 filter 之后,我们需要让他能够生效,则需要以下配置:

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;/*** Web配置类,用于配置全局异常处理器和Token过滤器** @AutoConfiguration注解* 来源与功能概述* 在 Spring Boot 框架中,@AutoConfiguration是一个非常重要的注解。它用于标识一个类是自动配置类。* Spring Boot 的自动配置机制会根据类路径中的依赖和预定义的配置条件自动加载和应用这些自动配置类。* 工作原理* 自动配置类通常包含一系列的@Bean方法,这些方法会向 Spring 容器中注入各种组件。* 例如,当项目的类路径中存在某些特定的库(如数据库驱动)时,相关的自动配置类就会被触发,* 它内部的@Bean方法会创建和配置与该库相关的组件,如数据源、事务管理器等,从而减少了开发者手动配置这些组件的工作量。** @ConditionalOnWebApplication注解* 条件注解类型* 这是 Spring Boot 中的一个条件注解。条件注解用于根据特定的条件来决定是否加载某个配置类或者某个@Bean方法。* 针对 Web 应用的条件判断* @ConditionalOnWebApplication用于判断当前应用是否是一个 Web 应用。它有不同的匹配模式:* 如果没有指定任何模式,只要是 Web 应用(无论是 Servlet - based 还是 Reactive - based)就会满足条件。* type = ConditionalOnWebApplication.Type.SERVLET:这种模式下,只有当应用是基于 Servlet 的 Web 应用时才会满足条件。例如,在传统的 Spring MVC 应用中,* 使用 Servlet 容器(如 Tomcat、Jetty 等)来处理 HTTP 请求,就属于这种情况。* type = ConditionalOnWebApplication.Type.REACTIVE:只有当应用是基于响应式(Reactive)的 Web 应用时才满足条件,如使用 Spring WebFlux 构建的应用,* 它采用响应式编程模型来处理 HTTP 请求。* 这些注解共同作用,使得 Spring Boot 能够根据应用的实际情况(如是否为 Web 应用等)智能地加载和配置相关的组件,提高了应用开发的效率和灵活性。** @author Hollis*/
@AutoConfiguration
@ConditionalOnWebApplication
public class WebConfiguration implements WebMvcConfigurer {/*** 配置全局Web异常处理处理器** @return GlobalWebExceptionHandler 全局异常处理器实例*/@Bean@ConditionalOnMissingBeanGlobalWebExceptionHandler globalWebExceptionHandler() {return new GlobalWebExceptionHandler();}/*** 注册Token过滤器** @param redissonClient Redisson客户端实例,用于分布式锁等Redis操作* @return FilterRegistrationBean<TokenFilter> 注册的Token过滤器实例*/@Beanpublic FilterRegistrationBean<TokenFilter> tokenFilter(RedissonClient redissonClient) {FilterRegistrationBean<TokenFilter> registrationBean = new FilterRegistrationBean<>();// 初始化TokenFilter实例并设置Redisson客户端registrationBean.setFilter(new TokenFilter(redissonClient));// 设置过滤器处理的URL模式registrationBean.addUrlPatterns("/trade/buy");// 设置过滤器顺序registrationBean.setOrder(10);return registrationBean;}}

这里,我们并不是给所有的页面都加这个 token 的校验,其实很多接口是不需要的,所以我们只需要通过registrationBean.addUrlPatterns(“/trade/buy”);设置上我们需要校验的路径就行了。

前端代码:

latestCollectionCreateOrder() {var that = this;this.$u.post('/trade/buy', {goodsId: that.collectionId,goodsType: 'COLLECTION',itemCount: 1,token: that.checkToken}, {Authorization: that.checkToken}).then(res => {if(res.success) {that.orderId = res.data;that.showPayModalFlag = true;}else{uni.showToast({icon: 'error',title: res.message,duration: 2000});}}).catch(error => {if (error.statusCode === 401) {uni.showModal({title: '请勿重复提交',content: ' 请刷新页面重新发起请求',showCancel: false,success: function (res) {if (res.confirm) {// Handle the case when user confirms the modal// For example, you can redirect to the login page}}});} else {// Handle other errorsuni.showToast({icon: 'error',title: 'An error occurred',duration: 2000});}});},

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1536678.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

春秋云境之CVE-2022-30887

一.靶场环境 1.下载靶场环境 根据题目提示&#xff0c;此靶场存在文件上传漏洞。 2.启动靶场环境 我们可以看到是一个登录页面&#xff0c;我们尝试进行登录 二.登录页面 1.尝试进行登录 我们发现用户名必须是邮箱&#xff0c;那么弱口令肯定不行&#xff0c;我们可以看到…

Qt集成Direct2D绘制,实现离屏渲染

没搜到关于Qt中使用Direct2D的方式&#xff0c;想了个办法&#xff0c;在此做个记录。 需要引入这两个库&#xff1a; 代码&#xff1a; #pragma once #include <QWidget> #include <QImage> #include <QPainter> #include <QMouseEvent>#include &q…

【23-24年】年度总结与迎新引荐

文章目录 相关连接前言1 忙碌的备研与本科毕设2 暑期阿里之旅3 团队荣誉与迎新引荐4 项目合作意向 相关连接 个人博客&#xff1a;issey的博客 - 愿无岁月可回首 前言 自从2023年4月更新了两篇关于NLP的文章后&#xff0c;我便消失了一年半的时间。如今&#xff0c;随着学业…

SpringBoot 图书管理系统

文章目录 一、删除图书二、批量删除三、强制登录3.1 不使用拦截器3.2 使用拦截器 四、更新图书 一、删除图书 并不使用delete语句&#xff1a; 原因&#xff1a;企业开发中&#xff0c;因为数据就意味着金钱&#xff0c;所以我们不会使用delete去删除&#xff08;delete删除是…

基于SpringBoot的人事管理系统【附源码】

基于SpringBoot的人事管理系统&#xff08;源码L文说明文档&#xff09; 目录 4 系统设计 4.1 系统概述 4.2系统功能结构设计 4.3数据库设计 4.3.1数据库E-R图设计 4.3.2 数据库表结构设计 5 系统实现 5.1管理员功能介绍 5.1.1管理员登…

2分钟解决联想电脑wifi功能消失 网络适配器错误代码56

分钟解决联想电脑wifi功能消失 网络适配器错误代码56 现象 原因 电脑装了虚拟机&#xff0c;导致网络适配器冲突。我的电脑是装了vm虚拟机&#xff0c;上次更新系统后wifi图标就消失了。 解决方案 1、先卸载虚拟机 2、键盘按winr&#xff0c;弹出运行窗口&#xff0c;输入“…

LLVM PASS-PWN-前置

文章目录 参考环境搭建基础知识![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/dced705dcbb045ceb8df2237c9b0fd71.png)LLVM IR实例1. **.ll 格式&#xff08;人类可读的文本格式&#xff09;**2. **.bc 格式&#xff08;二进制格式&#xff09;**3. **内存表示** …

『功能项目』伤害数字UI显示【53】

我们打开上一篇52眩晕图标显示的项目&#xff0c; 本章要做的事情是在Boss受到伤害时显示伤害数字 首先打开Boss01预制体空间在Canvas下创建一个Text文本 设置Text文本 重命名为DamageUI 设置为隐藏 编写脚本&#xff1a;PlayerCtrl.cs 运行项目 本章做了怪物受伤血量的显示UI…

C语言 ——— 写一个宏,将一个整数的二进制位的奇数位和偶数位交换

目录 题目要求 代码实现 题目要求 写一个宏&#xff0c;将一个整数的二进制位的奇数位和偶数位交换 举例说明&#xff1a; 输入&#xff1a;10 10 的二进制为 1010 &#xff0c;奇数位和偶数位交换后得 0101 &#xff0c;也就是 5 输出&#xff1a;5 代码实现 代码演示&…

RK3568驱动指南|第十六篇 SPI-第190章 配置模式下寄存器的配置

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

电流镜与恒流源

在两个晶体管完全对称的情况下&#xff0c;电源通过R1给两个晶体管提供相同的偏置电流&#xff0c; 这样他们流过集电极和发射极的电流就相同。 电流镜原视频链接&#xff1a; 【电流镜电路】https://www.bilibili.com/video/BV1b5411k7rh?vd_source3cc3c07b09206097d0d8b0ae…

Linux基础3-基础工具3(make,makefile,gdb详解)

上篇文章&#xff1a;Linux基础3-基础工具2&#xff08;vim详解&#xff0c;gcc详解&#xff09;-CSDN博客 本章重点&#xff1a; 1.自动化构建工具make,makefile 2.linux调试工具gdb 目录 一. 自动化构建工具make,makefile 1.1 make使用 1.2 使用make注意点 a. make和文件时…

Python数据分析案例60——扩展变量后的神经网络风速预测(tsfresh)

案例背景 时间序列的预测一直是经久不衰的实际应用和学术研究的对象&#xff0c;但是绝大多数的时间序列可能就没有太多的其他的变量&#xff0c;例如一个股票的股价&#xff0c;还有一个企业的用电量&#xff0c;人的血糖浓度等等&#xff0c;空气的质量&#xff0c;温度这些…

揭秘LLM计算数字的障碍的底层原理

LLM的 Tokenizer与数字切分 大语言模型在处理语言时&#xff0c;通常依赖Tokenization技术来将文本切分为可操作的单元。早期版本的Tokenizer对数字处理不够精确&#xff0c;常常将多个连续数字合并为一个Token。比如“13579”可能被切分为“13”、“57”和“9”。在这种情况…

【Linux修行路】网络套接字编程——UDP

目录 ⛳️推荐 前言 六、Udp Server 端代码 6.1 socket——创建套接字 6.2 bind——将套接字与一个 IP 和端口号进行绑定 6.3 recvfrom——从服务器的套接字里读取数据 6.4 sendto——向指定套接字中发送数据 6.5 绑定 ip 和端口号时的注意事项 6.5.1 云服务器禁止直接…

AIGC图片相关知识和实战经验(Flux.1,ComfyUI等等)

最近看了网上的一些新闻&#xff0c;flux.1火出圈了&#xff0c;因此自己也尝试跑了一下&#xff0c;作图的质量还是蛮高的&#xff0c;在这里做个知识总结回顾。 flux.1是什么&#xff1f; 根据介绍&#xff0c;flux.1是由stable diffusion 一作&#xff0c;Stability AI的核…

数据结构----栈和队列

&#xff08;一&#xff09;栈 1.栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First …

【数据结构】十大经典排序算法总结与分析

文章目录 前言1. 十大经典排序算法分类2. 相关概念3. 十大经典算法总结4. 补充内容4.1 比较排序和非比较排序的区别4.2 稳定的算法就真的稳定了吗&#xff1f;4.3 稳定的意义4.4 时间复杂度的补充4.5 空间复杂度补充 结语 前言 排序算法是《数据结构与算法》中最基本的算法之一…

PHP Swoole实现简易聊天室,附加小程序端连接websocket简易代码

目录 用到的工具&#xff1a; PHP Swoole拓展 | PHP Redis拓展 | Redis 7 一、安装上述必要工具&#xff08;下面是以宝塔面板中操作为例&#xff09; 给PHP安装Swoole和Redis拓展&#xff1a; 安装Redis软件 二、创建websocket服务器文件"wss_server.php" 具…

19 MDIO 接口读写以太网PHY寄存器

以太网概述 以太网&#xff08;Ethernet&#xff09;是应用最普遍的局域网技术。IEEE组织的 IEEE 802.3标准制定了以太网的技术标准&#xff0c;它规定了包括物理层的连线、电子信号和介质访问层协议的内容。以太网凭借其成本低、通信速率高、抗干扰性强等优点被广泛应用在网络…