SpringBoot+AOP+Redis 防止重复请求提交

本文项目基于以下教程的代码版本: https://javaxbfs.blog.csdn.net/article/details/135224261

代码仓库: springboot一些案例的整合_1: springboot一些案例的整合

1、实现步骤

2.引入依赖

我们需要redis、aop的依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.6</version>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>

redis的访问参数,默认的就行,不需要特别配置。

3、定义拦截注解

我们最终希望的效果是,你想要哪个方法有防止重复提交的功能,直接加上@RepeatSubmit注解即可。 代码如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
    /**
     * 加锁过期时间,默认是5秒
     * @return
     */
    long lockTime() default 5L;
}

这段代码定义了一个Java注解(Annotation)叫做RepeatSubmit。注解是Java提供的一种元数据机制,它可以被用于为代码提供附加的信息,这些信息可以被编译器用于生成代码、生成文档、代码检查等。

下面是对这段代码的详细解释:

  • @Target(ElementType.METHOD): 这个注解指定RepeatSubmit只能被用于方法上。ElementType.METHOD表示这个注解只能用于方法。

  • @Retention(RetentionPolicy.RUNTIME): 这个注解指定了RepeatSubmit的保留策略是RUNTIME。这意味着在运行时,这个注解仍然可以被读取。

  • @Documented: 这个注解表明RepeatSubmit应当被Java文档生成器包含在生成文档中。

  • public @interface RepeatSubmit: 定义了一个名为RepeatSubmit的公共注解。

  • long lockTime() default 5L: 这是RepeatSubmit注解的一个元素,名为lockTime。这个元素返回一个长整型值,并且有一个默认值5秒(5L表示5秒)。

这个注解是为了防止方法的重复提交,并提供了一个默认的加锁过期时间为5秒。如果一个方法被这个注解标记,那么在特定的加锁过期时间内,这个方法只会被执行一次。

4、实现防止重复提交切面

关于Spring Aop切面,可以看我之前的文章。

NoRepeatSubmitAspect

package com.it.demo.aspect;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.it.demo.annotation.RepeatSubmit;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
@Aspect
public class NoRepeatSubmitAspect {

    @Resource
    private RedisTemplate redisTemplate;

    @Pointcut("@annotation(repeatSubmit)")
    public void pointCutNoRepeatSubmitAspect(RepeatSubmit repeatSubmit) {

    }

    @Around("pointCutNoRepeatSubmitAspect(repeatSubmit)")
    public Object around(ProceedingJoinPoint joinPoint,RepeatSubmit repeatSubmit) throws Throwable {
        String reqSignature = buildReqSignature(joinPoint);
        //加锁时间
        long lockTime = repeatSubmit.lockTime();
        Boolean res = redisTemplate.opsForValue().setIfAbsent(reqSignature, 1, lockTime, TimeUnit.SECONDS);
        if(!res){
            throw new RuntimeException("重复请求!");
        }
        return joinPoint.proceed();
    }

    /**
     * 构建请求签名
     * @param joinPoint
     * @return
     */
    private String buildReqSignature(ProceedingJoinPoint joinPoint) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if(Objects.isNull(requestAttributes)){
            throw new RuntimeException("请求参数异常!");
        }
        HttpServletRequest request = requestAttributes.getRequest();
        String remoteAddr =request.getRemoteAddr()  + ":" +  request.getRemoteHost();
        String method = request.getMethod();
        String requestURI = request.getRequestURI();
        StringBuffer sb = new StringBuffer();
        for (Object arg : joinPoint.getArgs()) {
            sb.append(arg);
        }
        //请求签名(MD5加密)
        return DigestUtil.md5Hex(StrUtil.format("{}-{}-{}-{}",remoteAddr,method,requestURI,sb));
    }
}

这段代码是一个切面(Aspect),用于处理重复提交的情况。以下是对代码中的主要部分的解释:

  1. 导入包及注解:代码首先导入了一些类,并使用了一些注解,例如 @Component, @Aspect, @Resource, @Slf4j 等。

  2. NoRepeatSubmitAspect 类:这是一个切面类,用于实现对重复提交的处理逻辑。

  3. 成员变量:代码中使用了 @Resource 注解注入了一个 RedisTemplate 对象,用于操作 Redis。

  4. 切入点:使用 @Pointcut 注解定义了一个切入点,这里使用了 @annotation(repeatSubmit) 表达式,表示会拦截所有标注了 @RepeatSubmit 注解的方法。

  5. Around 通知:使用 @Around 注解定义了一个环绕通知,在拦截的方法执行前后会执行这里定义的逻辑。

  6. around() 方法:在该方法中,首先调用了 buildReqSignature(joinPoint) 方法构建了请求的签名信息,然后根据 RepeatSubmit 注解中的配置,在 Redis 中设置了一个锁,以防止重复提交。如果设置锁失败,则抛出了一个运行时异常,表示重复请求,否则执行被拦截的方法。

  7. buildReqSignature() 方法:该方法用于构建请求的签名信息,首先获取了请求相关的信息,然后将这些信息进行拼接,并使用 MD5 加密生成请求签名。

