springboot3使用自定义注解+AOP+redis优雅实现防重复提交

 

⛰️个人主页:     蒾酒

🔥系列专栏:《spring boot实战》

🌊山高路远,行路漫漫,终有归途


目录

写在前面

实现思路

实现步骤

1.定义防重复提交注解

2.编写一个切面去发现该注解然后执行防重复提交逻辑

3.测试

依赖条件

1.接口上标记防重复提交注解

2.接口测试

写在最后


写在前面

本文介绍了springboot开发后端服务中,防重复提交功能的设计与实现,坚持看完相信对你有帮助。

同时欢迎订阅springboot系列专栏,持续分享spring boot的使用经验。

实现思路

通过定义一个防重复提交的自定义注解,再通过AOP的前置通知拦截带有该注解的方法,执行防重复提交逻辑,需要拼接一个唯一的key,如果redis中不存在则代表第一次请求,将这个key存入redis,设置注解类中指定的过期时间,遇到下次重复提交请求,直接抛出对应异常,全局异常处理返回对应信息即可。

需要注意

这个key的生成需要考虑有token和无token情况,同时满足唯一性。

  • 有 token;可以用 token+请求参数,做为唯一值!
  • 无 token:可以用请求路径+请求参数,做为唯一值!

实现步骤

1.定义防重复提交注解

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * @author mijiupro
 */
@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {

    /**
     * 锁定时间,默认5000毫秒
     */
    int interval() default 5000;

    /**
     * 锁定时间单位,默认毫秒
     */
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

    /**
     * 提示信息
     */
    String message() default "不允许重复提交,请稍后再试!";

}

2.编写一个切面去发现该注解然后执行防重复提交逻辑

因为缓存的key有拼接请求参数,所以遇到文件类型的参数需要进行过滤,拼接逻辑以及参数过滤方法都在下面代码中。

import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.mijiu.commom.aop.annotation.RepeatSubmit;
import com.mijiu.commom.exception.GeneralBusinessException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import java.util.Collection;
import java.util.Map;
import java.util.Objects;

