Browse Source

Redis 最低版本 5.0.0 检测,解决搭建环境过程中无法理解 XREADGROUP 指令的报错

YunaiV 3 years ago
parent
commit
66ebb71b8a

+ 20 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DocumentEnum.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.framework.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 文档地址
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum DocumentEnum {
+
+    REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档");
+
+    private final String url;
+    private final String memo;
+
+}

+ 22 - 0
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/config/YudaoMQAutoConfiguration.java

@@ -1,6 +1,9 @@
 package cn.iocoder.yudao.framework.mq.config;
 package cn.iocoder.yudao.framework.mq.config;
 
 
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.hutool.system.SystemUtil;
 import cn.hutool.system.SystemUtil;
+import cn.iocoder.yudao.framework.common.enums.DocumentEnum;
 import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
 import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
 import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
 import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
 import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
 import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
@@ -10,10 +13,12 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.autoconfigure.AutoConfigureAfter;
 import org.springframework.boot.autoconfigure.AutoConfigureAfter;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisServerCommands;
 import org.springframework.data.redis.connection.stream.Consumer;
 import org.springframework.data.redis.connection.stream.Consumer;
 import org.springframework.data.redis.connection.stream.ObjectRecord;
 import org.springframework.data.redis.connection.stream.ObjectRecord;
 import org.springframework.data.redis.connection.stream.ReadOffset;
 import org.springframework.data.redis.connection.stream.ReadOffset;
 import org.springframework.data.redis.connection.stream.StreamOffset;
 import org.springframework.data.redis.connection.stream.StreamOffset;
+import org.springframework.data.redis.core.RedisCallback;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.data.redis.listener.ChannelTopic;
 import org.springframework.data.redis.listener.ChannelTopic;
@@ -22,6 +27,7 @@ import org.springframework.data.redis.stream.DefaultStreamMessageListenerContain
 import org.springframework.data.redis.stream.StreamMessageListenerContainer;
 import org.springframework.data.redis.stream.StreamMessageListenerContainer;
 
 
 import java.util.List;
 import java.util.List;
+import java.util.Properties;
 
 
 /**
 /**
  * 消息队列配置类
  * 消息队列配置类
@@ -73,6 +79,7 @@ public class YudaoMQAutoConfiguration {
     public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(
     public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(
             RedisMQTemplate redisMQTemplate, List<AbstractStreamMessageListener<?>> listeners) {
             RedisMQTemplate redisMQTemplate, List<AbstractStreamMessageListener<?>> listeners) {
         RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate();
         RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate();
+        checkRedisVersion(redisTemplate);
         // 第一步,创建 StreamMessageListenerContainer 容器
         // 第一步,创建 StreamMessageListenerContainer 容器
         // 创建 options 配置
         // 创建 options 配置
         StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, String>> containerOptions =
         StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, String>> containerOptions =
@@ -118,4 +125,19 @@ public class YudaoMQAutoConfiguration {
         return String.format("%s@%d", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID());
         return String.format("%s@%d", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID());
     }
     }
 
 
+    /**
+     * 校验 Redis 版本号,是否满足最低的版本号要求!
+     */
+    private static void checkRedisVersion(RedisTemplate<String, ?> redisTemplate) {
+        // 获得 Redis 版本
+        Properties info = redisTemplate.execute((RedisCallback<Properties>) RedisServerCommands::info);
+        String version = MapUtil.getStr(info, "redis_version");
+        // 校验最低版本必须大于等于 5.0.0
+        int majorVersion = Integer.parseInt(StrUtil.subBefore(version, '.', false));
+        if (majorVersion < 7) {
+            throw new IllegalStateException(StrUtil.format("您当前的 Redis 版本为 {},小于最低要求的 5.0.0 版本!" +
+                    "请参考 {} 文档进行安装。", version, DocumentEnum.REDIS_INSTALL.getUrl()));
+        }
+    }
+
 }
 }

+ 84 - 15
yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImplTest.java

