diff --git a/README.md b/README.md
index 652c3856b..d859b51a7 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,8 @@
1. Redis 监控:监控 Redis 数据库的使用情况,使用的 Redis Key 管理
1. Java 监控:基于 Spring Boot Admin 实现 Java 应用的监控
1. 链路追踪:基于 SkyWalking 实现性能监控,特别是链路的追踪
+1. 分布式锁:基于 Redis 实现分布式锁,满足并发场景
+1. 幂等组件:基于 Redis 实现幂等组件,解决重复请求问题
### 研发工具
@@ -45,7 +47,6 @@
1. 代码生成:前后端代码的生成(Java、Vue、SQL),支持 CRUD 下载
1. 系统接口:基于 Swagger 自动生成相关的 RESTful API 接口文档
1. 数据库文档:基于 Screw 自动生成数据库文档
-1. 幂等组件:基于 Redis 实现幂等组件,解决重复请求问题
## 在线体验
diff --git a/http-client.env.json b/http-client.env.json
index 761939bbe..ad661c281 100644
--- a/http-client.env.json
+++ b/http-client.env.json
@@ -1,6 +1,6 @@
{
"local": {
- "baseUrl": "http://127.0.0.1:8080/api",
+ "baseUrl": "http://127.0.0.1:48080/api",
"token": "test1"
}
}
diff --git a/pom.xml b/pom.xml
index e58e34a27..09b8f70e3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,6 +30,10 @@
1.2.4
3.4.1
3.14.1
+
+ 1.7.0
+
+ 2.2.0
8.3.0
2.3.1
@@ -125,7 +129,7 @@
com.ctrip.framework.apollo
apollo-client
- 1.7.0
+ ${apollo.version}
@@ -134,6 +138,13 @@
spring-boot-starter-quartz
+
+
+ com.baomidou
+ lock4j-redisson-spring-boot-starter
+ ${lock4j.version}
+
+
org.apache.skywalking
@@ -217,7 +228,7 @@
cn.smallbun.screw
- screw-core
+ screw-core
${screw.version}
diff --git a/src/main/java/cn/iocoder/dashboard/common/exception/enums/GlobalErrorCodeConstants.java b/src/main/java/cn/iocoder/dashboard/common/exception/enums/GlobalErrorCodeConstants.java
index a57a8120c..d08121242 100644
--- a/src/main/java/cn/iocoder/dashboard/common/exception/enums/GlobalErrorCodeConstants.java
+++ b/src/main/java/cn/iocoder/dashboard/common/exception/enums/GlobalErrorCodeConstants.java
@@ -29,7 +29,8 @@ public interface GlobalErrorCodeConstants {
ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
// ========== 自定义错误段 ==========
- ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求");
+ ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求
+ ErrorCode CONCURRENCY_REQUESTS = new ErrorCode(901, "请求失败,请稍后重试"); // 并发请求,不允许
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
diff --git a/src/main/java/cn/iocoder/dashboard/framework/lock4j/config/Lock4jConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/lock4j/config/Lock4jConfiguration.java
new file mode 100644
index 000000000..12dc59d2d
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/lock4j/config/Lock4jConfiguration.java
@@ -0,0 +1,23 @@
+package cn.iocoder.dashboard.framework.lock4j.config;
+
+import cn.hutool.core.util.ClassUtil;
+import cn.iocoder.dashboard.framework.lock4j.core.DefaultLockFailureStrategy;
+import cn.iocoder.dashboard.framework.lock4j.core.Lock4jRedisKeyConstants;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class Lock4jConfiguration {
+
+ static {
+ // 手动加载 Lock4jRedisKeyConstants 类,因为它不会被使用到
+ // 如果不加载,会导致 Redis 监控,看到它的 Redis Key 枚举
+ ClassUtil.loadClass(Lock4jRedisKeyConstants.class.getName());
+ }
+
+ @Bean
+ public DefaultLockFailureStrategy lockFailureStrategy() {
+ return new DefaultLockFailureStrategy();
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/lock4j/core/DefaultLockFailureStrategy.java b/src/main/java/cn/iocoder/dashboard/framework/lock4j/core/DefaultLockFailureStrategy.java
new file mode 100644
index 000000000..7f1b19cb4
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/lock4j/core/DefaultLockFailureStrategy.java
@@ -0,0 +1,20 @@
+package cn.iocoder.dashboard.framework.lock4j.core;
+
+import cn.iocoder.dashboard.common.exception.ServiceException;
+import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
+import com.baomidou.lock.LockFailureStrategy;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 自定义获取锁失败策略,抛出 {@link cn.iocoder.dashboard.common.exception.ServiceException} 异常
+ */
+@Slf4j
+public class DefaultLockFailureStrategy implements LockFailureStrategy {
+
+ @Override
+ public void onLockFailure(String key, long acquireTimeout, int acquireCount) {
+ log.debug("[onLockFailure][线程:{} 获取锁失败,key:{} 获取超时时长:{} ms]", Thread.currentThread().getName(), key, acquireTimeout);
+ throw new ServiceException(GlobalErrorCodeConstants.CONCURRENCY_REQUESTS);
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/lock4j/core/Lock4jRedisKeyConstants.java b/src/main/java/cn/iocoder/dashboard/framework/lock4j/core/Lock4jRedisKeyConstants.java
new file mode 100644
index 000000000..33917dad4
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/lock4j/core/Lock4jRedisKeyConstants.java
@@ -0,0 +1,19 @@
+package cn.iocoder.dashboard.framework.lock4j.core;
+
+import cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine;
+import org.redisson.api.RLock;
+
+import static cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine.KeyTypeEnum.HASH;
+
+/**
+ * Lock4j Redis Key 枚举类
+ *
+ * @author 芋道源码
+ */
+public interface Lock4jRedisKeyConstants {
+
+ RedisKeyDefine LOCK4J = new RedisKeyDefine("分布式锁",
+ "lock4j:%s", // 参数来自 DefaultLockKeyBuilder 类
+ HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/lock4j/package-info.java b/src/main/java/cn/iocoder/dashboard/framework/lock4j/package-info.java
new file mode 100644
index 000000000..600093955
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/lock4j/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 分布式锁组件,使用 https://gitee.com/baomidou/lock4j 开源项目
+ */
+package cn.iocoder.dashboard.framework.lock4j;
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/SysRedisKeyConstants.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/SysRedisKeyConstants.java
index 729b734d8..5c6be6b33 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/SysRedisKeyConstants.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/SysRedisKeyConstants.java
@@ -8,7 +8,7 @@ import java.time.Duration;
import static cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
/**
- * Redis Key 枚举类
+ * System Redis Key 枚举类
*
* @author 芋道源码
*/
diff --git a/src/main/java/cn/iocoder/dashboard/modules/tool/controller/test/ToolTestDemoController.http b/src/main/java/cn/iocoder/dashboard/modules/tool/controller/test/ToolTestDemoController.http
new file mode 100644
index 000000000..b9d9fea9b
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/tool/controller/test/ToolTestDemoController.http
@@ -0,0 +1,3 @@
+### 请求 /get-permission-info 接口 => 成功
+GET {{baseUrl}}/tool/test-demo/get?id=1
+Authorization: Bearer {{token}}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/tool/controller/test/ToolTestDemoController.java b/src/main/java/cn/iocoder/dashboard/modules/tool/controller/test/ToolTestDemoController.java
index ba42b4c58..e89b35f90 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/tool/controller/test/ToolTestDemoController.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/tool/controller/test/ToolTestDemoController.java
@@ -1,5 +1,6 @@
package cn.iocoder.dashboard.modules.tool.controller.test;
+import cn.hutool.core.thread.ThreadUtil;
import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.common.pojo.PageResult;
import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils;
@@ -8,6 +9,7 @@ import cn.iocoder.dashboard.modules.tool.controller.test.vo.*;
import cn.iocoder.dashboard.modules.tool.convert.test.ToolTestDemoConvert;
import cn.iocoder.dashboard.modules.tool.dal.dataobject.test.ToolTestDemoDO;
import cn.iocoder.dashboard.modules.tool.service.test.ToolTestDemoService;
+import com.baomidou.lock.annotation.Lock4j;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
@@ -21,6 +23,7 @@ import javax.validation.Valid;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
import static cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateTypeEnum.EXPORT;
@@ -62,7 +65,11 @@ public class ToolTestDemoController {
@ApiOperation("获得测试示例")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('tool:test-demo:query')")
+ @Lock4j // 分布式锁
public CommonResult getTestDemo(@RequestParam("id") Long id) {
+ if (true) { // 测试分布式锁
+ ThreadUtil.sleep(5, TimeUnit.SECONDS);
+ }
ToolTestDemoDO testDemo = testDemoService.getTestDemo(id);
return success(ToolTestDemoConvert.INSTANCE.convert(testDemo));
}
diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml
index 8fa9210b0..08209bfa9 100644
--- a/src/main/resources/application-dev.yaml
+++ b/src/main/resources/application-dev.yaml
@@ -81,6 +81,13 @@ apollo:
username: ${spring.datasource.username}
password: ${spring.datasource.password}
+--- #################### 服务保障相关配置 ####################
+
+# Lock4j 配置项
+lock4j:
+ acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
+ expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
+
--- #################### 监控相关配置 ####################
# Actuator 监控端点的配置项
diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml
index 878f5e377..6a5b2995b 100644
--- a/src/main/resources/application-local.yaml
+++ b/src/main/resources/application-local.yaml
@@ -81,6 +81,13 @@ apollo:
username: ${spring.datasource.username}
password: ${spring.datasource.password}
+--- #################### 服务保障相关配置 ####################
+
+# Lock4j 配置项
+lock4j:
+ acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
+ expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
+
--- #################### 监控相关配置 ####################
# Actuator 监控端点的配置项