/**
 * @author mijiupro
 */
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
    private final StringRedisTemplate redisTemplate;

    public RepeatSubmitAspect(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Before("@annotation(repeatSubmit)")
    public void before(JoinPoint joinPoint, RepeatSubmit repeatSubmit) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = null;

        if (attributes != null) {
            request = attributes.getRequest();
        }

        //请求参数拼接
        String requestParams = argsArrayToString(joinPoint.getArgs());

        String authorizationHeader = null;
        if (request != null) {
            authorizationHeader = request.getHeader("Authorization");
        }

        String submitKey = null;

        if (authorizationHeader != null) {
            //如果存在token则通过token+请求参数生成唯一标识
            String token = StringUtils.removeStart(authorizationHeader, "Bearer ");
            submitKey= SecureUtil.md5(token+":"+requestParams);

        } else{
            //不存在token则通过请求url+参数生成唯一标识
            if (request != null) {
                submitKey = SecureUtil.md5(request.getRequestURL().toString()+":"+requestParams);
            }
        }
        //缓存key
        String cacheKey = "repeat_submit:"+submitKey;

        if (Boolean.TRUE.equals(redisTemplate.hasKey(cacheKey))) {
            throw new GeneralBusinessException(repeatSubmit.message());
        }
        redisTemplate.opsForValue().set(cacheKey, "1", repeatSubmit.interval(), repeatSubmit.timeUnit());
    }


    /**
     * 参数拼接
     * @param args  参数数组
     * @return 拼接后的字符串
     */
    private String argsArrayToString(Object[] args){
        StringBuilder params = new StringBuilder();
        if(args!= null && args.length > 0){
            for(Object o:args){
                if(Objects.nonNull(o)&&!isFilterObject(o)){
                    try {
                        params.append(JSONUtil.toJsonStr(o)).append(" ");
                    }catch (Exception e){
                        log.error("参数拼接异常:{}",e.getMessage());
                    }
                }
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象。
     * @param o  对象
     * @return true:需要过滤;false:不需要过滤
     */
    private boolean isFilterObject(final Object o) {
        Class<?> c = o.getClass();
        //如果是数组且类型为文件类型的需要过滤
        if(c.isArray()){
            return  c.getComponentType().isAssignableFrom(MultipartFile.class);
        }
        //如果是集合且类型为文件类型的需要过滤
        else if(Collection.class.isAssignableFrom(c)){
            Collection collection = (Collection) o;
            for(Object value:collection){
                return value instanceof MultipartFile;
            }
        }
        //如果是Map且类型为文件类型的需要过滤
        else if(Map.class.isAssignableFrom(c)){
            Map map = (Map) o;
            for(Object value:map.entrySet()){
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        //如果是文件类型的需要过滤
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }

}

3.测试

依赖条件

redis:

Spring Boot3整合Redis_springboot3整合redis-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_62262918/article/details/136067550?spm=1001.2014.3001.5502

全局异常捕获:

Spring Boot3自定义异常及全局异常捕获_全局异常捕获 自定义异常-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_62262918/article/details/136110267?spm=1001.2014.3001.5502

swagger3:

Spring Boot3整合knife4j(swagger3)_springboot3 knife4j-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_62262918/article/details/135761392?spm=1001.2014.3001.5502

hutool工具包:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.25</version>
</dependency>

1.接口上标记防重复提交注解

随便写个测试接口添加防重复提交注解设置间隔5000毫秒

    @PostMapping("/add")
    @RepeatSubmit(interval= 5000)
    public void test(@RequestBody User user){
        //添加用户的操作逻辑。。。
    }

2.接口测试

第一次提交

可以看到对应缓存已经存入redis了

5s内第二次提交

写在最后

springboot使用自定义注解+AOP+redis优雅实现防重复提交到这里就结束了,本文介绍了一种通用的防重复提交的实现方式,代码逻辑清晰。任何问题评论区或私信讨论,欢迎指正。

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

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

相关文章

VNC Viewer 连接远程主机及常见错误处理(The connection closed unexpectedly)

搭建可接收vnc连接的环境 1、下载镜像 https://github.com/fcwu/docker-ubuntu-vnc-desktop 该镜像是含有ubuntu 通过docker运行的桌面环境&#xff0c;并接受vnc 的连接 docker pull dorowu/ubuntu-desktop-lxde-vnc 2、运行容器 1&#xff09;只暴露容器的80端口&#…

1.汉诺塔问题

C力扣 汉诺塔 class Solution { public:void hanota(vector<int>& a, vector<int>& b, vector<int>& c) {dfs(a,b,c,a.size());}void dfs(vector<int>& a, vector<int>& b, vector<int>& c,int n){if(n1){c.push…

李廉洋;4.13黄金,原油最新资讯,下周一盘走势分析及策略。

美国杜克大学大宗商品研究教授Harvey表示&#xff0c;目前除了避险情绪外&#xff0c;“上涨惯性”也是促使黄金“疯涨”的原因。他表示&#xff1a;“目前不断涌入黄金市场的资金实际上在增加风险敞口&#xff0c;除了散户&#xff0c;对冲基金和其他机构投资者也加入了黄金热…

股票价格预测 | Python使用LSTM预测股票价格

文章目录 效果一览文章概述代码设计效果一览 文章概述 Python使用LSTM预测股票价格 代码设计 import pandas as pd import matplotlib.pyplot as plt import numpy as np import tensorflowfrom numpy import

【Jenkins】Jenkins自动化工具介绍

目录 技术背景常规的手动打包步骤 Jenkins简介起源与发展Jenkins的核心价值1.自动化1.1代码构建1.2测试自动化1.3自动部署 2.持续集成与持续部署CI/CD的概念如何减少集成问题更快速地发布软件版本 Jenkins优势Jenkins的主要竞争对手Travis CI:CircleCI:GitLab CI: Jenkins与其他…

Go操作Kafka之kafka-go

Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff0c;本文介绍了如何使用kafka-go这个库实现Go语言与kafka的交互。 Go社区中目前有三个比较常用的kafka客户端库 , 它们各有特点。 首先是IBM/sarama&#xff08;这个库已经由Shopify转给了IBM&#xff09;&#xff0c;之…

WebGL 2.0相较于1.0有什么不同?

作者&#xff1a;STANCH 1.概述 WebGL 1.0自推出以来&#xff0c;已成为广泛支持的Web标准&#xff0c;既能跨平台&#xff0c;还免版税。它通过插件为Web浏览器带来高质量的3D图形&#xff0c;这是迄今为止市场上使用最广泛的Web图形&#xff0c;并得到Apple&#xff0c;Goog…

Arduino _按键点亮——led

int8_t led_pin13;int8_t led2_pin12; void setup() {// put your setup code here, to run once:pinMode(led_pin, INPUT);//输入pinMode(led2_pin, OUTPUT);//输出 }void loop() {// put your main code here, to run repeatedly:if(digitalRead(led_pin)1){//digitalRead(…

智慧校园平台解决方案-迎新管理系统

数字迎新管理系统是整个智慧校园不可缺少的一部分&#xff1b;主要是为了方便学校优化迎新流程&#xff0c;规范迎新流程&#xff0c;高效率的迎新&#xff0c;减少学生迎新繁琐流程。现阶段自友科技数字迎新利用了网络方式在网上即可提前知道迎新流程&#xff0c;学生家长在手…

【python】python饮料销售数据分析可视化(源码+数据集)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

高效保护,无粉乳胶手套助您安心工作

在快节奏的现代生活中&#xff0c;我们每天都在与各种细菌、污染物和化学品打交道&#xff0c;因此保护我们的身体健康变得愈发重要。特别是在工作环境中&#xff0c;手部是最容易受到伤害和污染的部位之一。为了提供最佳的保护&#xff0c;优斯特推出了一款高品质的无粉乳胶手…

Web3 的社会影响:数字社会的新时代

随着科技的不断进步和创新&#xff0c;人类社会正逐步进入数字化时代的新阶段。Web3 技术作为数字社会的重要组成部分&#xff0c;正在以前所未有的方式重塑着我们的社会生活和交往方式。本文将探讨 Web3 技术对社会的影响&#xff0c;以及它所带来的数字社会的新时代。 1. Web…

阻塞队列和生产消费模型

阻塞队列 阻塞队列的概念 队列相信我们已经不陌生了 之前也学过很多队列 比如: 普通队列 和 优先级队列 两种 这两种队列都是线程不安全的 而我们讲的阻塞队列 刚好可以解决线程安全问题 也是先进先出 并且带有阻塞功能. 阻塞功能是怎么回事呢 就是如果入队的时候阻塞队列为…

深入解析API技术:原理、实现与应用

在现代软件开发中&#xff0c;API&#xff08;应用程序接口&#xff09;扮演着至关重要的角色。API 允许不同的软件应用程序和系统之间进行通信和数据交换&#xff0c;从而构建出更加高效、灵活和可扩展的软件解决方案。本文将深入解析API技术的原理、实现方法&#xff0c;并附…

iptables/ebtables学习笔记

目录 一、前言 二、Netfilter 构成 三、Netfilter 转发框架 四、Netfilter 与 iptables 五、Netfilter 与 ebtables 一、前言 Netfilter 是 Linux 内核的数据包处理框架&#xff0c;由 Rusty Russell 于 1998 年开发&#xff0c; 旨在改进以前的 ipchains&#xff08;Lin…

中霖教育:一级建造师能跨省考吗?

一级建造师是可以跨省考的&#xff0c;对于考生的户籍地并没有限制&#xff0c;只要符合参加考试的条件就可以报名。 但是&#xff0c;对于异地报名参加一级建造师考试的考生&#xff0c;必须经过严格的资料审核流程。考生在报名过程中需要提交居住证明、工作证明、以及异地考…

c语言例题,求数组中最大值,99乘法口诀表

例题1&#xff1a;求出数组中最大的值 根据题意&#xff0c;我们知道的是需要从一个数组中找到一个最大的元素并且输出。那首先我们先建立一个数组&#xff0c;然后将一些不有序的整型元素放到数组中&#xff0c;然后再建立一个变量来存放数组中的第一个元素&#xff0c;通过一…

第23次修改了可删除可持久保存的前端html备忘录:增加了百度引擎

第22次修改了可删除可持久保存的前端html备忘录视频背景分离&#xff0c;增加了本地连接&#xff0c;增加了纯CSS做的折叠隐藏修改说明 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport…

(三)ffmpeg 解码流程以及函数介绍

一、视频解码流程 二、函数介绍 1.avformat_network_init 函数作用&#xff1a; 执行网络库的全局初始化。这是可选的&#xff0c;不再推荐。 此函数仅用于解决旧GnuTLS或OpenSSL库的线程安全问题。如果libavformat链接到这些库的较新版本&#xff0c;或者不使用它们&#…

跟TED演讲学英文:The dark side of competition in AI by Liv Boeree

The dark side of competition in AI Link: https://www.ted.com/talks/liv_boeree_the_dark_side_of_competition_in_ai Speaker:Liv Boeree Date: October 2023 文章目录 The dark side of competition in AIIntroductionVocabularyTranscriptSummary后记 Introduction Co…