springboot aop实现接口防重复操作

这篇具有很好参考价值的文章主要介绍了springboot aop实现接口防重复操作。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言
有时在项目开发中某些接口逻辑比较复杂,响应时间长,那么可能导致重复提交问题。

二、如何解决
1.先定义一个防重复提交的注解。

import java.lang.annotation.*;

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {

    /**
     * 防重复操作限时标记数值(存储redis限时标记数值)
     */
    String value() default "value" ;

    /**
     * 防重复操作过期时间(借助redis实现限时控制)
     */
    int expireSeconds() default 10;
}

2.编写防重复操作的AOP

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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;

@Slf4j
@Component
@Aspect
@Order(0)
public class NoRepeatSubmitAspect  {
 private static final String TOKENAuthorization = "Authorization";

    private static final String TOKENUSERNAME = "api-userName";

    private static final String PREVENT_DUPLICATION_PREFIX = "PREVENT_DUPLICATION_PREFIX:";

    @Autowired
    private RedisService redisService;

    @Pointcut("@annotation(com.dp.aop.annotation.RepeatSubmit)")
    public void preventDuplication() {}
    @Around("preventDuplication()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        if (Objects.isNull(request)) {
            return joinPoint.proceed();
        }
        //获取执行方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //获取防重复提交注解
        RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
        //获取token以及方法标记,生成redisKey
        String header = request.getHeader(TOKENAuthorization);
        String token = header == null ? "" : header;
        String requestHeader = request.getHeader(TOKENUSERNAME);
        String headerToken = requestHeader == null ? "" : requestHeader;
        token = token + headerToken;
        String url = request.getRequestURI();
        // 通过前缀 + url + token + 函数参数签名 来生成redis上的 key
        String redisKey = PREVENT_DUPLICATION_PREFIX
                .concat(url)
                .concat(token)
                .concat(getMethodSign(method, joinPoint.getArgs()));
        RedisLock redisLock = null;
        try {
            try {
                redisLock = redisService.tryLock(redisKey, annotation.expireSeconds());
            } catch (Exception e) {
                log.error("tryLock error  ", e);
                throw new BizException(CommonMsgConstants.NoRepeatSubmitMsg);
            }
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            log.error("throwable trace is ", throwable);
            throw new RuntimeException(throwable);
        } finally {
            if (Objects.nonNull(redisLock)) {
                redisLock.unlock();
            }
        }
    }
    /**
     * 生成方法标记:采用数字签名算法SHA1对方法签名字符串加签
     *
     * @param method
     * @param args
     * @return
     */
    private String getMethodSign(Method method, Object... args) {
        StringBuilder sb = new StringBuilder(method.toString());
        for (Object arg : args) {
            sb.append(toString(arg));
        }
        return DigestUtil.sha1Hex(sb.toString());
    }

    private String toString(Object arg) {
        if (Objects.isNull(arg)) {
            return "null";
        }
        if (arg instanceof Number) {
            return arg.toString();
        }
        return JSONObject.toJSONString(arg);
    }

}

3.接下来定义redisService类

@Component
public class RedisService {
	public RedisLock tryLock(String lockKey, int expireTime) {
        String lockValue = UUID.randomUUID().toString();
        Boolean hasLock = (Boolean)this.redisTemplate.execute((connection) -> {
            Object nativeConnection = connection.getNativeConnection();
            String status = null;
            if (nativeConnection instanceof Jedis) {
                Jedis jedis = (Jedis)nativeConnection;
                status = jedis.set(lockKey, lockValue, "nx", "ex", expireTime);
            } else {
                JedisCluster jedisx = (JedisCluster)nativeConnection;
                status = jedisx.set(lockKey, lockValue, "nx", "ex", (long)expireTime);
            }

            return "OK".equals(status);
        });
        if (hasLock) {
            return new RedisService.RedisLockInner(this.redisTemplate, lockKey, lockValue);
        } else {
            throw new RuntimeException("获取锁失败,lockKey:" + lockKey);
        }
    }
 private class RedisLockInner implements RedisLock {
        private RedisTemplate redisTemplate;
        private String key;
        private String expectedValue;

        protected RedisLockInner(RedisTemplate redisTemplate, String key, String expectedValue) {
            this.redisTemplate = redisTemplate;
            this.key = key;
            this.expectedValue = expectedValue;
        }

       public Object unlock() {
            final List<String> keys = new ArrayList();
            keys.add(this.key);
            final List<String> values = new ArrayList();
            values.add(this.expectedValue);
            Object result = this.redisTemplate.execute(new RedisCallback<Long>() {
                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    Object nativeConnection = connection.getNativeConnection();
                    return nativeConnection instanceof JedisCluster ? (Long)((JedisCluster)nativeConnection).eval("if redis.call('get',KEYS[1])==ARGV[1]\n then\n   return redis.call('del',KEYS[1])\n else\n   return 0\n end", keys, values) : (Long)((Jedis)nativeConnection).eval("if redis.call('get',KEYS[1])==ARGV[1]\n then\n   return redis.call('del',KEYS[1])\n else\n   return 0\n end", keys, values);
                }
            });
           return result;
        }

        public void close() throws Exception {
            this.unlock();
        }
    }
}

4.最后在Controller接口加上注解就行了。文章来源地址https://www.toymoban.com/news/detail-669062.html

