feat: 基于redis 的分布式锁的实现
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/AbstractDistributedLockHandler.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/AbstractDistributedLockHandler.java
new file mode 100644
index 0000000..8cbca67
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/AbstractDistributedLockHandler.java
@@ -0,0 +1,30 @@
+package com.supwisdom.institute.backend.common.core.distributedlock;
+
+public abstract class AbstractDistributedLockHandler implements DistributedLockHandler {
+
+  @Override
+  public boolean lock(String key) {
+    return lock(key, TIMEOUT_MILLIS, RETRY_TIMES, SLEEP_MILLIS);
+  }
+
+  @Override
+  public boolean lock(String key, int retryTimes) {
+    return lock(key, TIMEOUT_MILLIS, retryTimes, SLEEP_MILLIS);
+  }
+
+  @Override
+  public boolean lock(String key, int retryTimes, long sleepMillis) {
+    return lock(key, TIMEOUT_MILLIS, retryTimes, sleepMillis);
+  }
+
+  @Override
+  public boolean lock(String key, long expire) {
+    return lock(key, expire, RETRY_TIMES, SLEEP_MILLIS);
+  }
+
+  @Override
+  public boolean lock(String key, long expire, int retryTimes) {
+    return lock(key, expire, retryTimes, SLEEP_MILLIS);
+  }
+
+}
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DefaultDistributedLockHandler.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DefaultDistributedLockHandler.java
new file mode 100644
index 0000000..950a510
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DefaultDistributedLockHandler.java
@@ -0,0 +1,17 @@
+package com.supwisdom.institute.backend.common.core.distributedlock;
+
+public class DefaultDistributedLockHandler extends AbstractDistributedLockHandler {
+  
+  private ThreadLocal<String> lockFlag = new ThreadLocal<String>();
+
+  @Override
+  public boolean lock(String key, long expire, int retryTimes, long sleepMillis) {
+    return true;
+  }
+
+  @Override
+  public boolean releaseLock(String key) {
+    return true;
+  }
+
+}
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockAutoConfiguration.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockAutoConfiguration.java
new file mode 100644
index 0000000..68df599
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockAutoConfiguration.java
@@ -0,0 +1,21 @@
+package com.supwisdom.institute.backend.common.core.distributedlock;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Slf4j
+@Configuration
+public class DistributedLockAutoConfiguration {
+  
+  @Bean
+  @ConditionalOnMissingBean(DistributedLockHandler.class)
+  public DistributedLockHandler defaultDistributedLockHandler() {
+    DistributedLockHandler defaultDistributedLockHandler = new DefaultDistributedLockHandler();
+    log.debug("DistributedLockAutoConfiguration.defaultDistributedLockHandler is {}", defaultDistributedLockHandler);
+
+    return defaultDistributedLockHandler;
+  }
+}
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockHandler.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockHandler.java
new file mode 100644
index 0000000..e989da9
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/DistributedLockHandler.java
@@ -0,0 +1,83 @@
+package com.supwisdom.institute.backend.common.core.distributedlock;
+
+public interface DistributedLockHandler {
+  public static final long TIMEOUT_MILLIS = 30000;
+
+  public static final int RETRY_TIMES = Integer.MIN_VALUE;
+
+  public static final long SLEEP_MILLIS = 500;
+
+  /**
+   * 获取锁<br>
+   * 超时:30000 ms (30 s) {@link DistributedLockHandler.TIMEOUT_MILLIS}<br>
+   * 重试次数:不重试 {@link DistributedLockHandler.RETRY_TIMES}<br>
+   * 重试等待:500 ms {@link DistributedLockHandler.SLEEP_MILLIS}<br>
+   * <br>
+   * @param key
+   * @return
+   */
+  public boolean lock(String key);
+
+  /**
+   * 获取锁<br>
+   * 超时:30000 ms (30 s) {@link DistributedLockHandler.TIMEOUT_MILLIS}<br>
+   * 重试等待:500 ms {@link DistributedLockHandler.SLEEP_MILLIS}<br>
+   * <br>
+   * @param key
+   * @param retryTimes 重试次数
+   * @return
+   */
+  public boolean lock(String key, int retryTimes);
+
+  /**
+   * 获取锁<br>
+   * 超时:30000 ms (30 s) {@link DistributedLockHandler.TIMEOUT_MILLIS}<br>
+   * <br>
+   * @param key
+   * @param retryTimes 重试次数
+   * @param sleepMillis 重试等待 单位 ms
+   * @return
+   */
+  public boolean lock(String key, int retryTimes, long sleepMillis);
+
+  /**
+   * 获取锁<br>
+   * 重试次数:不重试 {@link DistributedLockHandler.RETRY_TIMES}<br>
+   * 重试等待:500 ms {@link DistributedLockHandler.SLEEP_MILLIS}<br>
+   * <br>
+   * @param key
+   * @param expire 超时 单位 ms
+   * @return
+   */
+  public boolean lock(String key, long expire);
+
+  /**
+   * 获取锁<br>
+   * 重试等待:500 ms {@link DistributedLockHandler.SLEEP_MILLIS}<br>
+   * <br>
+   * @param key
+   * @param retryTimes 重试次数
+   * @param expire 超时 单位 ms
+   * @return
+   */
+  public boolean lock(String key, long expire, int retryTimes);
+
+  /**
+   * 获取锁<br>
+   * <br>
+   * @param key
+   * @param expire 超时 单位 ms
+   * @param retryTimes 重试次数
+   * @param sleepMillis 重试等待 单位 ms
+   * @return
+   */
+  public boolean lock(String key, long expire, int retryTimes, long sleepMillis);
+
+  /**
+   * 释放锁<br>
+   * <br>
+   * @param key
+   * @return
+   */
+  public boolean releaseLock(String key);
+}
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/EnableDistributedLock.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/EnableDistributedLock.java
new file mode 100644
index 0000000..89053c9
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/EnableDistributedLock.java
@@ -0,0 +1,17 @@
+package com.supwisdom.institute.backend.common.core.distributedlock;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Import;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Import({DistributedLockAutoConfiguration.class})
+public @interface EnableDistributedLock {
+
+}
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLock.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLock.java
new file mode 100644
index 0000000..52c2485
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLock.java
@@ -0,0 +1,36 @@
+package com.supwisdom.institute.backend.common.core.distributedlock.aop;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface DistributedLock {
+
+  /** 锁的资源,redis的key */
+  String value() default "default";
+
+  /** 持锁时间,单位毫秒 */
+  long keepMills() default 30000;
+
+  /** 当获取失败时候动作 */
+  LockFailAction action() default LockFailAction.CONTINUE;
+
+  public enum LockFailAction {
+    /** 放弃 */
+    GIVEUP,
+    /** 继续 */
+    CONTINUE;
+  }
+
+  /** 重试的间隔时间,设置GIVEUP忽略此项 */
+  long sleepMills() default 200;
+
+  /** 重试次数 */
+  int retryTimes() default 5;
+
+}
diff --git a/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLockAspectConfiguration.java b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLockAspectConfiguration.java
new file mode 100644
index 0000000..fb67688
--- /dev/null
+++ b/common/core/src/main/java/com/supwisdom/institute/backend/common/core/distributedlock/aop/DistributedLockAspectConfiguration.java
@@ -0,0 +1,67 @@
+package com.supwisdom.institute.backend.common.core.distributedlock.aop;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.StringUtils;
+
+import com.supwisdom.institute.backend.common.core.distributedlock.DistributedLockAutoConfiguration;
+import com.supwisdom.institute.backend.common.core.distributedlock.DistributedLockHandler;
+import com.supwisdom.institute.backend.common.core.distributedlock.aop.DistributedLock.LockFailAction;
+
+@Aspect
+@Configuration
+@ConditionalOnClass({ProceedingJoinPoint.class, DistributedLockHandler.class})
+@AutoConfigureAfter(DistributedLockAutoConfiguration.class)
+public class DistributedLockAspectConfiguration {
+  
+  private final Logger logger = LoggerFactory.getLogger(DistributedLockAspectConfiguration.class);
+  
+  @Autowired
+  private DistributedLockHandler distributedLockHandler;
+
+  @Pointcut("@annotation(com.supwisdom.institute.backend.common.core.distributedlock.aop.DistributedLock)")
+  private void lockPoint(){
+    
+  }
+  
+  @Around("lockPoint()")
+  public Object around(ProceedingJoinPoint pjp) throws Throwable{
+    Method method = ((MethodSignature) pjp.getSignature()).getMethod();
+    DistributedLock distributedLockAnnotation = method.getAnnotation(DistributedLock.class);
+    String key = distributedLockAnnotation.value();
+    if(StringUtils.isEmpty(key)){
+      Object[] args = pjp.getArgs();
+      key = Arrays.toString(args);
+    }
+    int retryTimes = distributedLockAnnotation.action().equals(LockFailAction.CONTINUE) ? distributedLockAnnotation.retryTimes() : 0;
+    boolean lock = distributedLockHandler.lock(key, distributedLockAnnotation.keepMills(), retryTimes, distributedLockAnnotation.sleepMills());
+    if(!lock) {
+      logger.debug("get lock failed : " + key);
+      return null;
+    }
+    
+    //得到锁,执行方法,释放锁
+    logger.debug("get lock success : " + key);
+    try {
+      return pjp.proceed();
+    } catch (Exception e) {
+      logger.error("execute locked method occured an exception", e);
+    } finally {
+      boolean releaseResult = distributedLockHandler.releaseLock(key);
+      logger.debug("release lock : " + key + (releaseResult ? " success" : " failed"));
+    }
+    return null;
+  }
+}
\ No newline at end of file
diff --git a/common/core/src/main/resources/META-INF/spring.factories b/common/core/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..b94673d
--- /dev/null
+++ b/common/core/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,4 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  com.supwisdom.institute.backend.common.core.distributedlock.DistributedLockAutoConfiguration,\
+  com.supwisdom.institute.backend.common.core.distributedlock.aop.DistributedLockAspectConfiguration
+
diff --git a/common/framework/pom.xml b/common/framework/pom.xml
index 18d0ac5..1cf7bc7 100644
--- a/common/framework/pom.xml
+++ b/common/framework/pom.xml
@@ -47,11 +47,35 @@
       <artifactId>spring-boot-starter-web</artifactId>
       <optional>true</optional>
     </dependency>
