springboot+redis+aop自定义注解进行接口限流幂等性校验


前言

本文使用到的技术有springboot springaop redis。


提示:以下是本篇文章正文内容,下面案例可供参考

一、引入aop以及redis依赖

<!--包含 spring-aop 和 AspectJ 来支持面向切面编程(AOP)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

二、自定义注解

自定义注解 当然如果所有接口都要进行限流的话就直接使用aop不需要自定义注解

/**
 * @author 宋中能
 * 用分布式锁实现接口幂等性校验
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IdempotenceAnnotation {
    /**
     * key类型1user 2ip
     * @return
     */
    int type() default 1;

    /**
     * 过期时间
     * @return
     */
    long expiration() default 1000L;

}


自定义aop

/**
 * @author szn
 */
@Aspect
@Component
//@Transactional
public class IdempotenceAnnotationImpl {
    private static final Logger logger= LoggerFactory.getLogger(RedisLock.class);
    @Resource
    private RedisTemplate<String,Object> redisTemplate;
    /**
     * 定义切入点为添加了自定如注解的方法
     */
    @Pointcut("@annotation(com.wenwei.sharepark.annotation.IdempotenceAnnotation)")
    public void idempotence() {
    }

    /**
     * 在方法执行之前使用redis分布式锁进行接口等幂性约束
     * @param joinPoint
     */
    @Around("idempotence()")
    public Object redisLock(ProceedingJoinPoint joinPoint) throws Throwable {

        //从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取切入点所在的方法
        Method method = signature.getMethod();
        StringBuffer lockKey=new StringBuffer();
        IdempotenceAnnotation idempotenceAnnotation=method.getAnnotation(IdempotenceAnnotation.class);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes()).getRequest();
        //使用username
        if(idempotenceAnnotation.type()==1){
            JwtUser jwtUser=(JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            lockKey.append(jwtUser.getUsername());
        }else {
            //使用ip
            lockKey.append(IpUtils.ipAddress(request));
        }
        lockKey.append("_");
        //方法名称
        lockKey.append(method.getName());
        lockKey.append("_idempotence");

        logger.info("redis分布式锁Key:"+lockKey.toString());
        //创建redis锁对象
        RedisLock redisLock=new RedisLock(redisTemplate,lockKey.toString(),false,idempotenceAnnotation.expiration());
        Object result;
        //获取锁
        if(redisLock.lockNoRetry()){
            try{
                 //执行方法
                 result=joinPoint.proceed();
            } finally{
                //释放锁
                redisLock.unlock();
            }
        }else {
            throw new ObjectException(250,"请勿重复提交!");
        }
        return result;
    }
}

rdis分布式锁工具类这里只是用了lockNoRetry()这个方法 另一个lockRetry()方法可以用来做商品抢购功能

**
 * @author : szn
 * Description : redisLock 并发锁工具类,提供了加锁和释放锁的方法,使用方法参考RedisTest.redisLock()方法
 */

public class RedisLock {
   private static final Logger logger=LoggerFactory.getLogger(RedisLock.class);

    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 重试时间
     */
    private static final long DEFAULT_ACQUIRY_RETRY_MILLIS = 100L;
    /**
     * 锁的后缀
     */
    private static final String LOCK_SUFFIX = "_redis_lock";
    /**
     * 锁的key
     */
    private String lockKey;
    /**
     * 锁超时时间,防止线程在入锁以后,防止阻塞后面的线程无法获取锁
     */
    private long expireMsecs = 60L * 1000L;
    /**
     * 线程获取锁的等待时间
     */
    private long timeoutMsecs = 10L * 1000L;
    /**
     * 是否锁定标志
     */
    private volatile boolean locked = false;

