分布式防止重复请求或者高并发防止重复提交

1:自定义注解JRepeat

package com.huan.study.mybatis.config;



import java.lang.annotation.*;

/**
 * 防止重复提交的注解
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface JRepeat {

    /**
     * 超时时间
     *
     * @return
     */
    int lockTime();


    /**
     * redis 锁key的
     *
     * @return redis 锁key
     */
    String lockKey() default "";



}

2:自定义切面

package com.huan.study.mybatis.aspect;

/**
 * @author zyf
 */

import com.huan.study.mybatis.config.JRepeat;
import com.huan.study.mybatis.config.RedissonLockClient;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 防止重复提交分布式锁拦截器
 *
 */
@Aspect
@Component
public class RepeatSubmitAspect extends BaseAspect {

    @Resource
    private RedissonLockClient redissonLockClient;

    /***
     * 定义controller切入点拦截规则,拦截JRepeat注解的业务方法
     */
    @Pointcut("@annotation(jRepeat)")
    public void pointCut(JRepeat jRepeat) {
    }

    /**
     * AOP分布式锁拦截
     *
     * @param joinPoint
     * @return
     * @throws Exception
     */
    @Around("pointCut(jRepeat)")
    public Object repeatSubmit(ProceedingJoinPoint joinPoint,JRepeat jRepeat) throws Throwable {
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
        if (Objects.nonNull(jRepeat)) {
            // 获取参数
            Object[] args = joinPoint.getArgs();
            // 进行一些参数的处理,比如获取订单号,操作人id等
            StringBuffer lockKeyBuffer = new StringBuffer();
            String key =getValueBySpEL(jRepeat.lockKey(), parameterNames, args,"RepeatSubmit").get(0);
            // 公平加锁,lockTime后锁自动释放
            boolean isLocked = false;
            try {
                isLocked = redissonLockClient.fairLock(key, TimeUnit.SECONDS, jRepeat.lockTime());
                // 如果成功获取到锁就继续执行
                if (isLocked) {
                    // 执行进程
                    return joinPoint.proceed();
                } else {
                    // 未获取到锁
                    throw new RuntimeException("请勿重复提交");
                }
            } finally {
                // 如果锁还存在,在方法执行完成后,释放锁
                if (isLocked) {
                    redissonLockClient.unlock(key);
                }
            }
        }
        return joinPoint.proceed();
    }
}
package com.huan.study.mybatis.aspect;

import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.util.ArrayList;
import java.util.List;


@Slf4j
public class BaseAspect {

    /**
     * 通过spring SpEL 获取参数
     *
     * @param key            定义的key值 以#开头 例如:#user
     * @param parameterNames 形参
     * @param values         形参值
     * @param keyConstant    key的常亮
     * @return
     */
    public List<String> getValueBySpEL(String key, String[] parameterNames, Object[] values, String keyConstant) {
        List<String> keys = new ArrayList<>();
        if (!key.contains("#")) {
            String s = "redis:lock:" + key + keyConstant;
            log.debug("lockKey:" + s);
            keys.add(s);
            return keys;
        }
        //spel解析器
        ExpressionParser parser = new SpelExpressionParser();
        //spel上下文
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], values[i]);
        }
        Expression expression = parser.parseExpression(key);
        Object value = expression.getValue(context);
        if (value != null) {
            if (value instanceof List) {
                List value1 = (List) value;
                for (Object o : value1) {
                    addKeys(keys, o, keyConstant);
                }
            } else if (value.getClass().isArray()) {
                Object[] obj = (Object[]) value;
                for (Object o : obj) {
                    addKeys(keys, o, keyConstant);
                }
            } else {
                addKeys(keys, value, keyConstant);
            }
        }
        log.info("表达式key={},value={}", key, keys);
        return keys;
    }

    private void addKeys(List<String> keys, Object o, String keyConstant) {
        keys.add("redis:lock:" + o.toString() + keyConstant);
    }
}

3:自定义RedissonLockClient

package com.huan.study.mybatis.config;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁实现基于Redisson
 *
 * @author zyf
 * @date 2020-11-11
 */
@Slf4j
@Component
public class RedissonLockClient {

    @Autowired
    private RedissonClient redissonClient;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 获取锁
     */
    public RLock getLock(String lockKey) {
        return redissonClient.getLock(lockKey);
    }

    /**
     * 加锁操作
     *
     * @return boolean
     */
    public boolean tryLock(String lockName, long expireSeconds) {
        return tryLock(lockName, 0, expireSeconds);
    }


