Spring Boot使用AOP防止重复提交的方法示例
程序员文章站
2023-11-12 20:43:04
在传统的web项目中,防止重复提交,通常做法是:后端生成一个唯一的提交令牌(uuid),并存储在服务端。页面提交请求携带这个提交令牌,后端验证并在第一次验证后删除该令牌,保...
在传统的web项目中,防止重复提交,通常做法是:后端生成一个唯一的提交令牌(uuid),并存储在服务端。页面提交请求携带这个提交令牌,后端验证并在第一次验证后删除该令牌,保证提交请求的唯一性。
上述的思路其实没有问题的,但是需要前后端都稍加改动,如果在业务开发完在加这个的话,改动量未免有些大了,本节的实现方案无需前端配合,纯后端处理。
思路
- 自定义注解 @norepeatsubmit 标记所有controller中的提交请求
- 通过aop 对所有标记了 @norepeatsubmit 的方法拦截
- 在业务方法执行前,获取当前用户的 token(或者jsessionid)+ 当前请求地址,作为一个唯一 key,去获取 redis 分布式锁(如果此时并发获取,只有一个线程会成功获取锁)
- 业务方法执行后,释放锁
关于redis 分布式锁
不了解的同学戳这里 ==> redis分布式锁的正确实现方式
使用redis 是为了在负载均衡部署,如果是单机的部署的项目可以使用一个线程安全的本地cache 替代 redis
code
这里只贴出 aop 类和测试类,完整代码见 ==> gitee
@aspect @component public class repeatsubmitaspect { private static final logger logger = loggerfactory.getlogger(repeatsubmitaspect.class); @autowired private redislock redislock; @pointcut("@annotation(com.gitee.taven.aop.norepeatsubmit)") public void pointcut() {} @around("pointcut()") public object before(proceedingjoinpoint pjp) { try { httpservletrequest request = requestutils.getrequest(); assert.notnull(request, "request can not null"); // 此处可以用token或者jsessionid string token = request.getheader("authorization"); string path = request.getservletpath(); string key = getkey(token, path); string clientid = getclientid(); boolean issuccess = redislock.trylock(key, clientid, 10); logger.info("trylock key = [{}], clientid = [{}]", key, clientid); if (issuccess) { logger.info("trylock success, key = [{}], clientid = [{}]", key, clientid); // 获取锁成功, 执行进程 object result = pjp.proceed(); // 解锁 redislock.releaselock(key, clientid); logger.info("releaselock success, key = [{}], clientid = [{}]", key, clientid); return result; } else { // 获取锁失败,认为是重复提交的请求 logger.info("trylock fail, key = [{}]", key); return new apiresult(200, "重复请求,请稍后再试", null); } } catch (throwable throwable) { throwable.printstacktrace(); } return new apiresult(500, "系统异常", null); } private string getkey(string token, string path) { return token + path; } private string getclientid() { return uuid.randomuuid().tostring(); } }
多线程测试
测试代码如下,模拟十个请求并发同时提交
@component public class runtest implements applicationrunner { private static final logger logger = loggerfactory.getlogger(runtest.class); @autowired private resttemplate resttemplate; @override public void run(applicationarguments args) throws exception { system.out.println("执行多线程测试"); string url="http://localhost:8000/submit"; countdownlatch countdownlatch = new countdownlatch(1); executorservice executorservice = executors.newfixedthreadpool(10); for(int i=0; i<10; i++){ string userid = "userid" + i; httpentity request = buildrequest(userid); executorservice.submit(() -> { try { countdownlatch.await(); system.out.println("thread:"+thread.currentthread().getname()+", time:"+system.currenttimemillis()); responseentity<string> response = resttemplate.postforentity(url, request, string.class); system.out.println("thread:"+thread.currentthread().getname() + "," + response.getbody()); } catch (interruptedexception e) { e.printstacktrace(); } }); } countdownlatch.countdown(); } private httpentity buildrequest(string userid) { httpheaders headers = new httpheaders(); headers.setcontenttype(mediatype.application_json); headers.set("authorization", "yourtoken"); map<string, object> body = new hashmap<>(); body.put("userid", userid); return new httpentity<>(body, headers); } }
成功防止重复提交,控制台日志如下,可以看到十个线程的启动时间几乎同时发起,只有一个请求提交成功了
本节demo
戳这里 ==> gitee
build项目之后,启动本地redis,运行项目自动执行测试方法
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
Spring Boot使用AOP防止重复提交的方法示例
-
Spring AOP中使用args表达式的方法示例
-
JSP使用自定义标签防止表单重复提交的方法
-
Spring Boot中使用AOP统一处理web层异常的方法
-
spring boot使用自定义注解+AOP实现对Controller层方法的日志记录
-
Spring AOP中使用args表达式的方法示例
-
使用Docker部署Spring Boot的方法示例
-
在 Spring Boot 中使用 Spring AOP 和 AspectJ 来测量方法的执行时间
-
JSP使用自定义标签防止表单重复提交的方法
-
PHP使用token防止表单重复提交的方法_PHP