Browse Source

fix:添加锁

ly 9 months ago
parent
commit
7cff495392

+ 11 - 1
pom.xml

@@ -253,7 +253,17 @@
             <artifactId>swagger-models</artifactId>
             <version>1.6.2</version>
         </dependency>
-
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-boot-starter</artifactId>
+            <version>3.13.6</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-actuator</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
         <!-- 获取系统信息 -->
         <dependency>
             <groupId>com.github.oshi</groupId>

+ 36 - 0
src/main/java/cn/ezhizao/common/utils/ELUtils.java

@@ -0,0 +1,36 @@
+package cn.ezhizao.common.utils;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+import java.lang.reflect.Method;
+
+public class ELUtils {
+
+    private static final ExpressionParser parser = new SpelExpressionParser();
+
+    public static String parseExpression(String expression, ProceedingJoinPoint joinPoint) {
+        if (expression.contains("#") || expression.contains("T(")) {
+            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+            Method method = signature.getMethod();
+
+            // 创建解析上下文并设置方法参数
+            StandardEvaluationContext context = new StandardEvaluationContext();
+            Object[] args = joinPoint.getArgs();
+            String[] paramNames = signature.getParameterNames();
+            for (int i = 0; i < paramNames.length; i++) {
+                context.setVariable(paramNames[i], args[i]);
+            }
+
+            // 解析 SpEL 表达式
+            return parser.parseExpression(expression).getValue(context, String.class);
+        } else {
+            // 如果是普通字符串,直接返回
+            return expression;
+        }
+    }
+}
+

+ 17 - 0
src/main/java/cn/ezhizao/framework/aspectj/lang/annotation/distributedLock.java

@@ -0,0 +1,17 @@
+package cn.ezhizao.framework.aspectj.lang.annotation;
+
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface distributedLock {
+    String prefix() default "";
+    String key() default "";
+    long leaseTime() default 10;
+    TimeUnit leaseTimeUnit() default TimeUnit.SECONDS;
+    long waitTime() default 0;
+    String errorMsg() default "其他人正在处理请稍后刷新页面";
+}

+ 114 - 0
src/main/java/cn/ezhizao/framework/aspectj/lang/redisLockAspect.java

@@ -0,0 +1,114 @@
+package cn.ezhizao.framework.aspectj.lang;
+
+import cn.ezhizao.common.utils.ELUtils;
+import cn.ezhizao.common.utils.SecurityUtils;
+import cn.ezhizao.framework.aspectj.lang.annotation.distributedLock;
+import lombok.extern.log4j.Log4j2;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.ApplicationContext;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+@Component
+@Aspect
+@Log4j2
+@ConditionalOnProperty(name = "cache.type", havingValue = "redis", matchIfMissing = true)
+public class redisLockAspect {
+    @Autowired
+    private RedissonClient redissonClient;
+    private final ExpressionParser parser = new SpelExpressionParser();
+    private final ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
+    @Autowired
+    private ApplicationContext context;
+    @Around("@annotation(distributedLock)")
+    public Object around(ProceedingJoinPoint joinPoint, distributedLock distributedLock) throws Throwable {
+        return distributed(joinPoint, distributedLock);
+    }
+    private Object distributed(ProceedingJoinPoint joinPoint, distributedLock distributedLock) throws Throwable {
+        // 获取 SpEL 表达式的 key 值
+        // 解析 prefix 和 key
+        String prefix = ELUtils.parseExpression(distributedLock.prefix(), joinPoint);
+        String key = distributedLock.key();
+        if(distributedLock.key().startsWith("@")){
+//            Object bean = context.getBean(key.substring(1,key.indexOf(".")));
+//            Object o = SpringUtils.getBean(key.substring(1,key.indexOf(".")));
+//            Method method = bean.getClass().getMethod(key.substring(key.indexOf(".")+1,key.indexOf("(")));需要参数列表放弃
+//            method.invoke(MethodSignatureMatcher.splitParameters(key));
+            if(key.contains("tenantId")){
+                key = String.valueOf(SecurityUtils.getTenantId());
+            }
+        }else if(distributedLock.key().startsWith("#")){
+           key = ELUtils.parseExpression(distributedLock.key(), joinPoint);
+        }
+
+        String lockKey = prefix + key; // 组合成完整的锁 key
+        long leaseTime = distributedLock.leaseTime();
+        TimeUnit timeUnit = distributedLock.leaseTimeUnit();
+        RLock lock = redissonClient.getLock(lockKey);
+
+        log.info("start lock {}", lockKey);
+        try {
+            // 尝试获取锁
+            if(lock.isLocked()){
+                throw new RuntimeException(distributedLock.errorMsg());
+            }else{
+                if(lock.tryLock(distributedLock.waitTime(), leaseTime, timeUnit)){
+                    return joinPoint.proceed();
+                }else{
+                    throw new RuntimeException("加锁失败");
+                }
+            }
+        } finally {
+            if(lock.isHeldByCurrentThread()){
+                log.info("end lock {}", lockKey);
+                // 只有是当前线程才去释放锁
+                lock.unlock();
+            }
+        }
+    }
+    private Object alone(ProceedingJoinPoint joinPoint, distributedLock distributedLock) throws Throwable {
+        // 解析 prefix 和 key
+        String prefix = ELUtils.parseExpression(distributedLock.prefix(), joinPoint);
+        String key = ELUtils.parseExpression(distributedLock.key(), joinPoint);
+        String lockKey = prefix + key; // 组合成完整的锁 key
+        // 获取 leaseTime
+        long leaseTime = distributedLock.leaseTime();
+        TimeUnit timeUnit = distributedLock.leaseTimeUnit();
+
+        // 从 map 中获取锁
+        ReentrantLock lock = lockMap.computeIfAbsent(lockKey, k -> new ReentrantLock());
+        boolean acquired = false;
+
+        log.info("Attempting to lock {}", lockKey);
+        try {
+            // 尝试获取锁,设置等待时间
+            acquired = lock.tryLock(leaseTime, timeUnit);
+            if (acquired) {
+                log.info("Lock acquired: {}", lockKey);
+                // 获取到锁,执行方法
+                return joinPoint.proceed();
+            } else {
+                log.warn("Unable to acquire lock for key: {}", lockKey);
+                throw new RuntimeException("Unable to acquire lock for key: " + lockKey);
+            }
+        } finally {
+            if (acquired) {
+                lock.unlock();
+                log.info("Lock released: {}", lockKey);
+                // 锁释放后,移除锁对象,避免内存泄漏
+                lockMap.remove(lockKey);
+            }
+        }
+    }
+}