使用注解+aop+redis分布式锁解决方法幂等性问题

1.redis工具类

import lombok.extern.slf4j.Slf4j;
import net.logstash.logback.encoder.org.apache.commons.lang.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class RedisUtil {
    //根据项目名端口号获取项目级别的key前缀
    @Value("${spring.redis.projectKeyPrefix}")
    public String projectKeyPrefix;
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    /**
     * 成功标志
     */
    private static final long SUCCESS = 1L;

    /**
     * 默认过期时间(60s)
     */
    private static final long DEFAULT_EXPIRE = 60L;

    /**
     * 释放锁脚本,原子操作,lua脚本
     */
    private static final String UNLOCK_LUA = "if redis.call('get',KEYS[1]) == ARGV[1] then" +
            "   return redis.call('del',KEYS[1]) " +
            "else" +
            "   return 0 " +
            "end";

    /** -------------------分布式锁相关操作------------------------- */

    /**
     * 获取分布式锁
     * @param lockKey    锁
     * @param requestId  唯一ID
     * @return
     */
    public boolean tryLock(String lockKey, String requestId){
        return tryLock(lockKey, requestId, DEFAULT_EXPIRE);
    }

    /**
     * 获取分布式锁
     * @param lockKey    锁
     * @param requestId  唯一ID
     * @param expireTime 过期时间(默认单位秒)
     * @return
     */
    public  boolean tryLock(String lockKey, String requestId, long expireTime){
        Boolean result = Boolean.FALSE;
        try {
            result = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);
        }catch (Exception e){
            log.error("try lock lockKey [{}], requestId [{}], expireTime [{}]  error {}", lockKey, requestId,
                    expireTime, ExceptionUtils.getStackTrace(e));
        }
        log.info("try lock lockKey [{}], requestId [{}], result is [{}]", lockKey, requestId, result);
        return Optional.ofNullable(result).orElse(Boolean.FALSE);
    }

    /**
     * 释放锁
     * @param lockKey   锁
     * @param requestId 唯一ID
     */
    public void unlock(String lockKey, String requestId) {
        Boolean result = Boolean.FALSE;
        DefaultRedisScript<Long> unlockScript = new DefaultRedisScript<>(UNLOCK_LUA, Long.class);
        Long rs = redisTemplate.execute(unlockScript, Collections.singletonList(lockKey), requestId);
        if(SUCCESS == rs){
            result = Boolean.TRUE;
        }
        log.info("unlock lockKey [{}], requestId [{}] result is [{}]", lockKey, requestId, result);
    }

    /**
     * 设置Hash类型缓存值
     * @param key
     * @param hashKey
     * @param value
     * @return
     */
    public boolean hPut(String key, Object hashKey, Object value) {
        try {
            redisTemplate.opsForHash().put(key, hashKey, value);
            return true;
        }catch (Exception e){
            log.error("redis set hash value [key={}] error, {}", key, ExceptionUtils.getStackTrace(e));
            return false;
        }
    }

    /**
     * 查看哈希表 key 中,指定的字段是否存在
     *
     * @param key
     * @param hashKey
     * @return
     */
    public boolean hExists(String key, Object hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    /**
     * 删除一个或多个哈希表字段
     *
     * @param key
     * @param hashKeys
     * @return
     */
    public Long hDelete(String key, Object... hashKeys) {
        return redisTemplate.opsForHash().delete(key, hashKeys);
    }

    /**
     * 获取所有给定字段的值
     *
     * @param key
     * @return
     */
    public Map<?, ?> hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 设置Hash类型多个缓存值
     * @param key
     * @param maps
     * @return
     */
    public boolean hPutAll(String key, Map<?, ?> maps) {
        try {
            redisTemplate.opsForHash().putAll(key, maps);
            return true;
        }catch (Exception e){
            log.error("redis set hash values [key={}] error, {}", key, ExceptionUtils.getStackTrace(e));
            return false;
        }
    }

    /**
     * 设置Hash类型多个缓存值及设置过期时间
     * @param key
     * @param maps
     * @return
     */
    public boolean hPutAllAndExpire(String key, Map<?, ?> maps, Duration duration) {
        try {
            redisTemplate.opsForHash().putAll(key, maps);
            redisTemplate.expire(key, duration);
            return true;
        }catch (Exception e){
            log.error("redis set hash values [key={}] error, {}", key, ExceptionUtils.getStackTrace(e));
            return false;
        }
    }

    /**
     * 获取递增id(步长=1)
     * @param key
     * @return
     */
    public long generateId(String key){
        return generateId(key,1L);
    }

    /**
     * 获取指定步长递增id
     * @param key
     * @param step
     * @return
     */
    public long generateId(String key, long step){
        RedisAtomicLong atomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        return atomicLong.addAndGet(step);
    }

    /**
     * 获取递增id,并设置过期时间(步长=1)
     * @param key
     * @param expireTime
     * @return
     */
    public long generateId(String key, Date expireTime){
        return generateId(key, expireTime, 1L);
    }

    /**
     * 获取指定步长递增id,并设置过期时间
     * @param key
     * @param step
     * @param expireTime
     * @return
     */
    public long generateId(String key, Date expireTime, long step){
        RedisAtomicLong atomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        atomicLong.expireAt(expireTime);
        return atomicLong.addAndGet(step);
    }

    public boolean delete(String key) {
        return redisTemplate.delete(key);
    }


    /**
     * 设置指定 key 的值
     * @param key
     * @param value
     */
    public boolean set(String key, String value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        }catch (Exception e){
            log.error("redis set [key={}] failed ==> {}",key, ExceptionUtils.getStackTrace(e));
            return false;
        }
    }

    /**
     * 将值 value 关联到 key ,并将 key 的过期时间设为 timeout
     *
     * @param key
     * @param value
     * @param timeout 过期时间
     * @param unit 时间单位
     */
    public boolean setExpire(String key, String value, long timeout, TimeUnit unit) {
        try {
            redisTemplate.opsForValue().set(key, value, timeout, unit);
            return true;
        }catch (Exception e){
            log.error("redis set expire [key={}] failed ==> {}",key, ExceptionUtils.getStackTrace(e));
            return false;
        }
    }

    /**
     * 获取指定 key 的值
     * @param key
     * @return
     */
    public String get(String key) {
        Object value = redisTemplate.opsForValue().get(key);
        return value == null ? null : String.valueOf(redisTemplate.opsForValue().get(key));
    }

    /**
     * 添加元素,有序集合是按照元素的score值由小到大排列
     *
     * @param key
     * @param value
     * @param score
     * @return
     */
    public Boolean zAdd(String key, String value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     * 移除指定索引位置的成员
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Long zRemoveRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().removeRange(key, start, end);
    }

    /**
     * 获取集合大小
     *
     * @param key
     * @return
     */
    public Long zSize(String key) {
        return redisTemplate.opsForZSet().size(key);
    }

    /**
     * 获取集合的元素, 从大到小排序, 并返回score值
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeWithScores(String key, long start, long end) {
        return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
    }

    /**
     * set集合添加值
     *
     * @param key
     * @param values
     * @return
     */
    public Long sAdd(String key, String... values) {
        return redisTemplate.opsForSet().add(key, values);
    }

    /**
     * set集合删除值
     *
     * @param key
     * @param values
     * @return
     */
    public boolean sRemove(String key, String... values) {
        return redisTemplate.opsForSet().remove(key,values)>0;
    }

    /**
     * set集合获取所有值
     *
     * @param key
     * @return
     */
    public Set<Object> sMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * zset有序集合添加值及计数+1
     *
     * @param key
     * @param value
     * @return
     */
    public double zIncrementScore(String key, String value) {
        return redisTemplate.opsForZSet().incrementScore(key, value, 1d);
    }

    /**
     * zset有序集合查询前几
     *
     * @param key
     * @param top 前几
     * @return
     */
    public Set<Object> zReverseRange(String key, long top) {
        return redisTemplate.opsForZSet().reverseRange(key, 0L, top);
    }
}

