redis分布式锁+aop防重复提交

之前有记录一篇用redis+拦截器防重复提交的内容:
redis+拦截器防重复提交

1.防重复提交注解


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {
    
    

    /**
     * 设置请求锁定时间
     *
     * @return
     */
    int lockTime() default 10;

}

2.redis分布式锁


/**
 * Redis 分布式锁实现
 */
@Service
public class RedisLock {
    
    

    private static final Long RELEASE_SUCCESS = 1L;
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    // 当前设置 过期时间单位, EX = seconds; PX = milliseconds
    private static final String SET_WITH_EXPIRE_TIME = "EX";
    // if get(key) == value return del(key)
    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 StringRedisTemplate redisTemplate;

    /**
     * 该加锁方法仅针对单实例 Redis 可实现分布式加锁
     * 对于 Redis 集群则无法使用
     * <p>
     * 支持重复,线程安全
     *
     * @param lockKey  加锁键
     * @param clientId 加锁客户端唯一标识(采用UUID)
     * @param seconds  锁过期时间
     * @return
     */
    public boolean tryLock(String lockKey, String clientId, int seconds) {
    
    
        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
    
    
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            String result = jedis.set(lockKey, clientId, SetParams.setParams().nx().ex(seconds));
            if (LOCK_SUCCESS.equals(result)) {
    
    
                return true;
            }
            return false;
        });
    }
//redisTemplate.execute的源码:
//	public <T> T execute(RedisCallback<T> action) {
    
    
//		return execute(action, isExposeConnection());
	//}

    /**
     * 与 tryLock 相对应,用作释放锁
     *
     * @param lockKey
     * @param clientId
     * @return
     */
    public boolean releaseLock(String lockKey, String clientId) {
    
    
        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
    
    
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey),
                    Collections.singletonList(clientId));
            if (RELEASE_SUCCESS.equals(result)) {
    
    
                return true;
            }
            return false;
        });
    }
}

3.防止重复提交Aop

//yixiangshangcheng
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
    
    


    @Autowired
    private RedisLock redisLock;

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

    @Around("pointCut(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
    
    
        int lockSeconds = noRepeatSubmit.lockTime();

        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        Assert.notNull(request, "request can not null");

        String bearerToken = request.getHeader("Authorization");
        String[] tokens = bearerToken.split(" ");
        String token = tokens[1];
        String path = request.getServletPath();
        String key = getKey(token, path);
        String clientId = getClientId();

        boolean isSuccess = redisLock.tryLock(key, clientId, lockSeconds);
        log.info("tryLock key = [{}], clientId = [{}]", key, clientId);

        if (isSuccess) {
    
    
            log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
            // 获取锁成功
            Object result;

            try {
    
    
                // 执行进程
                result = pjp.proceed();
            } finally {
    
    
                // 解锁
                redisLock.releaseLock(key, clientId);
                log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
            }

            return result;

        } else {
    
    
            // 获取锁失败,认为是重复提交的请求
            log.info("tryLock fail, key = [{}]", key);
            ///return  ApiResult.fail("重复请求,请稍后再试");
            throw new BadRequestException("重复请求,请稍后再试");
        }

    }

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

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

}

之后在Contoller方法上加 @NoRepeatSubmit即可。

猜你喜欢

转载自blog.csdn.net/qq_41358574/article/details/121882538