使用AOP防止请求重复提交

news/2024/12/14 16:48:43/文章来源:https://www.cnblogs.com/zsyzw/p/18606918

使用AOP防止请求重复提交

  1. 首先定义注解NoPepeatSubmit
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {long value() default 1000*10;
}
  1. 定义AOP相关方法
public class RepeatSubmitAspect {@Autowiredprivate StringRedisService stringRedisService;@Pointcut("@annotation(xxx.xxx.NoRepeatSubmit)")public void pointCut() {}@Around("pointCut()")public Object around(ProceedingJoinPoint pjp) throws Throwable {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();Assert.notNull(request, "request can not null");// 此处可以用token--作为用户唯一标识String token = request.getHeader("Authorization-admin");String key = token + "-" + request.getServletPath();// 获取注解NoRepeatSubmit annotation = ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(NoRepeatSubmit.class);// 获取注解相关参数:这里是时间,表示同一用户多久可以请求一次long expire = annotation.value();//超时时间:10秒,最好设为常量String time=String.valueOf(System.currentTimeMillis() + expire);//加锁 --这里需要考虑并发问题,详情见下文boolean islock = stringRedisService.secKilllock(key, time);if (islock) {Object result;try {//执行请求result = pjp.proceed();} finally {//解锁stringRedisService.unlock(key,time);}return result;}else {// 重复请求return new Result(CoReturnFormat.REPEAT_REQUEST);}}
}
  1. 请求上添加NoPepeatSubmit注解

注:相关概念

防止重复提交涉及到的锁相关概念

防止重复提交的基本思路是对一个请求,将它存储到redis中作为key,value是保持时间---这个时间内不对相同的请求做响应
首先是确保同一用户的同一请求,这里使用token + "-" + request.getServletPath()保证
其次,redis中锁设置如下:

public boolean secKilllock(String key,String value){/*** setIfAbsent就是setnx* 将key设置值为value,如果key不存在,这种情况下等同SET命令。* 当key存在时,什么也不做。SETNX是”SET if Not eXists”的简写* */if(redisTemplate.opsForValue().setIfAbsent(key,value)){//加锁成功返回truereturn true;}//避免死锁,且只让一个线程拿到锁//走到这里的线程都加锁失败了,有两种情况,//一种是在重复提交的时间范围内,//一种是不在重复提交的范围内,这是这可能是因为加入redis的请求出现异常,导致没有删除keyString currentValue = (String) redisTemplate.opsForValue().get(key);/*** 下面这几行代码的作用:* 1、防止死锁* 2、防止多线程抢锁* */if(! StringUtils.isEmpty(currentValue)&& Long.parseLong(currentValue) < System.currentTimeMillis()){//如果锁过期了,获取上一个锁的时间String oldValue = (String) redisTemplate.opsForValue().getAndSet(key,value);//只会让一个线程拿到锁----这里有个问题,多个线程到这里的时候,不能确保通过的线程是上锁的线程//这里应该保证的是立即重复点击的请求放行一个即可//如果旧的value和currentValue相等,只会有一个线程达成条件,因为第二个线程拿到的oldValue已经和currentValue不一样了if(! StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)){return true;}}return false;}/*** 解锁* @param key* @param value* */@Overridepublic void unlock(String key,String value){try{String currentValue = (String) redisTemplate.opsForValue().get(key);if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {redisTemplate.opsForValue().getOperations().delete(key);}}catch (Exception e){e.printStackTrace();log.error("『redis分布式锁』解锁异常,{}", e);}}

注解--黑马

Java注解是代码中的特殊标记,比如@Override、@Test等,作用是:让其他程序根据注解信息决定怎么执行该程序。
比如:Junit框架的@Test注解可以用在方法上,用来标记这个方法是测试方法,被@Test标记的方法能够被Junit框架执行。
再比如:@Override注解可以用在方法上,用来标记这个方法是重写方法,被@Override注解标记的方法能够被IDEA识别进行语法检查。
注解不光可以用在方法上,还可以用在类上、变量上、构造器上等位置。
自定义注解的格式如下图所示:
img
比如:现在我们自定义一个MyTest注解

public @interface MyTest{String aaa();boolean bbb() default true;	//default true 表示默认值为true,使用时可以不赋值。String[] ccc();
}

定义好MyTest注解之后,我们可以使用MyTest注解在类上、方法上等位置做标记。注意使用注解时需要加@符号,如下

@MyTest1(aaa="牛魔王",ccc={"HTML","Java"})
public class AnnotationTest1{@MyTest(aaa="铁扇公主",bbb=false, ccc={"Python","前端","Java"})public void test1(){}
}

注意:注解的属性名如何是value的话,并且只有value没有默认值,使用注解时value名称可以省略。比如现在重新定义一个MyTest2注解

public @interface MyTest2{String value(); //特殊属性int age() default 10;
}

定义好MyTest2注解后,再将@MyTest2标记在类上,此时value属性名可以省略,代码如下

@MyTest2("孙悟空") //等价于 @MyTest2(value="孙悟空")
@MyTest1(aaa="牛魔王",ccc={"HTML","Java"})
public class AnnotationTest1{@MyTest(aaa="铁扇公主",bbb=false, ccc={"Python","前端","Java"})public void test1(){}
}

注解本质是什么呢?

img
1.MyTest1注解本质上是接口,每一个注解接口都继承子Annotation接口
2.MyTest1注解中的属性本质上是抽象方法
3.@MyTest1实际上是作为MyTest接口的实现类对象
4.@MyTest1(aaa="孙悟空",bbb=false,ccc={"Python","前端","Java"})里面的属性值,可以通过调用aaa()、bbb()、ccc()方法获取到。
什么是元注解?

元注解是修饰注解的注解。这句话虽然有一点饶,但是非常准确。我们看一个例子
img
接下来分别看一下@Target注解和@Retention注解有什么作用,如下图所示

@Target是用来声明注解只能用在那些位置,比如:类上、方法上、成员变量上等
@Retetion是用来声明注解保留周期,比如:源代码时期、字节码时期、运行时期

img
解析注解

我们把获取类上、方法上、变量上等位置注解及注解属性值的过程称为解析注解。
解析注解套路如下
1.如果注解在类上,先获取类的字节码对象,再获取类上的注解
2.如果注解在方法上,先获取方法对象,再获取方法上的注解
3.如果注解在成员变量上,先获取成员变量对象,再获取变量上的注解
总之:注解在谁身上,就先获取谁,再用谁获取谁身上的注解
img

接口--黑马

当定义接口是默认成员变量使用public static final修饰,方法使用public abstract修饰,可以省略

public interface A{//这里public static final可以加,可以不加。public static final String SCHOOL_NAME = "黑马程序员";//这里的public abstract可以加,可以不加。public abstract void test();
}

JDK8的新特性
增加了默认方法,私有方法,静态方法三种方法

public interface A {/*** 1、默认方法:必须使用default修饰,默认会被public修饰* 实例方法:对象的方法,必须使用实现类的对象来访问。*/default void test1(){System.out.println("===默认方法==");test2();}/*** 2、私有方法:必须使用private修饰。(JDK 9开始才支持的)*   实例方法:对象的方法。*/private void test2(){System.out.println("===私有方法==");}/*** 3、静态方法:必须使用static修饰,默认会被public修饰*/static void test3(){System.out.println("==静态方法==");}void test4();void test5();default void test6(){}
}

1.一个接口继承多个接口,如果多个接口中存在相同的方法声明,则此时不支持多继承
2.一个类实现多个接口,如果多个接口中存在相同的方法声明,则此时不支持多实现
3.一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先使用父类的方法
4.一个类实现类多个接口,多个接口中有同名的默认方法,则这个类必须重写该方法。
综上所述:一个接口可以继承多个接口,接口同时也可以被类实现。

public class Test implements A,B {//对应情况2,一个类实现多个同名方法的接口只会实现一个方法,确保唯一,idea中这个方法显示是多个同名方法的实现@Overridepublic void test() {System.out.println("hello");}
}
interface A{void test();
}
interface B{void test();
}
interface C extends A,B{}
class D implements C{// 对应情况1,接口继承了多个同名方法接口,实现时只有一个同名方法@Overridepublic void test() {System.out.println("D");}
}

类似的,类比到情况4,当出现接口同名方法冲突时,java会保证只实现一个,从而确保唯一性,避免冲突

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

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

相关文章

三门峡知识付费系统服务热线

关于三门峡地区的知识付费系统及教育服务,虽然直接与三门峡地区的本地资源相关的信息比较有限,我们可以提供一些更宽广的信息和资源链接以帮助相关从业人员更好地了解知识付费系统和服务相关的背景信息与技术细节。例如,如果您在寻求构建或选择一个适合自身需求的知识付费系…

2024-2025-1 20241305《计算机基础与程序设计》第十二周学习总结

------------恢复内容开始------------ 作业信息这个作业属于哪个课程 2024-2025-1-计算机基础与程序设计(https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP))这个作业要求在哪里 2024-2025-1计算机基础与程序设计第十二周作业这个作业的目标 指针和数组作业正文 本博客…

微信小程序商城构建全栈应用

D:\PanDownload\【微信小程序】\微信小程序商城构建全栈应用 第1章 前言:不同的时代,不同的Web 1-1 前言与导语 导语 好的课程需要包含俩方面: 一:整体的思路与编程思想(大局观,AOP ,10~20%) 二:具体的编程知识与技巧(TP5,小程序,数据库等80%) books+code 1-2 产品所使用的技…

龍兄虎弟 综艺 All In One

龍兄虎弟 综艺 All In One 主持人:張菲、費玉清龍兄虎弟 综艺 All In One主持人:張菲、費玉清精彩片段 https://www.youtube.com/watch?v=fD1MxE9e3Bg&list=PLtww_vcpAB8pJn3goLppo42EDqjt8t1kh完整版 https://www.youtube.com/watch?v=67MJj22yMp0&list=PLRWrniKO…

UWB物理层实现-特殊汉明码纠错

根据802.15.4协议,chapter15.2.7,PHR部分的编码,除了一些控制参数外,在后面添加了6位单错纠正双错检测码(SECDED),用于纠错能力的提升,这6位汉明码为PHR部分提供了至少1bit的纠错能力,以及至少2bit的检错能力。此码块由汉明码构成,与一般汉明码不同的是,改码并没有穿…

NestJS导出API文档

在NestJS中,你可以使用@nestjs/swagger包来定义你的API文档,并且可以很容易地将这些文档转换为API调用。以下是一个简单的例子,展示如何使用NestJS和Swagger来创建一个API文档,并且如何生成API调用。 首先,安装@nestjs/swagger和swagger-ui-express:npm install @nestjs/…

python版本切换

1、搜索框输入:环境变量2、在下面的系统变量:选择path -> 点击:编辑3、将需要的python版本上移到上面,调整好后,一路点击确定关闭几个打开的窗口,保存环境变量配置。3、打开cmd,输入:python --version 确认版本是否切换成功

机器学习期末复习笔记

机器学习期末复习机器学习期末复习笔记 简介 主要注重决策树的计算,朴素贝叶斯,PCA降维的计算 笔记

07相关软件的安装以及HTML介绍

一、内容回顾这个软件从1.0就开始收费了,这里使用这个版本这里将侧边栏打开 这个typora软件支持Markdown的格式,markdown格式是我们经常会使用到的笔记格式的形式,后缀名是md 这种语法在整理笔记是比较方便的 1、标题 加上#号表示是标题,这个和H1,H2...标签类似 加上不同的…

Threejs教程,2024全新系统threejs入门教程

Threejs教程,2024全新系统threejs入门教程 https://www.bilibili.com/video/BV1Zm421g7oi/?spm_id_from=333.999.0.0 2 4 01-theejs三要素 WebGL 顶点数据 顶点索引 矩阵 三要素 场景 Scene 容器 相机 Camera 观察 渲染器Renderer 组合 透视相机(PerspectiveCamera) htt…

命令行下php加载模块

命令行下php加载模块 在命令行下使用 PHP 加载模块,你可以使用 -d 选项来设置 php.ini 中的设置,或者使用 dl() 函数来动态加载 PHP 扩展模块。 例如,如果你想要加载一个名为 my_module.so 的模块,你可以这样做: php -d "extension=my_module.so" script.php &l…

虚拟机配置rsync同步

虚拟机配置rsync同步安装 apt install rsync修改配置文件 vim /etc/rsyncd.confuid = nobody gid = nogroup use chroot = no max connections = 4 pid file = /var/run/rsyncd.pid lock file = /var/run/rsync.lock log file = /var/log/rsyncd.log[html]path = /home/xuxb/ht…