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 监控端点的配置项