    /**
     * 加锁操作
     *
     * @return boolean
     */
    public boolean tryLock(String lockName, long waitTime, long expireSeconds) {
        RLock rLock = getLock(lockName);
        boolean getLock = false;
        try {
            getLock = rLock.tryLock(waitTime, expireSeconds, TimeUnit.SECONDS);
            if (getLock) {
                log.info("获取锁成功,lockName={}", lockName);
            } else {
                log.info("获取锁失败,lockName={}", lockName);
            }
        } catch (InterruptedException e) {
            log.error("获取式锁异常,lockName=" + lockName, e);
            getLock = false;
        }
        return getLock;
    }


    public boolean fairLock(String lockKey, TimeUnit unit, int leaseTime) {
        RLock fairLock = redissonClient.getFairLock(lockKey);
        try {
            boolean existKey = existKey(lockKey);
            // 已经存在了,就直接返回
            if (existKey) {
                return false;
            }
            return fairLock.tryLock(3, leaseTime, unit);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    public boolean existKey(String key) {
        return redisTemplate.hasKey(key);
    }
    /**
     * 锁lockKey
     *
     * @param lockKey
     * @return
     */
    public RLock lock(String lockKey) {
        RLock lock = getLock(lockKey);
        lock.lock();
        return lock;
    }

    /**
     * 锁lockKey
     *
     * @param lockKey
     * @param leaseTime
     * @return
     */
    public RLock lock(String lockKey, long leaseTime) {
        RLock lock = getLock(lockKey);
        lock.lock(leaseTime, TimeUnit.SECONDS);
        return lock;
    }


    /**
     * 解锁
     *
     * @param lockName 锁名称
     */
    public void unlock(String lockName) {
        try {
            redissonClient.getLock(lockName).unlock();
        } catch (Exception e) {
            log.error("解锁异常,lockName=" + lockName, e);
        }
    }
}

4:demo所著相同请求5秒,如果方法执行完成释放

    @SneakyThrows
    @GetMapping("test")
    @JRepeat(lockKey = "#phone", lockTime = 5)
    public void test(String phone) {

        new Thread(() -> {
            try {
                // 尝试获取锁,最多等待10秒,获取锁后10秒自动释放
                System.out.println(new Date() + Thread.currentThread().getName() + " 获取到锁,开始处理任务...");
                Thread.sleep(10000); // 模拟处理任务需要花费一些时间
                System.out.println(new Date() + Thread.currentThread().getName() + " 处理任务完成,释放锁...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        System.out.println("===" + new Date());

    }

5:yml配置redis

spring:
  redis:
    database: 0
    host: 127.0.0.1
    password: 123456
    port: 6379

注意:因为是基于AOP切面,如果在方法内,调用的方法上添加该注解会失效

相关推荐

  1. 分布式防止重复请求或者并发防止重复提交

    2024-06-07 13:38:04       8 阅读
  2. 后端怎样防止重复提交订单?

    2024-06-07 13:38:04       36 阅读
  3. SpringBoot表单防止重复提交

    2024-06-07 13:38:04       17 阅读
  4. springboot防止表单重复提交

    2024-06-07 13:38:04       9 阅读
  5. 前端:防止重复请求的方案

    2024-06-07 13:38:04       11 阅读
  6. 功能问题:如何防止接口重复请求

    2024-06-07 13:38:04       8 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-07 13:38:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-07 13:38:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-07 13:38:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-07 13:38:04       20 阅读

热门阅读

  1. Flutter与iOS原生混合开发 iOS项目集成Flutter

    2024-06-07 13:38:04       9 阅读
  2. KNN算法实例_电影类型判断

    2024-06-07 13:38:04       7 阅读
  3. C++中为什么尽量使用using 代替 typedef

    2024-06-07 13:38:04       8 阅读
  4. Vue 组件之间的通信

    2024-06-07 13:38:04       7 阅读
  5. 连续分配存储管理方式

    2024-06-07 13:38:04       6 阅读
  6. C++实现图像的模拟运动模糊

    2024-06-07 13:38:04       7 阅读
  7. 1103. 分糖果 II

    2024-06-07 13:38:04       7 阅读
  8. 力扣每日一题 6/7

    2024-06-07 13:38:04       9 阅读
  9. input 输入框只能输入数字的处理方式

    2024-06-07 13:38:04       9 阅读
  10. shujugeshi

    2024-06-07 13:38:04       9 阅读
  11. linux系统使用达梦数据库

    2024-06-07 13:38:04       7 阅读