通过spring boot/redis/aspect 防止表单重复提交【防抖】

一、啥是防抖
 

所谓防抖,一是防用户手抖,二是防网络抖动。在Web系统中,表单提交是一个非常常见的功能,如果不加控制,容易因为用户的误操作或网络延迟导致同一请求被发送多次,进而生成重复的数据记录。要针对用户的误操作,前端通常会实现按钮的loading状态,阻止用户进行多次点击。而对于网络波动造成的请求重发问题,仅靠前端是不行的。为此,后端也应实施相应的防抖逻辑,确保在网络波动的情况下不会接收并处理同一请求多次。

一个理想的防抖组件或机制,我觉得应该具备以下特点:

逻辑正确,也就是不能误判;

响应迅速,不能太慢;

易于集成,逻辑与业务解耦;

良好的用户反馈机制,比如提示“您点击的太快了”

二、思路解析
前面讲了那么多,我们已经知道接口的防抖是很有必要的了,但是在开发之前,我们需要捋清楚几个问题。

2.1.哪一类接口需要防抖?
接口防抖也不是每个接口都需要加,一般需要加防抖的接口有这几类:

用户输入类接口:比如搜索框输入、表单输入等,用户输入往往会频繁触发接口请求,但是每次触发并不一定需要立即发送请求,可以等待用户完成输入一段时间后再发送请求。

按钮点击类接口:比如提交表单、保存设置等,用户可能会频繁点击按钮,但是每次点击并不一定需要立即发送请求,可以等待用户停止点击一段时间后再发送请求。

滚动加载类接口:比如下拉刷新、上拉加载更多等,用户可能在滚动过程中频繁触发接口请求,但是每次触发并不一定需要立即发送请求,可以等待用户停止滚动一段时间后再发送请求。

2.2.如何确定接口是重复的?
防抖也即防重复提交,那么如何确定两次接口就是重复的呢?首先,我们需要给这两次接口的调用加一个时间间隔,大于这个时间间隔的一定不是重复提交;其次,两次请求提交的参数比对,不一定要全部参数,选择标识性强的参数即可;最后,如果想做的更好一点,还可以加一个请求地址的对比。

  • 定义一个RequestLock,配置超时时间、异常消息、分组标识(用户标识)
/*** 请求锁,防止重复提交** @author xt*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLock {/*** 过期时间** @return*/long expire() default 3;/*** 异常提示** @return*/String message() default "您的操作太快了,请稍后重试";/*** 参数分隔符** @return*/String delimiter() default "|";/*** 时间单位** @return*/TimeUnit timeUnit() default TimeUnit.SECONDS;/*** 前缀(从请求header key)** @return*/String group() default "loginuserid";
}

  • 定义一个aspect 实现对注解RequestLock的endpoint进行拦截
@EnableAspectJAutoProxy
@Aspect
@Configuration
@Order
public class RequestLockAspect {@Resourceprivate RedisTemplate redisTemplate;@Pointcut("execution(public * * (..)) && @annotation(org.xt.shisui.redis.duplicate.RequestLock)")public void endpointPointcut() {}@Around("endpointPointcut()")public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();Method method = methodSignature.getMethod();RequestLock requestLock = method.getAnnotation(RequestLock.class);if (redisTemplate != null) {String key = RequestLockKeyGenerator.getLockKey(joinPoint);Boolean success = redisTemplate.opsForValue().setIfAbsent(key, new byte[0], requestLock.expire(), requestLock.timeUnit());if (Boolean.FALSE.equals(success)) {return Response.no(requestLock.message());}}return joinPoint.proceed();}
}
  • 根据请求参数构建RequestLock锁的key,即Redis存储的key

/*** 根据请求参数构建锁的key** @author xt* @date 2022-07-15 14:21*/
public class RequestLockKeyGenerator {public static String getLockKey(ProceedingJoinPoint joinPoint) {String ipAddress = null, group = null;ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();//方法名称String methodName = signature.getName();//类路径String declaringTypeName = signature.getDeclaringTypeName();RequestLock requestLock = method.getAnnotation(RequestLock.class);if (attributes != null) {//加上请求中的ip和分组标识,防止错误拦截HttpServletRequest request = attributes.getRequest();ipAddress = request.getRemoteAddr();group = request.getHeader(requestLock.group());}final Object[] args = joinPoint.getArgs();final Parameter[] parameters = method.getParameters();StringBuilder params = new StringBuilder();String delimiter = requestLock.delimiter();for (int i = 0; i < parameters.length; i++) {//忽略特殊参数,如图片、大文本等,如果是存hashcode 可以不需要这个注解final RequestLockKeyIgnore keyIgnore = parameters[i].getAnnotation(RequestLockKeyIgnore.class);if (keyIgnore != null) {continue;}Object arg = args[i];if (arg != null) {params.append(delimiter).append(arg);}}StringBuilder result = new StringBuilder();result.append(declaringTypeName).append(delimiter).append(methodName).append(delimiter).append(ipAddress).append(delimiter).append(delimiter).append(group).append(params.hashCode());return result.toString();}
}

