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

This commit is contained in:
YunaiV 2022-02-26 00:41:27 +08:00
parent c64bb81cae
commit 66ebb71b8a
5 changed files with 158 additions and 19 deletions

View File

@ -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;
}

View File

@ -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()));
}
}
} }

View File

@ -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.setName("芋道源码");
o.setStatus(null); o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setRemark(null); o.setRemark("源码解析");
o.setCreateTime(null); 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.setName("芋道");
reqVO.setStatus(null); reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
reqVO.setRemark(null); reqVO.setRemark("源码");
reqVO.setBeginCreateTime(null); reqVO.setBeginCreateTime(buildTime(2022, 10, 9));
reqVO.setEndCreateTime(null); 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());
}
} }

View File

@ -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.module.system.mq.producer.tenant.TenantProducer;
import cn.iocoder.yudao.framework.common.pojo.PageResult; 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() {
// 准备参数 // 准备参数

View File

@ -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