@@ -1,24 +1,32 @@
 package cn.iocoder.yudao.module.system.service.tenant;
 package cn.iocoder.yudao.module.system.service.tenant;
 
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageUpdateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageUpdateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
 import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantPackageMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantPackageMapper;
 import cn.iocoder.yudao.module.system.test.BaseDbUnitTest;
 import cn.iocoder.yudao.module.system.test.BaseDbUnitTest;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.Import;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
 
 
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.TENANT_PACKAGE_NOT_EXISTS;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
+import static java.util.Arrays.asList;
 import static org.junit.jupiter.api.Assertions.*;
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 
 /**
 /**
 * {@link TenantPackageServiceImpl} 的单元测试类
 * {@link TenantPackageServiceImpl} 的单元测试类
@@ -34,6 +42,9 @@ public class TenantPackageServiceImplTest extends BaseDbUnitTest {
     @Resource
     @Resource
     private TenantPackageMapper tenantPackageMapper;
     private TenantPackageMapper tenantPackageMapper;
 
 
+    @MockBean
+    private TenantService tenantService;
+
     @Test
     @Test
     public void testCreateTenantPackage_success() {
     public void testCreateTenantPackage_success() {
         // 准备参数
         // 准备参数
@@ -57,12 +68,21 @@ public class TenantPackageServiceImplTest extends BaseDbUnitTest {
         TenantPackageUpdateReqVO reqVO = randomPojo(TenantPackageUpdateReqVO.class, o -> {
         TenantPackageUpdateReqVO reqVO = randomPojo(TenantPackageUpdateReqVO.class, o -> {
             o.setId(dbTenantPackage.getId()); // 设置更新的 ID
             o.setId(dbTenantPackage.getId()); // 设置更新的 ID
         });
         });
+        // mock 方法
+        Long tenantId01 = randomLongId();
+        Long tenantId02 = randomLongId();
+        when(tenantService.getTenantListByPackageId(eq(reqVO.getId()))).thenReturn(
+                asList(randomPojo(TenantDO.class, o -> o.setId(tenantId01)),
+                        randomPojo(TenantDO.class, o -> o.setId(tenantId02))));
 
 
         // 调用
         // 调用
         tenantPackageService.updateTenantPackage(reqVO);
         tenantPackageService.updateTenantPackage(reqVO);
         // 校验是否更新正确
         // 校验是否更新正确
         TenantPackageDO tenantPackage = tenantPackageMapper.selectById(reqVO.getId()); // 获取最新的
         TenantPackageDO tenantPackage = tenantPackageMapper.selectById(reqVO.getId()); // 获取最新的
         assertPojoEquals(reqVO, tenantPackage);
         assertPojoEquals(reqVO, tenantPackage);
+        // 校验调用租户的菜单
+        verify(tenantService).updateTenantRoleMenu(eq(tenantId01), eq(reqVO.getMenuIds()));
+        verify(tenantService).updateTenantRoleMenu(eq(tenantId02), eq(reqVO.getMenuIds()));
     }
     }
 
 
     @Test
     @Test
@@ -81,6 +101,8 @@ public class TenantPackageServiceImplTest extends BaseDbUnitTest {
         tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据
         tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据
         // 准备参数
         // 准备参数
         Long id = dbTenantPackage.getId();
         Long id = dbTenantPackage.getId();
+        // mock 租户未使用该套餐
+        when(tenantService.getTenantCountByPackageId(eq(id))).thenReturn(0);
 
 
         // 调用
         // 调用
         tenantPackageService.deleteTenantPackage(id);
         tenantPackageService.deleteTenantPackage(id);
@@ -97,31 +119,45 @@ public class TenantPackageServiceImplTest extends BaseDbUnitTest {
         assertServiceException(() -> tenantPackageService.deleteTenantPackage(id), TENANT_PACKAGE_NOT_EXISTS);
         assertServiceException(() -> tenantPackageService.deleteTenantPackage(id), TENANT_PACKAGE_NOT_EXISTS);
     }
     }
 
 
-    @Test // TODO 请修改 null 为需要的值
+    @Test
+    public void testDeleteTenantPackage_used() {
+        // mock 数据
+        TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class);
+        tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbTenantPackage.getId();
+        // mock 租户在使用该套餐
+        when(tenantService.getTenantCountByPackageId(eq(id))).thenReturn(1);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> tenantPackageService.deleteTenantPackage(id), TENANT_PACKAGE_USED);
+    }
+
+    @Test
     public void testGetTenantPackagePage() {
     public void testGetTenantPackagePage() {
        // mock 数据
        // mock 数据
        TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class, o -> { // 等会查询到
        TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class, o -> { // 等会查询到
-           o.setName(null);
-           o.setStatus(null);
-           o.setRemark(null);
-           o.setCreateTime(null);
+           o.setName("芋道源码");
+           o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+           o.setRemark("源码解析");
+           o.setCreateTime(buildTime(2022, 10, 10));
        });
        });
        tenantPackageMapper.insert(dbTenantPackage);
        tenantPackageMapper.insert(dbTenantPackage);
        // 测试 name 不匹配
        // 测试 name 不匹配
-       tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setName(null)));
+       tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setName("源码")));
        // 测试 status 不匹配
        // 测试 status 不匹配
-       tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setStatus(null)));
+       tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
        // 测试 remark 不匹配
        // 测试 remark 不匹配
-       tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setRemark(null)));
+       tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setRemark("解析")));
        // 测试 createTime 不匹配
        // 测试 createTime 不匹配
-       tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setCreateTime(null)));
+       tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setCreateTime(buildTime(2022, 11, 11))));
        // 准备参数
        // 准备参数
        TenantPackagePageReqVO reqVO = new TenantPackagePageReqVO();
        TenantPackagePageReqVO reqVO = new TenantPackagePageReqVO();
-       reqVO.setName(null);
-       reqVO.setStatus(null);
-       reqVO.setRemark(null);
-       reqVO.setBeginCreateTime(null);
-       reqVO.setEndCreateTime(null);
+       reqVO.setName("芋道");
+       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+       reqVO.setRemark("源码");
+       reqVO.setBeginCreateTime(buildTime(2022, 10, 9));
+       reqVO.setEndCreateTime(buildTime(2022, 10, 11));
 
 
        // 调用
        // 调用
        PageResult<TenantPackageDO> pageResult = tenantPackageService.getTenantPackagePage(reqVO);
        PageResult<TenantPackageDO> pageResult = tenantPackageService.getTenantPackagePage(reqVO);
@@ -131,4 +167,37 @@ public class TenantPackageServiceImplTest extends BaseDbUnitTest {
        assertPojoEquals(dbTenantPackage, pageResult.getList().get(0));
        assertPojoEquals(dbTenantPackage, pageResult.getList().get(0));
     }
     }
 
 
+    @Test
+    public void testValidTenantPackage_success() {
+        // mock 数据
+        TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class,
+                o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据
+
+        // 调用
+        TenantPackageDO result = tenantPackageService.validTenantPackage(dbTenantPackage.getId());
+        // 断言
+        assertPojoEquals(dbTenantPackage, result);
+    }
+
+    @Test
+    public void testValidTenantPackage_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> tenantPackageService.validTenantPackage(id), TENANT_PACKAGE_NOT_EXISTS);
+    }
+
+    @Test
+    public void testValidTenantPackage_disable() {
+        // mock 数据
+        TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class,
+                o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
+        tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据
+
+        // 调用, 并断言异常
+        assertServiceException(() -> tenantPackageService.validTenantPackage(dbTenantPackage.getId()),
+                TENANT_PACKAGE_DISABLE, dbTenantPackage.getName());
+    }
 }
 }

+ 31 - 4
yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceTest.java → yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java

@@ -1,30 +1,42 @@
 package cn.iocoder.yudao.module.system.service.tenant;
 package cn.iocoder.yudao.module.system.service.tenant;
 
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantUpdateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantUpdateReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
 import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper;
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.mq.producer.tenant.TenantProducer;
+import cn.iocoder.yudao.module.system.service.permission.MenuService;
+import cn.iocoder.yudao.module.system.service.permission.PermissionService;
+import cn.iocoder.yudao.module.system.service.permission.RoleService;
+import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 import cn.iocoder.yudao.module.system.test.BaseDbUnitTest;
 import cn.iocoder.yudao.module.system.test.BaseDbUnitTest;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.Import;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
 import java.util.List;
 import java.util.List;
 
 
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.TENANT_NOT_EXISTS;
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.TENANT_NOT_EXISTS;
 import static org.junit.jupiter.api.Assertions.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 
+/**
+ * {@link TenantServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
 @Import(TenantServiceImpl.class)
 @Import(TenantServiceImpl.class)
-public class TenantServiceTest extends BaseDbUnitTest {
+public class TenantServiceImplTest extends BaseDbUnitTest {
 
 
     @Resource
     @Resource
     private TenantServiceImpl tenantService;
     private TenantServiceImpl tenantService;
@@ -32,6 +44,21 @@ public class TenantServiceTest extends BaseDbUnitTest {
     @Resource
     @Resource
     private TenantMapper tenantMapper;
     private TenantMapper tenantMapper;
 
 
+    @MockBean
+    private TenantProperties tenantProperties;
+    @MockBean
+    private TenantPackageService tenantPackageService;
+    @MockBean
+    private AdminUserService userService;
+    @MockBean
+    private RoleService roleService;
+    @MockBean
+    private MenuService menuService;
+    @MockBean
+    private PermissionService permissionService;
+    @MockBean
+    private TenantProducer tenantProducer;
+
     @Test
     @Test
     public void testCreateTenant_success() {
     public void testCreateTenant_success() {
         // 准备参数
         // 准备参数

+ 1 - 0
更新日志.md

@@ -32,6 +32,7 @@ TODO
 * 【新增】新增 `@TenantIgnore` 注解,标记指定方法,忽略多租户的自动过滤,适合实现跨租户的逻辑 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/4d53944771c66b563da1e3d68d3ba43405af8a06)
 * 【新增】新增 `@TenantIgnore` 注解,标记指定方法,忽略多租户的自动过滤,适合实现跨租户的逻辑 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/4d53944771c66b563da1e3d68d3ba43405af8a06)
 * 【新增】租户套餐的管理,可配置每个租户的可使用的功能权限 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/6b6d676a6baa2dad16ae9bf03d5002209064c8cc)
 * 【新增】租户套餐的管理,可配置每个租户的可使用的功能权限 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/6b6d676a6baa2dad16ae9bf03d5002209064c8cc)
 * 【优化】新建租户时,自动创建对应的管理员账号、角色等基础信息 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/2598c033a95d4b61d5f5ab3da5f1414f25c510d6)
 * 【优化】新建租户时,自动创建对应的管理员账号、角色等基础信息 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/2598c033a95d4b61d5f5ab3da5f1414f25c510d6)
+* 【优化】Redis 最低版本 5.0.0 检测,解决搭建环境过程中无法理解 XREADGROUP 指令的报错 [commit]()
 
 
 ### 🐞 Bug Fixes
 ### 🐞 Bug Fixes