  • 如果Redis存储请求参数字符串,可以增加特殊参数忽略注解,如图片等属性,建议用hashcode
/*** 忽略该参数,防止一些base64字符串被当做主键** @author xt* @date 2022-01-05 14:37*/
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RequestLockKeyIgnore {
}
  • 具体使用demo
    @RequestLock(expire = 5)@ApiOperation("新增")@RequestMapping(value = "/create", method = RequestMethod.POST)public Response<ChatSpeechcraftCategoryCreateResp> create(@RequestBody @Validated ChatSpeechcraftCategoryCreateReq req, final HttpServletRequest request) throws SimpleException {return chatSpeechcraftCategoryApiService.create(req);}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用pyecharts的overlap绘制叠加图时,设置的颜色不起作用

问题 用pyecharts绘制叠加图时&#xff0c;如折线图上叠加散点图时&#xff0c;分别设置了自己的颜色&#xff08;三角是绿色&#xff0c;圆形是蓝色&#xff09;&#xff0c;但是渲染颜色和图例颜色不一致&#xff0c;如下图所示&#xff0c;折线颜色和散点颜色相同。 解决…

Leet code 904 水果成篮

解题思路&#xff1a;滑动窗口 创建hash表记录水果的种类和数量 两个指针left 和 right 从数组起始位置开始滑动&#xff0c;right在循环内 hash[fruits[right]] 进窗口 然后判断 hash表中的种类是否大于2 一旦大于2就要想办法出窗口 出窗口需要将hash表中种类重回2种 …

python做筛子用

import randomdef roll_dice():roll random.randint(1,10)return roll def main():total_times 100#初始化列表result_list[0]*10for i in range(total_times):print(roll_dice())for j in range(1,11):if roll_dice() j:result_list[j-1] 1if __name__ __main__:main() 结…

基于Java (spring-boot)的个人博客系统

一、项目介绍 登录模块&#xff1a;用户登录、用户注册 普通用户&#xff1a;博客主页、博客随笔、博客留言、个人信息修改 管理员&#xff1a;系统管理、内容管理、文章管理、随笔管理、分类管理、标签管理、留言管理、评论管理、文件管理 博客编辑&#xff1a;支持富文本编辑…

Autosar教程-Mcal教程-Fls配置教程

3.11.1 FLS基础知识 flash操作中有两个术语:block和page。block是flash最小的擦除单位,page则是flash写入的最小单位。以我们使用的F1KM-S4(R7F7016533)来说,它的是64 bytes, page是4bytes。这也就意味着,如果要擦除的话,最小要擦除64 bytes,但是写入可以按4字节的大小写入…

程序员的知识宝库,100+开源书籍、文档

公众号&#xff1a;【可乐前端】&#xff0c;每天3分钟学习一个优秀的开源项目&#xff0c;分享web面试与实战知识&#xff0c;也有全栈交流学习摸鱼群&#xff0c;期待您的关注! 每天3分钟开源 hi&#xff0c;这里是每天3分钟开源&#xff0c;很高兴又跟大家见面了&#xff0…

每日五道java面试题之springMVC篇(四)

目录&#xff1a; 第一题. Spring MVC怎么样设定重定向和转发的&#xff1f;第二题.Spring MVC怎么和AJAX相互调用的&#xff1f;第三题. 如何解决POST请求中文乱码问题&#xff0c;GET的又如何处理呢&#xff1f;第四题. Spring MVC的异常处理&#xff1f;第五题. 如果在拦截请…

iOS面试题锦集

1. 问&#xff1a;一张图片所占内存大小跟什么有关&#xff1f; 图片所占内存大小&#xff0c;与图片的宽高有关 我们平时看到的png、jpg、webp这些图片格式&#xff0c;其实都是图片压缩格式。通过对应的算法来优化了大小以节省网络传输与本地保存所需的资源。 但是当我们加…

操作系统(一)

操作系统 一、操作系统的四个特性 并发性&#xff1a;并发性是指两个或多个时间在同一时间间隔发生&#xff0c;并发和并行是两个相似的概念&#xff0c;并行是指两个或多个事件在同一时刻发生。进程在宏观上并行&#xff0c;微观上串行 2.共享性&#xff1a;** 由于操作系统…

04-java基础--流程控制语句

一、switch语句 二、循环的三种结构 流程控制语句分为三种结构&#xff1a; 顺序结构&#xff08;按代码的书写顺序执行&#xff0c;从上到下依次执行&#xff09;分支结构&#xff08;if语句、if–else语句、switch语句&#xff09;循环结构&#xff08;while、for循环、do–…

H5 响应式精品网站推荐导航源码

源码名称&#xff1a;响应式精品网站推荐导航源码 源码介绍&#xff1a;一款响应式精品网站推荐导航源码&#xff0c;可以自己修改代码替换图标图片和指向网址。背景图支持自动替换&#xff0c;背景图可以在img.php中修改 需求环境&#xff1a;H5 下载地址&#xff1a; http…

python_Anaconda虚拟环境导出以及重现

文章目录 1. 场景2. 解决方案2.1 方案一&#xff1a;直接将打包&#xff0c;然后将包传输到另外一台服务器2.2 方案二&#xff1a;导出环境所有的包名及版本&#xff0c;然后重新安装 1. 场景 我们有时候需要把一个虚拟环境迁移到别的服务器上面去&#xff0c;这时候&#xff…

JavaScript基础(超详细)

目录 1.JavaScript概述 2.JavaScript的组成及其基本结构 1.JavaScript的组成 1.ECMAScript ECMAScript是一种由Ecma国际[前向为欧洲计算机制造商协会(European Computer Manufacturers Associaiton)]通过ECMA-262标准化的脚本程序设计语言。其主要描述了JavaScript的语法…

使用阿里云服务器查看网站备案的方法和注意事项

随着互联网的发展&#xff0c;网站注册已成为在中国建设网站的必要步骤。 在使用阿里云服务器时&#xff0c;我们可以通过以下步骤查看网站注册状态。 备案概述&#xff1a; 在中国&#xff0c;互联网信息服务提供者必须进行登记&#xff0c;以监管互联网内容、规范市场运营和…

P1149 [NOIP2008 提高组] 火柴棒等式

题目描述 给你 &#xfffd;n 根火柴棍&#xff0c;你可以拼出多少个形如 &#xfffd;&#xfffd;&#xfffd;ABC 的等式&#xff1f;等式中的 &#xfffd;A、&#xfffd;B、&#xfffd;C 是用火柴棍拼出的整数&#xff08;若该数非零&#xff0c;则最高位不能是 00&…

C语言:字符串逆序输出, test ok

【问题描述】字符串逆序&#xff1a;设计函数功能是将一个字符串逆序&#xff0c;函数声明&#xff1a;void stringNx(char a[ ])&#xff0c;使用这个函数完成将输入的字符串逆序输出。 【输入形式】要求输入一个字符串 【输出形式】逆序后输出 【样例输入】abcd 【样例输…

美国将采取新政策降低对中国大陆的依赖 | 百能云芯

美国财政部长叶伦13日表示&#xff0c;美国正采取一系列行动来确保国内电动汽车产业的成功&#xff0c;并降低对中国大陆的依赖&#xff0c;这一做法虽然在一定程度上可以理解&#xff0c;但是也存在诸多值得深思的问题。 首先&#xff0c;我们必须承认中国在全球电动车市场的地…

SpringBoot项目根据配置文件初始化并向容器注册Bean

SpringBoot项目根据配置文件初始化并向容器注册Bean 文章目录 SpringBoot项目根据配置文件初始化并向容器注册Bean[TOC] 前言一、场景图示二、实现1.定义一个Condition实现类2.按照配置装配bean 总结 前言 在开发过程种有这种场景&#xff0c;我们在使用数据存储的时候定义了一…

Hadoop大数据应用:Linux 部署 HDFS 分布式集群

目录 一、实验 1.环境 2.Linux 部署 HDFS 分布式集群 3.Linux 使用 HDFS 文件系统 二、问题 1.ssh-copy-id 报错 2. 如何禁用ssh key 检测 3.HDFS有哪些配置文件 4.hadoop查看版本报错 5.启动集群报错 6.hadoop 的启动和停止命令 7.上传文件报错 8.HDFS 使用命令 一…

米尔更新面向工业产品的软件系统-基于瑞米派(Remi Pi)

米尔电子发布的瑞萨第一款MPU生态板卡——瑞米派&#xff08;Remi Pi&#xff09;自上市当天200套售罄&#xff0c;获得不少新老用户的青睐。为感谢大家的支持&#xff0c;米尔加推300套瑞米派活动&#xff0c;以补贴价回馈大家&#xff0c;抢完即止&#xff01; 不仅如此&…