这段代码是一个使用 AOP 实现的防止重复提交的切面,在方法执行前通过生成请求签名并利用 Redis 进行锁定的方式,来避免重复提交。

环绕通知实现逻辑如下图所示

构建 Redis 加锁需要使用的 key,加锁key由指定前缀 + MD5(请求签名)组成,对请求签名进行MD5,一方面减少Key的长度,另一方面保证签名信息中的敏感信息不可见,其实现逻辑如下图所示:

/**
 * 构建请求签名
 * @param joinPoint
 * @return
 */
private String buildReqSignature(ProceedingJoinPoint joinPoint) {
    ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    if(Objects.isNull(requestAttributes)){
        throw new RuntimeException("请求参数异常!");
    }
    HttpServletRequest request = requestAttributes.getRequest();
    String remoteAddr =request.getRemoteAddr()  + ":" +  request.getRemoteHost();
    String method = request.getMethod();
    String requestURI = request.getRequestURI();
    StringBuffer sb = new StringBuffer();
    for (Object arg : joinPoint.getArgs()) {
        sb.append(arg);
    }
    //请求签名(MD5加密)
    return DigestUtil.md5Hex(StrUtil.format("{}-{}-{}-{}",remoteAddr,method,requestURI,sb));
}

5、 测试

查询方法加一个@RepeatSubmit

@GetMapping("/list")
@RepeatSubmit
public List<Device> list(){
    List<Device> list = deviceService.list();
    System.out.println(list);
    return deviceService.list();
}

访问localhost:8080/v1/device/list

五秒内再访问,报错:

{
    "code": 400,
    "msg": "重复请求!",
    "data": null
}

验证通过。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker - 镜像 | 容器 | 数据卷 日常开发常用指令 + 演示(一文通关)

目录 Docker 开发常用指令汇总 辅助命令 docker version docker info docker --help 镜像命令 查看镜像信息 下载镜像 搜索镜像 删除镜像 容器命令 查看运行中的容器 运行容器 停止、启动、重启、暂停、恢复容器 杀死容器 删除容器 查看容器日志 进入容器内部…

记一次修复外网无法访问vmware里面的虚拟机的网络端口的问题

发现一个奇怪的网络问题&#xff0c;vmware里一个程序的端口通过vmnat穿透出来&#xff0c;然后这个端口就能够通过局域网被其他机器访问&#xff0c;但是另一个网段就没法访问这个端口。使用主机上的其他程序使用开启同样的端口&#xff0c;另一个网段的机器却可以访问。我想不…

idea 如何快速拉取新分支

方式1 &#xff08;快捷键&#xff1a;CtrlShift~&#xff09; 方式2:&#xff08;快捷键&#xff1a;Alt9&#xff09;

java Servlet 汽车保养服务平台系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java Servlet 汽车保养服务平台系统是一套完善的java web信息管理系统&#xff0c;采用serlvetdaobean mvc模式开发&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数 据库&#xff0c;系统主要采用B/S模式开发。开发环境为…

Linux系统下隧道代理HTTP

在Linux系统下配置隧道代理HTTP是一个涉及网络技术的话题&#xff0c;主要目的是在客户端和服务器之间建立一个安全的通信通道。下面将详细解释如何进行配置。 一、了解基本概念 在开始之前&#xff0c;需要了解几个关键概念&#xff1a;代理服务器、隧道代理和HTTP协议。代理…

一文带你掌握Flutter dio网络请求库的封装

highlight: an-old-hope 封装网络库考虑的几个方面&#xff1a; 请求参数的封装&#xff1a; 将请求所需的参数进行封装&#xff0c;例如 URL、请求头、请求体等。可以定义一个统一的数据结构或模型类来表示请求参数&#xff0c;以便于传递和管理。 响应结果的封装&#xff…

ChatGPT使用注意事项有哪些?

一、环境注意事项 1、需要构造稳定的环境&#xff0c;很多人说自己的号为什么突然被封&#xff0c;被封的原因是因为有些环境会自动换IP&#xff0c;所以你要设置好 2、搭建美国住宅IP 3、注册时也不要使用香港&#xff0c;最好选择美国、新加坡等地区注册 二、API调用注意事…

【Java EE初阶四】锁及synchronized关键字

1. 加锁的目的 对于count这样的一个java语句&#xff0c;其底层是由三个基本操作组成的&#xff0c;我们在多线程中运行一个java语句&#xff0c;但是该语句的三个操作会被其他线程冲散&#xff0c;导致整个Java语句不能及时的一次性完成&#xff0c;这样就会导致我们的预期结果…

