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

这篇具有很好参考价值的文章主要介绍了springboot3使用自定义注解+AOP+redis优雅实现防重复提交。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

⛰️个人主页:     蒾酒

🔥系列专栏:《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博客https://blog.csdn.net/qq_62262918/article/details/136067550?spm=1001.2014.3001.5502

全局异常捕获:

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

swagger3:

Spring Boot3整合knife4j(swagger3)_springboot3 knife4j-CSDN博客https://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.接口测试

第一次提交

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

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

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

5s内第二次提交

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

写在最后

springboot使用自定义注解+AOP+redis优雅实现防重复提交到这里就结束了,本文介绍了一种通用的防重复提交的实现方式,代码逻辑清晰。任何问题评论区或私信讨论,欢迎指正。文章来源地址https://www.toymoban.com/news/detail-847983.html

到了这里,关于springboot3使用自定义注解+AOP+redis优雅实现防重复提交的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • redis + AOP + 自定义注解实现接口限流

    限流(rate limiting) ​ 是指在一定时间内,对某些资源的访问次数进行限制,以避免资源被滥用或过度消耗。限流可以防止服务器崩溃、保证用户体验、提高系统可用性。 限流的方法有很多种,常见的有以下几种: 漏桶算法: ​漏桶算法通过一个固定大小的漏桶来模拟流量

    2024年02月03日
    浏览(22)
  • springboot aop 自定义注解形式

    2024年01月25日
    浏览(28)
  • SpringBoot3自动配置流程 SPI机制 核心注解 自定义starter

    导入 starter 依赖导入 autoconfigure 寻找类路径下 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件 启动,加载所有 自动配置类 xxxAutoConfiguration 给容器中配置功能 组件 组件参数 绑定到 属性类 中。 xxxProperties 属性类 和 配置文件 前缀项绑定 @Contional 派生的条件

    2024年02月16日
    浏览(31)
  • SpringBoot + 自定义注解 + AOP 打造通用开关

    前言 最近在工作中迁移代码的时候发现了以前自己写的一个通用开关实现,发现挺不错,特地拿出来分享给大家。 为了有良好的演示效果,我特地重新建了一个项目,把核心代码提炼出来加上了更多注释说明,希望xdm喜欢。 案例 1、项目结构 2、引入依赖 3、yml配置 连接Re

    2024年01月23日
    浏览(31)
  • SpringBoot+自定义注解+AOP高级玩法打造通用开关

    1.项目结构 2.引入依赖 3.yml配置 4.自定义注解 5.定义常量 6.AOP核心实现 7.使用注解 8.工具类 9.测试接口 10.Redis中把开关加上 11.启动服务 将redis中开关置为1 欢迎大家积极留言交流学习心得,点赞的人最美丽!

    2024年02月07日
    浏览(26)
  • 【SpringMVC】自定义注解与AOP结合使用

    目录 一、SpringMVC之自定义注解 1.1 Java注解简介 1.2 为什么要用注解 1.3 注解的分类 ⭐ 1.3.1 JDK基本注解 1.3.2 JDK元注解  1.3.3 自定义注解  1.4 自定义注解三种使用案例 1.4.1 案例一(获取类与方法上的注解值) 1.4.2 案例二(获取类属性上的注解属性值) 1.4.3 案例三(获取参数

    2024年02月07日
    浏览(58)
  • 【Spring】使用自定义注解方式实现AOP鉴权

    AOP,是一种面向切面编程,可以通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 在软件开发中,鉴权(Authentication)是一项非常重要的安全措施,用于验证用户身份和权限。在应用程序中,我们通常会使用AOP(Aspect-Oriented Programming)来实现鉴权功能

    2024年02月11日
    浏览(37)
  • 【数据脱敏方案】不使用 AOP + 注解,使用 SpringBoot+YAML 实现

    在项目中遇到一个需求,需要对交易接口返回结果中的指定字段进行脱敏操作,但又不能使用 AOP+注解 的形式,于是决定使用一种比较笨的方法: 首先将所有需要脱敏字段及其对应脱敏规则存储到 Map 中。 在接口返回时,遍历结果中的所有字段,判断字段名在 Map 中是否存在

    2024年03月15日
    浏览(74)
  • SpringBoot定义拦截器+自定义注解+Redis实现接口防刷(限流)

    在拦截器Interceptor中拦截请求 通过地址+请求uri作为调用者访问接口的区分在Redis中进行计数达到限流目的 定义参数 访问周期 最大访问次数 禁用时长 代码实现 定义拦截器:实现HandlerInterceptor接口,重写preHandle()方法 注册拦截器:配置类实现WebMvcConfigurer接口,重写addIntercep

    2024年02月05日
    浏览(42)
  • SpringBoot3集成Kafka优雅实现信息消费发送

           首先,你的JDK是否已经是8+了呢?        其次,你是否已经用上SpringBoot3了呢?        最后,这次分享的是SpringBoot3下的kafka发信息与消费信息。        这次的场景是springboot3+多数据源的数据交换中心(数仓)需要消费Kafka里的上游推送信息,这里做数据

    2024年02月02日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包