使用注解和redis实现防止接口重复提交

该文章介绍了一个使用自定义注解和SpringAOP来防止接口重复提交的解决方案。通过添加特定注解到方法上,然后创建一个切面类进行环绕通知,利用Redis作为临时存储来锁定请求,确保在设定的时间内同一请求不会被执行多次。
摘要由CSDN通过智能技术生成

第一步自定义注解,如下所示:

添加aop相关依赖

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
@Target(ElementType.METHOD)//作用于方法上
@Retention(RetentionPolicy.RUNTIME)//运行时
public @interface NoRepeatSubmit {

    /**
     * 设置请求锁定时间  默认5秒
     */
    int lockTime() default 3;
    /**
     * 当发生重复提交时候默认返回的错误信息
     */
    String errMsg() default "重复提交,请  second 秒后重试";
}

 第二步,基于spring aop原理,给加了此注解的接口做环绕通知,切面类如下:

package com.donlim.fms.common.aop;

import com.donlim.fms.annotation.NoRepeatSubmit;
import com.donlim.fms.exception.OutVO;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
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.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
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;

/**
 * @author 李安山
 * @date 2023/6/1
 * @version 1.0
 */


@Slf4j
@Aspect
@Component
public class RepeatSubmitAspect {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Pointcut("@annotation(noRepeatSubmit)")
    public void pointcut(NoRepeatSubmit noRepeatSubmit) {
    }

    @Around("@annotation(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) throws Throwable {
        log.warn("AOP防重复提交设置的加锁时间:{}", noRepeatSubmit.lockTime());
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        // 请求的方法参数值  POST的JSON请求request.getParameter()是获取不到值的
        Object[] args = joinPoint.getArgs();
        JSONObject requestParams = ObjectUtils.isNotEmpty(args) ? JSONObject.fromObject(args[0]) : null;
        log.warn("AOP防重复提交[" + request.getRequestURI() + "]接口的请求参数:{}",requestParams);

        if (ObjectUtils.isNotEmpty(requestParams)){
           String key = requestParams.toString();
           log.info("key==》{}" ,key);
            String s = stringRedisTemplate.opsForValue().get(key);
            if (StringUtils.isNotEmpty(s)){
                return OutVO.repeat("重复提交!");
            }else {
                stringRedisTemplate.opsForValue().set(key,"1",noRepeatSubmit.lockTime(), TimeUnit.SECONDS);
                Object returnVale = joinPoint.proceed();
                return returnVale;
            }
        }
        return null;
    }

    /**
     * @MethodName: tryLock
     * @Param: [key --- key值, lockSeconds  时长]
     * @Return: boolean       是否获取到
     * @Author: Panguaxe
     * @Date: 2020-05-26 16:13
     * @Description: TODO          最终加强分布式锁
     */
    private boolean tryLock(String key, int lockSeconds) {
        //lambda表达式
        return (Boolean) stringRedisTemplate.execute((RedisCallback) connection -> {
            long expireAt = System.currentTimeMillis() + lockSeconds + 1;
            log.warn("失效时间:{}", expireAt);
            Boolean acquire = connection.setNX(key.getBytes(), String.valueOf(expireAt).getBytes());
            if (acquire) {
                return true;
            }
            byte[] value = connection.get(key.getBytes());
            if (Objects.nonNull(value) && value.length > 0) {
                if (Long.parseLong(new String(value)) < System.currentTimeMillis()) {
                    // 如果锁已经过期
                    byte[] oldValue = connection.getSet(key.getBytes(),String.valueOf(System.currentTimeMillis() + lockSeconds + 1).getBytes());
                    // 防止死锁
                    return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
                }
            }
            return false;
        });
    }
}

第三步:

给需要防止重复操作的接口上加上此注解即可

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值