【WordPress插件】热门关键词推荐v1.3.0 Pro开心版

介绍&#xff1a; WordPress插件-WBOLT热门关键词推荐插件&#xff08;Smart Keywords Tool&#xff09;是一款集即时关键词推荐、关键词选词工具及文章智能标签功能于一体的WordPress网站SEO优化插件。 智能推荐&#xff1a; 热门关键词推荐引擎-支持360搜索、Bing、谷歌&a…

极智开发 | 解读英伟达软件生态 深度神经网络库cuDNN

欢迎关注,获取我的更多经验分享 大家好,我是极智视界,本文来介绍一下 解读英伟达软件生态 深度神经网络库cuDNN。 邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码下载,链接:https://t.zsxq.com/0aiNxERDq cuDNN,全称为 NVIDIA CUDA Deep Neural Net…

3D动态路障生成

3D动态路障生成 介绍设计实现1.路面创建2.空物体的创建3.Create.cs脚本创建 总结 介绍 上一篇文章介绍了Mathf.Lerp的底层实现原理&#xff0c;这里介绍一下跑酷类游戏的动态路障生成是如何实现的。 动态路障其实比较好生成&#xff0c;但是难点在哪里&#xff0c;如果都是平面…

RHCE9学习指南 第11章 网络配置

11.1 网络基础知识 一台主机需要配置必要的网络信息&#xff0c;才可以连接到互联网。需要的配置网络信息包括IP&#xff0c;子网掩码&#xff0c;网关和DNS。 11.1.1 IP地址 在计算机中对IP的标记使用的是32bit的二进制&#xff0c;例如&#xff0c; 11000000 10101000 00…

基于梯度和频率域的深度超分辨率新方法笔记二

一、实现方法和网络结构的剖析 1.1 网络结构 梯度校准模块(GCM)和频率感知模块&#xff08;FAM&#xff09; 1&#xff09;梯度校准模块(GCM) 1、使用梯度映射函数&#xff08;如下图所示&#xff09;&#xff0c;将RGB和LR深度图映射到梯度域 2、再梯度域中使用RGB的梯度特…

【 C语言 】 | C程序百例

【 C语言 】 | C程序百例 时间&#xff1a;2023年12月28日13:50:43 文章目录 【 C语言 】 | C程序百例1.参考2.练习 1.参考 1.【 C语言 】 | C程序百例-CSDN博客 2.100Example: C程序百例-酷勤网&#xff08;kuqin.com&#xff09;提供.pdf (gitee.com) 3.cProgram/LinuxC - 码…

IPD-PDP产品开发流程-PDT产品开发计划Charter文档模板(word)2

书接上回&#xff0c;继续为大家分享PDT的产品开发计划Charter模板的主要内容。 据华研荟了解&#xff0c;大部分国内的企业在推行IPD的时候就直接像华为一样&#xff0c;把开发计划&#xff08;任务书&#xff09;叫做Charter&#xff0c;而不翻译为中文。其实这也是一种很好…

高校怎么搭建虚拟数字人动作捕捉实训室?

如今&#xff0c;虚拟数字人市场迎来爆发风口&#xff0c;在产业推动下&#xff0c;虚拟数字人动作捕捉技术人才需求旺盛&#xff0c;高校搭建虚拟数字人动作捕捉实训室&#xff0c;可以让学生接触前沿的全身动作捕捉系统&#xff0c;能够培养具有开阔眼界和先进技术接轨的专业…

鸿蒙系列--组件介绍之其他基础组件(上)

上回介绍了基础组件中最常用的组件常用的基础组件&#xff0c;接下来还有其他基础组件 一、Blank 描述&#xff1a;空白填充组件 功能&#xff1a;在容器主轴方向上&#xff0c;具有自动填充容器空余部分的能力。只有当父组件为Row/Column时生效 子组件&#xff1a;无 Blan…

羊大师讲解,羊奶在本草纲目中的作用及其益处

羊大师讲解&#xff0c;羊奶在本草纲目中的作用及其益处 羊奶是一种传统的中药材&#xff0c;早在《本草纲目》中就被广泛记录和应用。它是从母羊妊娠后分泌出来的乳汁&#xff0c;具有丰富的营养成分和独特的药用价值。小编羊大师发现&#xff0c;羊奶被认为是一种滋补养颜、…

3D 渲染如何帮助电商促进销售?

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 3D 渲染图像因其高转化率而成为亚马逊卖家的最新趋势。它是电子商务平…

Linux iptables防火墙(一)

1.1 Linux防火墙基础 在 Internet 中&#xff0c;企业通过架设各种应用系统来为用户提供各种网络服务&#xff0c;如 Web 网站、 电子邮件系统、 FTP 服务器、数据库系统等。那么&#xff0c;如何来保护这些服务器&#xff0c;过滤企业不 需要的访问甚至是恶意的入侵呢&a…