2.定义注解

/**
 * 不允许并发执行注解
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DisallowConcurrentExecution {
}

3.创建切面类,指定切点实现环绕通知

@Slf4j
@Aspect
@Component
public class DisallowConcurrentExecutionAspect {
    @Autowired
    private RedisUtil redisUtil;

    @Pointcut("@annotation(disallowConcurrentExecution)||@within(disallowConcurrentExecution)")
    private void cutMethod(DisallowConcurrentExecution disallowConcurrentExecution){
    }

    @Around("cutMethod(disallowConcurrentExecution)")
    private Object around(ProceedingJoinPoint point,DisallowConcurrentExecution disallowConcurrentExecution) throws Throwable {
        //获取方法名
        String methodName = point.getSignature().getName();
        //获取类名
        String className = point.getTarget().getClass().getSimpleName();
        //获取分布式锁
        String key = StringUtils.join(redisUtil.projectKeyPrefix, className,"_", methodName);
        boolean success = redisUtil.tryLock(key, "当前用户唯一标识", 3L);
        if (!success) {
            throw new BizException("当前用户正在执行该操作,请勿重复提交");
        }
        //执行目标方法
        Object result = point.proceed();
        redisUtil.unlock(key, "当前用户唯一标识");
        log.info("[{}] unlock redis lock success <---", key);
        return result;
    }
}

4.使用

在需要解决幂等性问题的方法上面添加注解即可

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值