diff --git a/pom.xml b/pom.xml
index bda7b27..5d26f7c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,8 @@
spring-boot-nebula-mybatis
spring-boot-nebula-aop-base
spring-boot-nebula-distribute-lock
-
+ spring-boot-nebula-web-common
+
spring-boot-nebula
spring-boot-common
diff --git a/spring-boot-nebula-dependencies/pom.xml b/spring-boot-nebula-dependencies/pom.xml
index ae34132..79f22b7 100644
--- a/spring-boot-nebula-dependencies/pom.xml
+++ b/spring-boot-nebula-dependencies/pom.xml
@@ -28,6 +28,7 @@
4.4
6.6.5
3.5.5
+ 3.17.3
@@ -65,6 +66,12 @@
${revision}
+
+ io.github.weihubeats
+ spring-boot-nebula-web-common
+ ${revision}
+
+
com.google.guava
guava
@@ -89,6 +96,12 @@
${mybatis-plus-boot-starter.version}
+
+ org.redisson
+ redisson
+ ${redission.version}
+
+
diff --git a/spring-boot-nebula-distribute-lock/pom.xml b/spring-boot-nebula-distribute-lock/pom.xml
index fa3dacb..d480b5c 100644
--- a/spring-boot-nebula-distribute-lock/pom.xml
+++ b/spring-boot-nebula-distribute-lock/pom.xml
@@ -16,12 +16,29 @@
11
UTF-8
-
+
+
io.github.weihubeats
spring-boot-nebula-aop-base
+
+
+ org.redisson
+ redisson
+
+
+
+ io.github.weihubeats
+ spring-boot-nebula-common
+
+
+
+ io.github.weihubeats
+ spring-boot-nebula-web-common
+
+
\ No newline at end of file
diff --git a/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/annotation/NebulaDistributedLock.java b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/annotation/NebulaDistributedLock.java
new file mode 100644
index 0000000..46ab458
--- /dev/null
+++ b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/annotation/NebulaDistributedLock.java
@@ -0,0 +1,65 @@
+package com.nebula.distribute.lock.annotation;
+
+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 java.util.concurrent.TimeUnit;
+
+/**
+ * @author : wh
+ * @date : 2024/3/13 13:49
+ * @description:
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface NebulaDistributedLock {
+
+ /**
+ * 锁名字
+ */
+ String lockName() default "";
+
+ /**
+ * 锁前缀
+ */
+ String lockNamePre() default "";
+
+ /**
+ * 锁后缀
+ */
+ String lockNamePost() default "";
+
+ /**
+ * 锁前后缀拼接分隔符
+ */
+ String separator() default "_";
+
+ /**
+ * 是否使用公平锁
+ */
+ boolean fairLock() default false;
+
+ /**
+ * 是否使用尝试锁
+ */
+ boolean tryLock() default false;
+
+ /**
+ * 尝试锁最长等待时间
+ */
+ long tryWaitTime() default 30L;
+
+ /**
+ * 锁超时时间,超时自动释放锁
+ */
+ long outTime() default 20L;
+
+ /**
+ * 时间单位 默认秒
+ */
+ TimeUnit timeUnit() default TimeUnit.SECONDS;
+
+}
diff --git a/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/aop/NebulaDistributedLockAnnotationInterceptor.java b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/aop/NebulaDistributedLockAnnotationInterceptor.java
new file mode 100644
index 0000000..b0e656a
--- /dev/null
+++ b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/aop/NebulaDistributedLockAnnotationInterceptor.java
@@ -0,0 +1,113 @@
+package com.nebula.distribute.lock.aop;
+
+import com.nebula.base.utils.DataUtils;
+import com.nebula.distribute.lock.annotation.NebulaDistributedLock;
+import com.nebula.distribute.lock.core.DistributedLock;
+import com.nebula.distribute.lock.core.NebulaDistributedLockTemplate;
+import com.nebula.web.common.utils.ExpressionUtil;
+import java.lang.reflect.Method;
+import java.util.Objects;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import lombok.extern.slf4j.Slf4j;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+/**
+ * @author : wh
+ * @date : 2024/3/15 13:34
+ * @description:
+ */
+@Slf4j
+public class NebulaDistributedLockAnnotationInterceptor implements MethodInterceptor {
+
+ private final NebulaDistributedLockTemplate lock;
+
+ public NebulaDistributedLockAnnotationInterceptor(NebulaDistributedLockTemplate lock) {
+ if (DataUtils.isEmpty(lock)) {
+ throw new RuntimeException("DistributedLockTemplate is null");
+ }
+ this.lock = lock;
+ }
+
+ @Nullable
+ @Override
+ public Object invoke(@Nonnull MethodInvocation methodInvocation) {
+ Method method = methodInvocation.getMethod();
+ NebulaDistributedLock annotation = method.getAnnotation(NebulaDistributedLock.class);
+ Object[] args = methodInvocation.getArguments();
+ String lockName = getLockName(annotation, args, method);
+ if (log.isDebugEnabled()) {
+ log.debug("lockName: {}", lockName);
+ }
+ boolean fairLock = annotation.fairLock();
+ if (annotation.tryLock()) {
+ return lock.tryLock(new DistributedLock<>() {
+ @Override
+ public Object process() {
+ return proceed(methodInvocation);
+ }
+
+ @Override
+ public String lockName() {
+ return lockName;
+ }
+ }, annotation.tryWaitTime(), annotation.outTime(), annotation.timeUnit(), fairLock);
+ } else {
+ return lock.lock(new DistributedLock<>() {
+ @Override
+ public Object process() {
+ return proceed(methodInvocation);
+ }
+
+ @Override
+ public String lockName() {
+ return lockName;
+ }
+ }, annotation.outTime(), annotation.timeUnit(), fairLock);
+ }
+ }
+
+ public Object proceed(MethodInvocation methodInvocation) {
+ try {
+ return methodInvocation.proceed();
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ /**
+ * 获取锁名字,优先获取注解中锁名
+ *
+ * @param nebulaDistributedLock
+ * @param args
+ * @param method
+ * @return
+ */
+ private String getLockName(NebulaDistributedLock nebulaDistributedLock, Object[] args, Method method) {
+ if (DataUtils.isNotEmpty(nebulaDistributedLock.lockName())) {
+ return nebulaDistributedLock.lockName();
+ }
+ String lockNamePre = nebulaDistributedLock.lockNamePre();
+ String lockNamePost = nebulaDistributedLock.lockNamePost();
+ String separator = nebulaDistributedLock.separator();
+
+ if (ExpressionUtil.isEl(lockNamePre)) {
+ lockNamePre = (String) ExpressionUtil.parse(lockNamePre, method, args);
+ }
+ if (ExpressionUtil.isEl(lockNamePost)) {
+ lockNamePost = Objects.requireNonNull(ExpressionUtil.parse(lockNamePost, method, args)).toString();
+ }
+
+ StringBuilder sb = new StringBuilder();
+ if (DataUtils.isNotEmpty(lockNamePre)) {
+ sb.append(lockNamePre);
+ }
+ sb.append(separator);
+ if (DataUtils.isNotEmpty(lockNamePost)) {
+ sb.append(lockNamePost);
+ }
+ return sb.toString();
+ }
+}
diff --git a/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/autoconfigure/NebulaDistributedLockAutoConfiguration.java b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/autoconfigure/NebulaDistributedLockAutoConfiguration.java
new file mode 100644
index 0000000..57ce410
--- /dev/null
+++ b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/autoconfigure/NebulaDistributedLockAutoConfiguration.java
@@ -0,0 +1,35 @@
+package com.nebula.distribute.lock.autoconfigure;
+
+import com.nebula.aop.base.NebulaBaseAnnotationAdvisor;
+import com.nebula.distribute.lock.annotation.NebulaDistributedLock;
+import com.nebula.distribute.lock.aop.NebulaDistributedLockAnnotationInterceptor;
+import com.nebula.distribute.lock.core.NebulaDistributedLockTemplate;
+import com.nebula.distribute.lock.core.RedissonDistributedLockTemplate;
+import org.redisson.api.RedissonClient;
+import org.springframework.aop.Advisor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+
+/**
+ * @author : wh
+ * @date : 2024/3/15 13:39
+ * @description:
+ */
+@Configuration(proxyBeanMethods = false)
+public class NebulaDistributedLockAutoConfiguration {
+
+ @Bean
+ public RedissonDistributedLockTemplate redissonDistributedLockTemplate(RedissonClient redissonClient) {
+ RedissonDistributedLockTemplate template = new RedissonDistributedLockTemplate(redissonClient);
+ return template;
+ }
+
+ @Bean
+ @Order(1)
+ public Advisor distributedLockAnnotationAdvisor(NebulaDistributedLockTemplate nebulaDistributedLockTemplate) {
+ NebulaDistributedLockAnnotationInterceptor advisor = new NebulaDistributedLockAnnotationInterceptor(nebulaDistributedLockTemplate);
+ return new NebulaBaseAnnotationAdvisor(advisor, NebulaDistributedLock.class);
+ }
+
+}
diff --git a/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/core/DistributedLock.java b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/core/DistributedLock.java
new file mode 100644
index 0000000..4e392de
--- /dev/null
+++ b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/core/DistributedLock.java
@@ -0,0 +1,16 @@
+package com.nebula.distribute.lock.core;
+
+/**
+ * @author : wh
+ * @date : 2024/3/15 13:35
+ * @description:
+ */
+public interface DistributedLock {
+
+ /**
+ * 分布式锁逻辑 代码块
+ */
+ T process();
+
+ String lockName();
+}
diff --git a/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/core/NebulaDistributedLockTemplate.java b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/core/NebulaDistributedLockTemplate.java
new file mode 100644
index 0000000..441d387
--- /dev/null
+++ b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/core/NebulaDistributedLockTemplate.java
@@ -0,0 +1,67 @@
+package com.nebula.distribute.lock.core;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author : wh
+ * @date : 2024/3/15 13:35
+ * @description:
+ */
+public interface NebulaDistributedLockTemplate {
+
+ /**
+ * 默认超时锁释放时间
+ */
+ long DEFAULT_OUT_TIME = 5;
+ /**
+ * 默认尝试加锁时间
+ */
+ long DEFAULT_TRY_OUT_TIME = 30;
+ /**
+ * 默认时间单位
+ */
+ TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;
+
+ /**
+ * 加锁
+ * @param distributedLock
+ * @param fairLock 是否使用公平锁
+ * @param
+ * @return
+ */
+ T lock(DistributedLock distributedLock, boolean fairLock);
+
+ /**
+ *
+ * @param distributedLock
+ * @param outTime 锁超时时间。超时后自动释放锁
+ * @param timeUnit 时间单位
+ * @param fairLock 是否使用公平锁
+ * @param
+ * @return
+ */
+ T lock(DistributedLock distributedLock, long outTime, TimeUnit timeUnit, boolean fairLock);
+
+ /**
+ * 尝试加锁
+ * @param distributedLock
+ * @param fairLock 是否使用公平锁
+ * @param
+ * @return
+ */
+ T tryLock(DistributedLock distributedLock, boolean fairLock);
+
+ /**
+ *
+ * @param distributedLock
+ * @param tryOutTime 尝试获取锁时间
+ * @param outTime 锁超时时间
+ * @param timeUnit 时间单位
+ * @param fairLock 是否使用公平锁
+ * @param
+ * @return
+ */
+ T tryLock(DistributedLock distributedLock, long tryOutTime, long outTime, TimeUnit timeUnit, boolean fairLock);
+
+
+}
diff --git a/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/core/RedissonDistributedLockTemplate.java b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/core/RedissonDistributedLockTemplate.java
new file mode 100644
index 0000000..2c8652a
--- /dev/null
+++ b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/core/RedissonDistributedLockTemplate.java
@@ -0,0 +1,74 @@
+package com.nebula.distribute.lock.core;
+
+import com.nebula.distribute.lock.exception.DistributedLockException;
+import java.util.concurrent.TimeUnit;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+
+/**
+ * @author : wh
+ * @date : 2024/3/15 13:36
+ * @description:
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class RedissonDistributedLockTemplate implements NebulaDistributedLockTemplate {
+
+ private final RedissonClient redisson;
+
+ @Override
+ public T lock(DistributedLock distributedLock, boolean fairLock) {
+ return lock(distributedLock, DEFAULT_OUT_TIME, DEFAULT_TIME_UNIT, fairLock);
+ }
+
+ @Override
+ public T lock(DistributedLock distributedLock, long outTime, TimeUnit timeUnit, boolean fairLock) {
+ RLock lock = getLock(distributedLock.lockName(), fairLock);
+ lock.lock(outTime, timeUnit);
+ try {
+ return distributedLock.process();
+ } finally {
+ if (lock.isLocked()) {
+ lock.unlock();
+ }
+ }
+ }
+
+ @Override
+ public T tryLock(DistributedLock distributedLock, boolean fairLock) {
+ return tryLock(distributedLock, DEFAULT_TRY_OUT_TIME, DEFAULT_OUT_TIME, DEFAULT_TIME_UNIT, fairLock);
+ }
+
+ @Override
+ public T tryLock(DistributedLock distributedLock, long tryOutTime, long outTime, TimeUnit timeUnit,
+ boolean fairLock) {
+ String lockName = distributedLock.lockName();
+ RLock lock = getLock(lockName, fairLock);
+ try {
+ log.info("try acquire lock {}", lockName);
+ if (lock.tryLock(tryOutTime, outTime, timeUnit)) {
+ log.info("lock acquired {}", lockName);
+ try {
+ return distributedLock.process();
+ } finally {
+ // isHeldByCurrentThread 防止锁过期再释放锁导致报错
+ if (lock.isLocked() && lock.isHeldByCurrentThread()) {
+ lock.unlock();
+ log.info("lock released {}", lockName);
+ }
+ }
+ }
+ } catch (InterruptedException ignored) {
+ log.warn("锁中断...");
+ log.info("can not acquire lock {}", lockName);
+ }
+ throw new DistributedLockException("lock fail");
+ }
+
+ private RLock getLock(String lockName, boolean fairLock) {
+ return fairLock ? redisson.getFairLock(lockName) : redisson.getLock(lockName);
+ }
+
+}
diff --git a/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/exception/DistributedLockException.java b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/exception/DistributedLockException.java
new file mode 100644
index 0000000..9924446
--- /dev/null
+++ b/spring-boot-nebula-distribute-lock/src/main/java/com/nebula/distribute/lock/exception/DistributedLockException.java
@@ -0,0 +1,13 @@
+package com.nebula.distribute.lock.exception;
+
+/**
+ * @author : wh
+ * @date : 2024/3/15 13:41
+ * @description:
+ */
+public class DistributedLockException extends RuntimeException {
+
+ public DistributedLockException(String message) {
+ super(message);
+ }
+}
diff --git a/spring-boot-nebula-distribute-lock/src/main/resources/META-INF/spring.factories b/spring-boot-nebula-distribute-lock/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..a1e8656
--- /dev/null
+++ b/spring-boot-nebula-distribute-lock/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ com.nebula.distribute.lock.autoconfigure.NebulaDistributedLockAutoConfiguration
diff --git a/spring-boot-nebula-web-common/pom.xml b/spring-boot-nebula-web-common/pom.xml
new file mode 100644
index 0000000..cc96855
--- /dev/null
+++ b/spring-boot-nebula-web-common/pom.xml
@@ -0,0 +1,34 @@
+
+
+ 4.0.0
+
+ io.github.weihubeats
+ spring-boot-nebula
+ ${revision}
+
+
+ spring-boot-nebula-web-common
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ io.github.weihubeats
+ spring-boot-nebula-common
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-boot-nebula-web-common/src/main/java/com/nebula/web/common/autoconfigure/NebulaApplicationContextAware.java b/spring-boot-nebula-web-common/src/main/java/com/nebula/web/common/autoconfigure/NebulaApplicationContextAware.java
new file mode 100644
index 0000000..354297b
--- /dev/null
+++ b/spring-boot-nebula-web-common/src/main/java/com/nebula/web/common/autoconfigure/NebulaApplicationContextAware.java
@@ -0,0 +1,21 @@
+package com.nebula.web.common.autoconfigure;
+
+import com.nebula.web.common.utils.SpringBeanUtils;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author : wh
+ * @date : 2024/3/16 10:33
+ * @description:
+ */
+@Configuration(proxyBeanMethods = false)
+public class NebulaApplicationContextAware {
+
+ @Bean
+ public SpringBeanUtils springBeanUtils() {
+ return new SpringBeanUtils();
+ }
+
+
+}
diff --git a/spring-boot-nebula-web-common/src/main/java/com/nebula/web/common/utils/ExpressionUtil.java b/spring-boot-nebula-web-common/src/main/java/com/nebula/web/common/utils/ExpressionUtil.java
new file mode 100644
index 0000000..90c4faf
--- /dev/null
+++ b/spring-boot-nebula-web-common/src/main/java/com/nebula/web/common/utils/ExpressionUtil.java
@@ -0,0 +1,48 @@
+package com.nebula.web.common.utils;
+
+import com.nebula.base.utils.DataUtils;
+import java.lang.reflect.Method;
+import java.util.Objects;
+import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+/**
+ * @author : wh
+ * @date : 2024/3/16 10:20
+ * @description:
+ */
+public class ExpressionUtil {
+
+ /**
+ * el表达式解析
+ *
+ * @param expressionString 解析值
+ * @param method 方法
+ * @param args 参数
+ * @return
+ */
+ public static Object parse(String expressionString, Method method, Object[] args) {
+ if (DataUtils.isEmpty(expressionString)) {
+ return null;
+ }
+ //获取被拦截方法参数名列表
+ LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
+ String[] paramNameArr = discoverer.getParameterNames(method);
+ //SPEL解析
+ ExpressionParser parser = new SpelExpressionParser();
+ StandardEvaluationContext context = new StandardEvaluationContext();
+ for (int i = 0; i < Objects.requireNonNull(paramNameArr).length; i++) {
+ context.setVariable(paramNameArr[i], args[i]);
+ }
+ return parser.parseExpression(expressionString).getValue(context);
+ }
+
+ public static boolean isEl(String param) {
+ if (DataUtils.isEmpty(param)) {
+ return false;
+ }
+ return Objects.equals(param.substring(0, 1), "#");
+ }
+}
diff --git a/spring-boot-nebula-web-common/src/main/java/com/nebula/web/common/utils/SpringBeanUtils.java b/spring-boot-nebula-web-common/src/main/java/com/nebula/web/common/utils/SpringBeanUtils.java
new file mode 100644
index 0000000..509063c
--- /dev/null
+++ b/spring-boot-nebula-web-common/src/main/java/com/nebula/web/common/utils/SpringBeanUtils.java
@@ -0,0 +1,41 @@
+package com.nebula.web.common.utils;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+/**
+ * @author : wh
+ * @date : 2024/3/16 10:23
+ * @description:
+ */
+public class SpringBeanUtils implements ApplicationContextAware {
+
+ private static ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ SpringBeanUtils.applicationContext = applicationContext;
+ }
+
+ private static ApplicationContext getApplicationContext() {
+ return applicationContext;
+ }
+
+ public static Object getBean(String name) {
+ return getApplicationContext().getBean(name);
+ }
+
+ public static T getBean(Class clazz) {
+ return getApplicationContext().getBean(clazz);
+ }
+
+ public static T getBean(String name, Class clazz) {
+ return getApplicationContext().getBean(name, clazz);
+ }
+
+ public static boolean containsBean(Class clazz) {
+ return getApplicationContext().getBeanNamesForType(clazz).length > 0;
+ }
+
+}
diff --git a/spring-boot-nebula-web-common/src/main/resources/META-INF/spring.factories b/spring-boot-nebula-web-common/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..ca5b352
--- /dev/null
+++ b/spring-boot-nebula-web-common/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ com.nebula.web.common.autoconfigure.NebulaApplicationContextAware