springboot实现后端防重复提交(AOP+redis分布式锁)单机情况下

这篇具有很好参考价值的文章主要介绍了springboot实现后端防重复提交(AOP+redis分布式锁)单机情况下。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


为什么要实现这个功能呢,可能用户在提交一份数据后,可能因为网络的原因、处理数据的速度慢等原因导致页面没有及时将用户刚提交数据的后台处理结果展示给用户,这时用户可能会进行如下操作:
  1. 1秒内连续点击提交按钮,导致重复提交表单。
  2. 使用浏览器后退按钮重复之前的操作,导致重复提交表单
    数据库中会存在大量重复的信息。

怎么实现呢:进入正题👇

0、依赖

		<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>9.0.21</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>2.6.3</version>
        </dependency>

1、自定义接口

/**
 * 自定义注解防止表单重复提交
 * @author Yuan Haozhe
 */
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
	 /**
     * 防重复操作过期时间,默认1s
     */
    long expireTime() default 1;
}

2、实现redis分布式锁

@Component
public class RedisLock {
	@Autowired
    private StringRedisTemplate redisTemplate;
	
	 /**
     * 该加锁方法仅针对单实例 Redis 可实现分布式加锁
     * 对于 Redis 集群则无法使用
     *
     * 支持重复,线程安全
     *
     * @param lockKey   加锁键
     * @param clientId  加锁客户端唯一标识(采用UUID)
     * @param seconds   锁过期时间
     * @return
     */
    public boolean tryLock(String lockKey, String clientId, long seconds) {
        //
        return redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, seconds, TimeUnit.SECONDS);
    }


}

对参数进行介绍:
第一个为key,我们使用key来当锁,因为key是唯一的。我们传的是lockKey
第二个为value,通过给value赋值为clientId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。clientId可以使用UUID.randomUUID().toString()方法生成。
第三个为time,代表key的过期时间
第四个为time的单位
setIfAbsent 相当于NX,设置过期时间相当于EX

