在SpringBoot使用AOP防止接口重复提交

前言

防止接口重复提交有跟多种方法,可以在前端做处理。同样在后端也能处理,而且后端的处理也有很多中方法。最先能想到的就是加锁,也可以直接在该接口的实现过程中进行处理(可以参考防止数据重复提交的6种方法(超简单)!),本文主要介绍另一种借助AOP实现的方法。

AOP

关于AOP就不做过多赘述,可以参考我的另一篇文章Spring框架(下半部分 -AOP)。主要是借助它能增强方法的功能,对接口做以下处理,这个方法跟直接在接口种处理相似,话不多说,我们直接开始吧。

自定义注解

我们要灵活的使用AOP,注解是必不可少的,能帮我们更加便捷灵活的处理。我们先创建一个Submit注解,有该注解的接口就是我们要使用AOP处理的接口。

package com.blog.annotation;import java.lang.annotation.*;@Documented
@Retention(RetentionPolicy.RUNTIME) // 注解的存活时间
@Target(ElementType.METHOD) // 作用在方法上
public @interface Submit {/*** 提交的间隔时间* 默认是10s* @return*/long expire() default 10000;
}

AOP的实现

其实使用AOP都有一个很创建的模板,我先贴出来,然后解释。

@Aspect
@Component
@Slf4j
public class SubmitAspect {@Pointcut("@annotation(com.blog.annotation.Submit)")public void pt() {}@Around("pt()")public Object around(ProceedingJoinPoint point) {}
}

@Pointcut("@annotation(com.blog.annotation.Submit)")就是切入点表达式,它的参数就是指定我们要处理,@Around("pt()")表明我们使用环绕通知来处理。具体的在我刚刚提到的另一篇博客中,感兴趣的可以仔细的了解一下。

接下来我们就要考虑该如何实现,防止接口重复提交就是说如果该接口提交过了,再来一次提交我们就不让他去执行,直接返回。现在就有一个问题了,我们该如何知道这个接口提交没提交过?我们是不是可以把提交过的接口保存下来,如果来了一个提交我们就去查找,如果找到了我们就不如他提交。

if (接口 not in 接口集合) {return "请勿重复提交";
}
// 说明接口没有提交,我们就执行该接口的方法
// 最重要的一点是把该接口存储到接口集合中
...执行提交操作...
接口集合.insert(接口)

所以我们就需要考虑使用哪些集合?这个接口该怎么存储?怎么执行原方法的操作?什么时候用户还能再次提交代码?等等,这些都是我们要考虑的问题。

关于集合的使用,我们首先能想到的是list、set、map等等,但是考虑到并发安全,我们应该使用线程安全的集合例如ConcurrentHashMap、CopyOnWriteArrayList等等。我们还要解决什么时候用户还能再次提交代码,我们可以设置一个实现,所以更加推荐ConcurrentHashMap,其key值就是我们为每一个接口构建的key(使用类名+方法名),value就是我们设置的时间。

想到这还有一个问题,我们为每一个接口构建key,如果有多个用户那么他们的key就是一样的,可事实上每个用户的同一接口的key一定是不能一样的,否则他提交了我提交不了,这凭什么?所以我们再构建每一个接口的key时加上当前用户的唯一标识,使用该用户的id就行。

那么又该如何获取到当前用户的id呢? 在这里我们ThreadLocal就可以,ThreadLocal也是很重要的,如果不是很了解,建议花点时间去认识它。在这里我们只需要知道,他是独立于线程之外的,每一个线程又一个独自的ThreadLocal ,也就是说,我们把每一个用户都存储在ThreadLocal 中。要的时候直接get就行。

到这里其实核心的问题都已经解决了,剩下的就是一些细节问题,在自己写的时候就能注意到。这里给出我的实现。我使用的redis实现,因为它设置过期时间会自动清除,不需要我们手动去清除,再加上redis是天生支持高并发。
SUBMIT_KEY_PREFIX和NOT_SUBMIT_REPEATEDLY都是一个常量而已,不用过多注意。