+    
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-aop</artifactId>
+      <optional>true</optional>
+    </dependency>
+    
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
       <optional>true</optional>
     </dependency>
+    
+    
+    <!-- <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-data-redis</artifactId>
+      <optional>true</optional>
+    </dependency> -->
+    <dependency>
+      <groupId>org.springframework.data</groupId>
+      <artifactId>spring-data-redis</artifactId>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>redis.clients</groupId>
+      <artifactId>jedis</artifactId>
+      <optional>true</optional>
+    </dependency>
 
 
     <dependency>
diff --git a/common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockAutoConfiguration.java b/common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockAutoConfiguration.java
new file mode 100644
index 0000000..edf8505
--- /dev/null
+++ b/common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockAutoConfiguration.java
@@ -0,0 +1,33 @@
+package com.supwisdom.institute.backend.common.framework.distributedlock;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import redis.clients.jedis.Jedis;
+
+import com.supwisdom.institute.backend.common.core.distributedlock.DistributedLockAutoConfiguration;
+import com.supwisdom.institute.backend.common.core.distributedlock.DistributedLockHandler;
+
+@Slf4j
+@Configuration
+@ConditionalOnClass({ Jedis.class })
+@AutoConfigureAfter(RedisAutoConfiguration.class)
+@AutoConfigureBefore(DistributedLockAutoConfiguration.class)
+public class RedisDistributedLockAutoConfiguration {
+
+  @Bean
+  public DistributedLockHandler redisDistributedLockHandler(RedisTemplate<Object, Object> redisTemplate) {
+    DistributedLockHandler redisDistributedLockHandler = new RedisDistributedLockHandler(redisTemplate);
+    log.debug("RedisDistributedLockAutoConfiguration.redisDistributedLockHandler is {}", redisDistributedLockHandler);
+    
+    return redisDistributedLockHandler;
+  }
+
+}
diff --git a/common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockHandler.java b/common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockHandler.java
new file mode 100644
index 0000000..59afa38
--- /dev/null
+++ b/common/framework/src/main/java/com/supwisdom/institute/backend/common/framework/distributedlock/RedisDistributedLockHandler.java
@@ -0,0 +1,117 @@
+package com.supwisdom.institute.backend.common.framework.distributedlock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.util.StringUtils;
+
+import com.supwisdom.institute.backend.common.core.distributedlock.AbstractDistributedLockHandler;
+
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisCluster;
+import redis.clients.jedis.JedisCommands;
+
+public class RedisDistributedLockHandler extends AbstractDistributedLockHandler {
+
+  private final Logger logger = LoggerFactory.getLogger(RedisDistributedLockHandler.class);
+
+  private RedisTemplate<Object, Object> redisTemplate;
+
+  private ThreadLocal<String> lockFlag = new ThreadLocal<String>();
+
+  public static final String UNLOCK_LUA;
+
+  static {
+    StringBuilder sb = new StringBuilder();
+    sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
+    sb.append("then ");
+    sb.append("    return redis.call(\"del\",KEYS[1]) ");
+    sb.append("else ");
+    sb.append("    return 0 ");
+    sb.append("end ");
+    UNLOCK_LUA = sb.toString();
+  }
+
+  public RedisDistributedLockHandler(RedisTemplate<Object, Object> redisTemplate) {
+    super();
+    this.redisTemplate = redisTemplate;
+  }
+
+  @Override
+  public boolean lock(String key, long expire, int retryTimes, long sleepMillis) {
+    boolean result = setRedis(key, expire);
+    // 如果获取锁失败,按照传入的重试次数进行重试
+    while ((!result) && retryTimes-- > 0) {
+      try {
+        logger.debug("lock failed, retrying..." + retryTimes);
+        Thread.sleep(sleepMillis);
+      } catch (InterruptedException e) {
+        return false;
+      }
+      result = setRedis(key, expire);
+    }
+    return result;
+  }
+
+  private boolean setRedis(String key, long expire) {
+    try {
+      String result = redisTemplate.execute(new RedisCallback<String>() {
+        @Override
+        public String doInRedis(RedisConnection connection) throws DataAccessException {
+          JedisCommands commands = (JedisCommands) connection.getNativeConnection();
+          String uuid = UUID.randomUUID().toString();
+          lockFlag.set(uuid);
+          return commands.set(key, uuid, "NX", "PX", expire);
+        }
+      });
+      return !StringUtils.isEmpty(result);
+    } catch (Exception e) {
+      logger.error("set redis occured an exception", e);
+    }
+    return false;
+  }
+
+  @Override
+  public boolean releaseLock(String key) {
+    // 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
+    try {
+      List<String> keys = new ArrayList<String>();
+      keys.add(key);
+      List<String> args = new ArrayList<String>();
+      args.add(lockFlag.get());
+
+      // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
+      // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
+
+      Long result = redisTemplate.execute(new RedisCallback<Long>() {
+        public Long doInRedis(RedisConnection connection) throws DataAccessException {
+          Object nativeConnection = connection.getNativeConnection();
+          // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
+          // 集群模式
+          if (nativeConnection instanceof JedisCluster) {
+            return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
+          }
+
+          // 单机模式
+          else if (nativeConnection instanceof Jedis) {
+            return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
+          }
+          return 0L;
+        }
+      });
+
+      return result != null && result > 0;
+    } catch (Exception e) {
+      logger.error("release lock occured an exception", e);
+    }
+    return false;
+  }
+
+}
diff --git a/common/framework/src/main/resources/META-INF/spring.factories b/common/framework/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..53d2c7c
--- /dev/null
+++ b/common/framework/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  com.supwisdom.institute.backend.common.framework.distributedlock.RedisDistributedLockAutoConfiguration