也可用Jedis实现:

  1. 添加依赖:
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
  1. 加锁代码
	private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    @Autowired
    private Jedis jedis;
    
	public boolean tryLock(String lockKey, String clientId, long seconds) {
        String result = jedis.set(lockKey, clientId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, seconds);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
    /**
     * 与 tryLock 相对应,用作释放锁
     *
     * @param lockKey
     * @param clientId
     * @return
     */
    public boolean releaseLock(String lockKey, String clientId) {
    	//这里使用Lua脚本的方式,尽量保证原子性。
       return jedis.eval(RELEASE_LOCK_SCRIPT,Collections.singletonList(lockKey),Collections.singletonList(clientId)).equals(1L);
        
    }

对参数进行介绍:
第一个为key,我们使用key来当锁,因为key是唯一的。我们传的是lockKey
第二个为value,通过给value赋值为clientId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。clientId可以使用UUID.randomUUID().toString()方法生成。
第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
第五个为time,与第四个参数相呼应,代表key的过期时间。

可以看到,我们的加锁就一行代码,保证了原子性,总的来说,执行上面的set()方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。

3、统一返回值ReturnT

public class ReturnT<T> implements Serializable {
	public static final long serialVersionUID = 42L;

	public static final int SUCCESS_CODE = 200;
	public static final int FAIL_CODE = 500;
	public static final ReturnT<String> SUCCESS = new ReturnT<String>(null);
	public static final ReturnT<String> FAIL = new ReturnT<String>(FAIL_CODE, null);
	
	private int code;
	private String msg;
	private T data;
	
	public ReturnT(int code, String msg) {
		this.code = code;
		this.msg = msg;
	}
	public ReturnT(T data) {
		this.code = SUCCESS_CODE;
		this.data = data;
	}
	
	public int getCode() {
		return code;
	}
	public void setCode(int code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
	public T getData() {
		return data;
	}
	public void setData(T data) {
		this.data = data;
	}

}

4、CookieUtil

public class CookieUtil {

/**
	 * 查询value
	 *
	 * @param request
	 * @param key
	 * @return
	 */
	public static String getValue(HttpServletRequest request, String key) {
		Cookie cookie = get(request, key);
		if (cookie != null) {
			return cookie.getValue();
		}
		return null;
	}


}

5、自定义AOP

@Component
@Aspect
@Slf4j
public class NoRepeatSubmitAspect {
    @Pointcut("@annotation(com.xxl.sso.base.annotation.RepeatSubmit)")
    public void repeatSubmit(){}

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private RedisLock redisLock;


    @Around("repeatSubmit()")
    public ReturnT around(ProceedingJoinPoint joinPoint) {
        log.info("校验重复提交");
        //用户的身份标识,这里用cookie只是方便做测试
        String userId = CookieUtil.getValue(request, Conf.SSO_SESSIONID).split("_")[0];
        String key = getKey(userId, request.getServletPath());
        String clientId = getClientId();
        
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        // 获取防重复提交注解
        RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
        
        boolean isSuccess = redisLock.tryLock(key, clientId, annotation.expireTime());
        // 如果缓存中有这个url视为重复提交
        if (isSuccess) {
            Object result = null;
            try {
                result = joinPoint.proceed();

            } catch (Throwable e) {
                log.error(e.getMessage());
            }
            //finall{
            //redisLock.releaseLock(key, clientId)
            //}
            return ReturnT.SUCCESS;
        } else {
            log.error("重复提交");
            return ReturnT.FAIL;
        }
    }

    private String getKey(String token, String path) {
        return token + path;
    }

    private String getClientId() {
        return UUID.randomUUID().toString();
    }
}

6、测试

 //测试重复提交
    @GetMapping("/test-get")
    //添加RepeatSubmit注解,默认1s,方便测试查看,写3s
    @RepeatSubmit(expireTime = 3)
    public ReturnT repeatTest(){
        return ReturnT.SUCCESS;
    }

结果:
springboot实现后端防重复提交(AOP+redis分布式锁)单机情况下文章来源地址https://www.toymoban.com/news/detail-480003.html

到了这里,关于springboot实现后端防重复提交(AOP+redis分布式锁)单机情况下的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • springboot aop实现接口防重复操作

    一、前言 有时在项目开发中某些接口逻辑比较复杂,响应时间长,那么可能导致重复提交问题。 二、如何解决 1.先定义一个防重复提交的注解。 2.编写防重复操作的AOP 3.接下来定义redisService类 4.最后在Controller接口加上注解就行了。

    2024年02月11日
    浏览(37)
  • Java 解决重复提交问题和表单唯一标识的 Redis 缓存实现

    在开发 Web 应用程序时,重复提交和表单唯一标识是常见的问题。重复提交可能导致数据重复插入或重复操作,而表单唯一标识则用于确保每个表单提交都是唯一的。本文将介绍如何使用 Java 来解决这些问题,并结合 Redis 缓存提供实际的案例。 什么是重复提交问题? 重复提

    2024年02月11日
    浏览(36)
  • springboot自定义注解+aop+redis实现延时双删

    redis作为用的非常多的缓存数据库,在多线程场景下,可能会出现数据库与redis数据不一致的现象 数据不一致的现象:https://blog.csdn.net/m0_73700925/article/details/133447466 这里采用aop+redis来解决这个方法: 删除缓存 更新数据库 延时一定时间,比如500ms 删除缓存 这里之所以要延时一

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

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

    2024年02月05日
    浏览(39)
  • SpringBoot使用Redis实现分布式缓存

    ✅作者简介:2022年 博客新星 第八 。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏:SpringBoot 框架从入门到精通 ✨特色专栏:国学周更-心性养成之路 🥭本文内容:SpringBoot使用

    2023年04月09日
    浏览(29)
  • 基于 SpringBoot + Redis 实现分布式锁

    大家好,我是余数,这两天温习了下分布式锁,然后就顺便整理了这篇文章出来。 文末附有源码链接,需要的朋友可以自取。 至于什么是分布式锁,这里不做赘述,不了解的可以自行去查阅资料。 1. 使用 Redis 的 Setnx(SET if Not Exists) 命令加锁。 即锁不存在的时候才能加锁成功

    2024年02月05日
    浏览(45)
  • 使用Redis控制表单重复提交控制接口访问频率

    防重提交有很多方案,从前端的按钮置灰,到后端synchronize锁、Lock锁、借助Redis语法实现简单锁、Redis+Lua分布式锁、Redisson分布式锁,再到DB的悲观锁、乐观锁、借助表唯一索引等等都可以实现防重提交,以保证数据的安全性。 这篇文章我们介绍其中一种方案– 借助Redis语法实

    2024年02月09日
    浏览(28)
  • springboot3 redis 实现分布式锁

    分布式锁介绍 分布式锁是一种在分布式系统中用于控制不同节点上的进程或线程对共享资源进行互斥访问的技术机制。 在分布式环境中,多个服务可能同时访问和操作共享资源,如数据库、文件系统等。为了保持数据的一致性和完整性,需要确保在同一时刻只有一个服务能

    2024年04月16日
    浏览(23)
  • springBoot防止重复提交

    两种方法, 一种是后端实现,较复杂,要通过自定义注解和AOP以及Redis组合实现 另一种是前端实现,简单,只需通过js,设置过期时间,一定时间内,多次点击按钮只生效一次 自定义注解+AOP+Redis 自定义异常类和全局异常处理 自定义异常类:CustomException 全局异常处理:Cust

    2024年02月11日
    浏览(28)
  • springboot防重复提交

    1、场景 网页卡顿的时候,用户点击会造成重复操作 如果前端不做防重复操作。会导致重复提交,重复下单等意外操作。而且对于系统资源来说也是一种浪费 常规的解决方法是让前端把点击后的按钮设置为不可点击,这样基本上能就能解决了。99.999999%能解决。前端这么弄过

    2024年02月09日
    浏览(19)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包