    /**
     * 构造器
     *
     * @param isAddUser 是否关联用户
     * @param lockKey   锁的key
     */
    public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey, Boolean isAddUser) {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey + LOCK_SUFFIX;

    }

    /**
     * 构造器
     *
     * @param redisTemplate
     * @param lockKey       锁的key
     * @param timeoutMsecs  获取锁的超时时间
     */
    public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey, Boolean isAddUser, long timeoutMsecs) {
        this(redisTemplate, lockKey, isAddUser);
        this.timeoutMsecs = timeoutMsecs;
    }

    /**
     * 构造器
     *
     * @param redisTemplate
     * @param lockKey       锁的key
     * @param timeoutMsecs  获取锁的超时时间
     * @param expireMsecs   锁的有效期
     */
    public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey, Boolean isAddUser, long timeoutMsecs, long expireMsecs) {
        this(redisTemplate, lockKey, isAddUser, timeoutMsecs);
        this.expireMsecs = expireMsecs;
    }

    public String getLockKey() {
        return lockKey;
    }

    /**
     * 获取值
     *
     * @param key
     * @return
     */
    private String get(final String key) {
        Object obj = redisTemplate.opsForValue().get(key);
        return obj != null ? obj.toString() : null;
    }

    /**
     * 存入值
     *
     * @param key
     * @param value
     * @return
     */
    private boolean setNX(final String key, final String value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 设置值并返回旧值
     *
     * @param key
     * @param value
     * @return
     */
    private String getSet(final String key, final String value) {
        Object obj = redisTemplate.opsForValue().getAndSet(key, value);
        return obj != null ? (String) obj : null;
    }

    /**
     * 获取锁,获取失败的话过100ms重试,总超时时间 10 * 1000 ms
     *
     * @return 获取锁成功返回ture,超时返回false
     * @throws InterruptedException
     */
    public  boolean lockRetry() {
        long timeout = timeoutMsecs;
        while (timeout >= 0) {
            if(this.lock()){
                return true;
            }
            timeout -= DEFAULT_ACQUIRY_RETRY_MILLIS;
            // 延时
            try {
                Thread.sleep(DEFAULT_ACQUIRY_RETRY_MILLIS);
            } catch (InterruptedException e) {

                logger.error("延时出错",e);
            }
        }
      return false;
    }

    /**
     * 获取锁,不重试,只获取一次,获取不到就返回失败
     *
     * @return 获取锁成功返回ture,失败返回false
     * @throws InterruptedException
     */
    public  boolean lockNoRetry() {
        return this.lock();
    }

    /**
     * 获取锁公共方法
     * @return
     */
    private  boolean lock(){
        long expires = System.currentTimeMillis() + expireMsecs + 1;
        // 锁到期时间
        String expiresStr = String.valueOf(expires);
        if (this.setNX(lockKey, expiresStr)) {
            locked = true;
            return true;
        }
        // redis里key的时间
        String currentValue = this.get(lockKey);
        // 判断锁是否已经过期,过期则重新设置并获取
        if (currentValue != null && Long.parseLong(currentValue) < System.currentTimeMillis()) {
            // 设置锁并返回旧值
            String oldValue = this.getSet(lockKey, expiresStr);
            // 比较锁的时间,如果不一致则可能是其他锁已经修改了值并获取
            if (oldValue != null && oldValue.equals(currentValue)) {
                locked = true;
                return true;
            }
        }
        return false;
    }
    /**
     * 释放获取到的锁
     */
    public synchronized void unlock() {
        if (locked) {
            redisTemplate.delete(lockKey);
            locked = false;
        }
    }

}

在接口上添加注解

	//因为设置了默认值所以不用设置参数就会使用默认值
    @IdempotenceAnnotation
    @ApiOperation(value = "收藏和取消收藏", notes = "参数>parkId")
    @PostMapping(value = "/app/favoriteAndUnFavorite")
    public ObjectResult<Object> favoriteAndUnFavorite(@RequestBody SpUserCollectionPark userCollectionPark) {
        userCollectionPark.setUserId(getCurrentUserId());
        spUserCollectionParkService.favoriteAndUnFavorite(userCollectionPark);
        return ObjectResultUtil.success("操作成功");
    }

三、效果

快速点击执行
在这里插入图片描述
控制台输出
在这里插入图片描述
过期时间和rediskey的生成方式都可以自行定义由于接口执行时间不足一秒所以还没有到一秒钟 就已经释放锁了 连续点击下还是成功了几个。如果需要所有接口都进行校验就不需要自定义注解 修改aop的切入点就好了

   /**
     * 定义切入点为添加了自定如注解的方法
     */
    // 注解方式切入@Pointcut("@annotation(com.wenwei.sharepark.annotation.IdempotenceAnnotation)")
    //请自行根据路径匹配
    @Pointcut("execution(public * com.wenwei.sharepark.controller.*..*(..))")
    public void idempotence() {
    }

有什么不懂可以在评论区探讨或者私信我

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用 Redisson 实现分布式锁,具体实现如下: 1. 引入 Redisson 依赖: ```xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.16.1</version> </dependency> ``` 2. 定义自定义注解 `DistributedLock`: ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DistributedLock { String value() default ""; long leaseTime() default 30L; TimeUnit timeUnit() default TimeUnit.SECONDS; } ``` 3. 在需要加锁的方法上加上 `@DistributedLock` 注解: ```java @Service public class UserService { @DistributedLock("user:#{#userId}") public User getUserById(String userId) { // ... } } ``` 4. 实现 `DistributedLockAspect` 切面: ```java @Aspect @Component public class DistributedLockAspect { private final RedissonClient redissonClient; public DistributedLockAspect(RedissonClient redissonClient) { this.redissonClient = redissonClient; } @Around("@annotation(distributedLock)") public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable { String lockKey = distributedLock.value(); if (StringUtils.isEmpty(lockKey)) { lockKey = joinPoint.getSignature().toLongString(); } RLock lock = redissonClient.getLock(lockKey); boolean locked = lock.tryLock(distributedLock.leaseTime(), distributedLock.timeUnit()); if (!locked) { throw new RuntimeException("获取分布式锁失败"); } try { return joinPoint.proceed(); } finally { lock.unlock(); } } } ``` 5. 在 `application.yml` 中配置 Redisson: ```yaml spring: redis: host: localhost port: 6379 password: database: 0 redisson: single-server-config: address: redis://${spring.redis.host}:${spring.redis.port} ``` 这样就实现了一个基于 Redis 的分布式锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值