到了这里,关于springboot aop实现接口防重复操作的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • Spring Boot入门(23):基于AOP实现自定义注解拦截接口日志并保存入库 | 超级详细,建议收藏

    Spring Boot入门(23):基于AOP实现自定义注解拦截接口日志并保存入库 | 超级详细,建议收藏

            在上两期中,我们着重介绍了如何集成使用 Logback 与 log4j2 日志框架的使用,今天我们讲解的主题依旧跟日志有关,不过不是使用何种开源框架,而是自己动手造。         Spring的核心之一AOP;AOP翻译过来叫面向切面编程, 核心就是这个切面. 切面表示从业务逻辑中

    2024年02月11日
    浏览(9)
  • 【springboot】spring的Aop结合Redis实现对短信接口的限流

    【springboot】spring的Aop结合Redis实现对短信接口的限流

    场景: 为了限制短信验证码接口的访问次数,防止被刷,结合Aop和redis根据用户ip对用户限流 首先我们创建一个 Spring Boot 工程,引入 Web 和 Redis 依赖,同时考虑到接口限流一般是通过注解来标记,而注解是通过 AOP 来解析的,所以我们还需要加上 AOP 的依赖,最终的依赖如下:

    2024年02月05日
    浏览(7)
  • spring boot 使用AOP+自定义注解+反射实现操作日志记录修改前数据和修改后对比数据,并保存至日志表

    spring boot 使用AOP+自定义注解+反射实现操作日志记录修改前数据和修改后对比数据,并保存至日志表

    使用FieldMeta自定义注解,看个人业务自行觉得是否需要重新定义实体 实现类 :通过该实现类获取更新前后的数据。 该实现类的实现原理为:获取入参出入的id值,获取sqlSessionFactory,通过sqlSessionFactory获取selectByPrimaryKey()该方法,执行该方法可获取id对应数据更新操作前后的数

    2024年01月23日
    浏览(12)
  • Spring Boot + Aop 记录用户操作日志

    Spring Boot + Aop 记录用户操作日志

    本文主要介绍通过Aop记录用户操作日志,这也是目前比较常用的用法,由于水平有限,所以可能存在错漏之处,望指正。 对应实体类为 SysOperLog.java MyLog.java BusinessType.java — 操作类型枚举类 LogAspect.java 用户操作日志是AOP最常见的一种业务场景,这里只是简单记录了少量信息,

    2024年02月06日
    浏览(14)
  • Spring Boot入门(23):记录接口日志再也不难!用AOP和自定义注解给Spring Boot加上日志拦截器!

    Spring Boot入门(23):记录接口日志再也不难!用AOP和自定义注解给Spring Boot加上日志拦截器!

            在上两期中,我们着重介绍了如何集成使用 Logback 与 log4j2 日志框架的使用,今天我们讲解的主题依旧跟日志有关,不过不是使用何种开源框架,而是自己动手造。         Spring的核心之一AOP;AOP翻译过来叫面向切面编程, 核心就是这个切面. 切面表示从业务逻辑中

    2024年02月11日
    浏览(9)
  • springboot3使用自定义注解+AOP+redis优雅实现防重复提交

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

      ⛰️个人主页:     蒾酒 🔥 系列专栏 :《spring boot实战》 🌊 山高路远,行路漫漫,终有归途 目录 写在前面 实现思路 实现步骤 1.定义防重复提交注解 2.编写一个切面去发现该注解然后执行防重复提交逻辑 3.测试 依赖条件 1.接口上标记防重复提交注解 2.接口测试 写在最

    2024年04月11日
    浏览(9)
  • 使用Spring Boot AOP实现日志记录

    使用Spring Boot AOP实现日志记录

    目录 介绍 1.1 什么是AOP 1.2 AOP体系与概念 AOP简单实现 2.1 新建一个SpringBoot项目,无需选择依赖 2.2 设置好本地Maven配置后,在pom.xml文件里添加添加maven依赖 2.3 创建一个业务类接口 2.4 在实体类实现接口业务  2.5 在单元测试运行结果 2.6 创建切面类 2.7 再次运行测试  总结 1.

    2024年02月14日
    浏览(14)
  • spring boot 使用AOP实现是否已登录检测

            前后端分离的开发中,用户http请求应用服务的接口时, 如果要求检测该用户是否已登录。可以实现的方法有多种, 本示例是通过aop 的方式实现,简单有效。         约定:前端http的post 请求 1、在pom.xml 引用 2、创建插入标记 3、实现切入类 4 建立api接口,在需要检

    2024年02月20日
    浏览(15)
  • Spring Boot学习随笔- 实现AOP(JoinPoint、ProceedingJoinPoint、自定义注解类实现切面)

    Spring Boot学习随笔- 实现AOP(JoinPoint、ProceedingJoinPoint、自定义注解类实现切面)

    学习视频:【编程不良人】2021年SpringBoot最新最全教程 问题 现有业务层开发存在问题 额外功能代码存在大量冗余 每个方法都需要书写一遍额外功能代码不利于项目维护 Spring中的AOP AOP:Aspect 切面 + Oriented 面向 Programmaing 面向切面编程 Aspect(切面) = Advice(通知) + Pointcut(

    2024年02月04日
    浏览(21)
  • 三方开放接口,Springboot通过AOP实现API接口的签名验证

    前言 对外开放的接口,需要验证请求方发送过来的数据确实是由发送方发起的,并且中途不能被篡改和伪造,所以才会对接口的访问进行签名验证,以保证双方获取到的原来的信息是没有经过篡改的。 实现方法 对请求的信息内容,通过MD5运算或者其他算法(必须是 不可逆的

    2024年02月07日
    浏览(15)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包