package com.blog.aspect;import com.alibaba.fastjson.JSON;
import com.blog.annotation.Submit;
import com.blog.utils.JWTUtils;
import com.blog.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.time.Duration;import static com.blog.domain.vo.ErrorCode.NOT_SUBMIT_REPEATEDLY;
import static com.blog.utils.ConstantValue.SUBMIT_KEY_PREFIX;@Aspect
@Component
@Slf4j
public class SubmitAspect {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Pointcut("@annotation(com.blog.annotation.Submit)")public void pt() {}@Around("pt()")public Object around(ProceedingJoinPoint point) {try {
//            User user = UserThreadLocal.get();
//            String UserId = user.getId();// 假设这里是从ThreadLocal获取到的用户id。String UserId = "123456";Signature signature = point.getSignature();// 获取当前类名String className = point.getTarget().getClass().getSimpleName();// 获取当前方法名String methodName = signature.getName();// 拿到该方法Method method = ((MethodSignature) signature).getMethod();// 获取Submit注解Submit annotation = method.getAnnotation(Submit.class);// 获取过期时间long expire = annotation.expire();// 设置key值,每个用户对与每一个接口的key都是一样的String key = SUBMIT_KEY_PREFIX + DigestUtils.md5Hex(UserId) + "::" + className + "::" + methodName;// 首先查看是否已经提交过String value = redisTemplate.opsForValue().get(key);if (StringUtils.isNoneEmpty(value)) {return Result.error(NOT_SUBMIT_REPEATEDLY.getCode(), NOT_SUBMIT_REPEATEDLY.getMsg());}// 没有提交过就执行原方法Object proceed = point.proceed();redisTemplate.opsForValue().set(key, JSON.toJSONString(proceed), Duration.ofMillis(expire));return proceed;} catch (Throwable throwable) {throwable.printStackTrace();}return Result.error(-999, "系统异常");}
}

测试

接下来我们使用ApiPost进行测试,由于我们给定了id,所以我们只能测试单用户的,如果想测试多用户的,可以在请求路径中加上一个id,来模拟多用户。

间隔0ms,调用5次,只有一次成功,失败的几次,这里就不截图了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e2fHCLrs-1720583474961)(https://i-blog.csdnimg.cn/direct/e0b09889def54e4494172c9edc1571e1.png)]

间隔11000ms,调用2次,每次都成功,这是因为我们的冷静窗口是10000ms。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zG3MVA4q-1720583474962)(https://i-blog.csdnimg.cn/direct/a0153a775a524fd0821df825fa3154ff.png)]

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

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

相关文章

动手学Avalonia:基于硅基流动构建一个文生图应用(一)

文生图 文生图&#xff0c;全称“文字生成图像”&#xff08;Text-to-Image&#xff09;&#xff0c;是一种AI技术&#xff0c;能够根据给定的文本描述生成相应的图像。这种技术利用深度学习模型&#xff0c;如生成对抗网络&#xff08;GANs&#xff09;或变换器&#xff08;T…

【Mac】Charles for Mac(HTTP协议抓包工具)及同类型软件介绍

软件介绍 Charles for Mac 是一款功能强大的网络调试工具&#xff0c;主要用于HTTP代理/HTTP监视器。以下是它的一些主要特点和功能&#xff1a; 1.HTTP代理&#xff1a;Charles 可以作为HTTP代理服务器&#xff0c;允许你查看客户端和服务器之间的所有HTTP和SSL/TLS通信。 …

金航标kinghelm宋仕强在介绍自己公司时说

金航标kinghelm宋仕强在介绍自己公司时说&#xff0c;金航标成立于2007年&#xff0c;成立地点在华强北雷圳大厦803室&#xff0c;后搬到华强北广业大厦24楼CD室&#xff0c;后搬迁到龙华展滔科技大厦C座C809和C817室&#xff0c;现在的办公地址为龙岗区坂田街道百瑞达大厦&…

WSL安装USB驱动

wsl用不了USB盘&#xff0c;需要安装驱动 1、安装windows驱动 https://github.com/dorssel/usbipd-win/releases 下载msi&#xff0c;并且安装 2、linux里面安装 sudo apt install linux-tools-5.4.0-77-generic hwdata sudo update-alternatives --install /usr/local/bin/usb…

PageDTO<T>,PageQuery,BeanUtils,CollUtils的封装

一、PageDTO<T> import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fasterxml.jackson.annotation.JsonIgnore; import com.tianji.common.utils.BeanUtils; import com.tianji.common.utils.CollUtils; import com.tianji.common.utils.…

C#中的MD5摘要算法与哈希算法

文章目录 一、哈希算法基础二、MD5 算法原理三、MD5摘要算法四、哈希算法五、C#实现示例MD5算法示例哈希算法示例字符串MD5值对比 六、总结 一、哈希算法基础 哈希算法是一种单向密码体制&#xff0c;它将任意长度的数据转换成固定长度的字符串。这种转换是不可逆的&#xff0…

IDEA中配置代理,解决Codearts Snap登陆不了的问题

问题描述&#xff1a;在mac电脑中的idea中安装了华为的codearts snap插件&#xff0c;一直登录不了&#xff0c;账号是没问题的&#xff0c;后来我怀疑是我的代理有问题&#xff0c;找到IDEA中的代理设置先是有这个问题“You have JVM property "https.proxyHost" se…

千呼新零售2.0分销商城视频介绍

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物等连锁店使用。 详细介绍请…

C语言 将两个字符串连接起来,不用strcat函数

编一个程序,将两个字符串连接起来,不要用strcat函数。 #include <stdio.h>void my_strcat(char *s1, const char *s2) {while (*s1) {s1;}while (*s2) {*s1 *s2;s1;s2;}*s1 \0; }int main() {char s1[100] "Hello, ";char s2[] "World!";my_str…

Android初学者书籍推荐

书单 1.《Android应用开发项目式教程》&#xff0c;机械工业出版社&#xff0c;2024年出版2.《第一行代码Android》第二版3.《第一行代码Android》第三版4.《疯狂Android讲义》第四版5.《Android移动应用基础教程&#xff08;Android Studio 第2版&#xff09;》 从学安卓到用安…

uniapp 打包成安卓APP预览base64pdf实现方法

下载PDF.js 问题描述 在uniapp中预览base64的PDF&#xff0c;可以使用web-view组件嵌入一个PDF.js的实例。以下是一个简单的示例&#xff1a; 解决方案&#xff1a; 1.在页面的.vue文件中添加web-view组件&#xff1a; <template><view style"width: 50%;&qu…

【机器学习】支持向量机与主成分分析在机器学习中的应用

文章目录 一、支持向量机概述什么是支持向量机&#xff1f;超平面和支持向量大边距直觉 二、数据预处理与可视化数据集的基本信息导入必要的库加载数据集数据概况数据可视化特征对的散点图矩阵类别分布条形图平均面积与平均光滑度的散点图变量之间的相关性热图 三、模型训练&am…

JS【详解】类 class ( ES6 新增语法 )

本质上&#xff0c;类只是一种特殊的函数。 console.log(typeof 某类); //"function"声明类 class 方式 1 – 类声明 class Car {constructor(model, year) {this.model model;this.year year;} }方式 2 – 类表达式 匿名式 const Car class {constructor(mod…

在conda的环境中安装Jupyter及其他软件包

Pytorch版本、安装和检验 大多数软件包都是随Anaconda安装的&#xff0c;也可以根据需要手动安装一些其他软件包。 目录 创建虚拟环境 进入虚拟环境 安装Jupyter notebook 安装matplotlib 安装 pandas 创建虚拟环境 基于conda包的环境创建、激活、管理与删除http://t.cs…

podman 替代 docker ? centos Stream 10 已经弃用docker,开始用podman了!

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;CSDN博客专家   &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01…

浅谈React

forwardRef和useImperativeHandle的联动使用 import React, { useImperativeHandle, useRef } from "react" import { forwardRef } from "react"const CustomInput forwardRef((props, ref) > {const inputRef useRef<HTMLInputElement>(null…

Java中锁的分类、原理、使用场景、注意事项、优缺点等详解

Java开发中&#xff0c;锁是保证多线程安全的重要手段。Java提供了多种类型的锁来满足不同的同步需求。在这篇文章中&#xff0c;我将为您介绍以下几种常见的锁类型&#xff1a; 偏向锁/轻量级锁/重量级锁 偏向锁&#xff1a;当一个线程获取一个对象的锁时&#xff0c;如果发现…

解决MCM功率电源模块EMC的关键

对MCM功率电源而言&#xff0c;由于其工作在几百kHz的高频开关状态&#xff0c;故易成为干扰源。电磁兼容性EMC&#xff08;Electro Magnetic Compatibility&#xff09;&#xff0c;是指设备或系统在其电磁环境中符合要求运行并不对其环境中的任何设备产生无法忍受的电磁干扰的…

react父调用子的方法,子调用父的方法

父调用子的方法 // 子组件 import React, { useRef, useEffect } from react;const ChildComponent ({ childMethodRef }) > {const childMethod useRef(null);useEffect(() > {childMethodRef.current childMethod;}, []);const someMethod () > {console.log(子…

量化交易的实战操作与心得

量化交易&#xff0c;作为一种基于数学模型和算法执行交易的方法&#xff0c;已经在全球金融市场中取得了广泛的应用。对于从事量化交易的投资者而言&#xff0c;了解实战操作的具体细节及相关心得是至关重要的&#xff0c;它可以帮助投资者优化策略&#xff0c;提高交易效率&a…