diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DocumentEnum.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DocumentEnum.java new file mode 100644 index 000000000..b304462e5 --- /dev/null +++ b/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; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/config/YudaoMQAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/config/YudaoMQAutoConfiguration.java index 488734947..b7422b8d5 100644 --- a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/config/YudaoMQAutoConfiguration.java +++ b/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; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; 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.interceptor.RedisMessageInterceptor; 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.context.annotation.Bean; 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.ObjectRecord; import org.springframework.data.redis.connection.stream.ReadOffset; 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.StringRedisTemplate; 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 java.util.List; +import java.util.Properties; /** * 消息队列配置类 @@ -73,6 +79,7 @@ public class YudaoMQAutoConfiguration { public StreamMessageListenerContainer> redisStreamMessageListenerContainer( RedisMQTemplate redisMQTemplate, List> listeners) { RedisTemplate redisTemplate = redisMQTemplate.getRedisTemplate(); + checkRedisVersion(redisTemplate); // 第一步,创建 StreamMessageListenerContainer 容器 // 创建 options 配置 StreamMessageListenerContainer.StreamMessageListenerContainerOptions> containerOptions = @@ -118,4 +125,19 @@ public class YudaoMQAutoConfiguration { return String.format("%s@%d", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID()); } + /** + * 校验 Redis 版本号,是否满足最低的版本号要求! + */ + private static void checkRedisVersion(RedisTemplate redisTemplate) { + // 获得 Redis 版本 + Properties info = redisTemplate.execute((RedisCallback) 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())); + } + } + } diff --git a/yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImplTest.java b/yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImplTest.java index 5c06dae7a..226469466 100755 --- a/yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImplTest.java +++ b/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; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 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.TenantPackagePageReqVO; 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.mysql.tenant.TenantPackageMapper; import cn.iocoder.yudao.module.system.test.BaseDbUnitTest; import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; 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.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.RandomUtils.randomLongId; 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.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * {@link TenantPackageServiceImpl} 的单元测试类 @@ -34,6 +42,9 @@ public class TenantPackageServiceImplTest extends BaseDbUnitTest { @Resource private TenantPackageMapper tenantPackageMapper; + @MockBean + private TenantService tenantService; + @Test public void testCreateTenantPackage_success() { // 准备参数 @@ -57,12 +68,21 @@ public class TenantPackageServiceImplTest extends BaseDbUnitTest { TenantPackageUpdateReqVO reqVO = randomPojo(TenantPackageUpdateReqVO.class, o -> { 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); // 校验是否更新正确 TenantPackageDO tenantPackage = tenantPackageMapper.selectById(reqVO.getId()); // 获取最新的 assertPojoEquals(reqVO, tenantPackage); + // 校验调用租户的菜单 + verify(tenantService).updateTenantRoleMenu(eq(tenantId01), eq(reqVO.getMenuIds())); + verify(tenantService).updateTenantRoleMenu(eq(tenantId02), eq(reqVO.getMenuIds())); } @Test @@ -81,6 +101,8 @@ public class TenantPackageServiceImplTest extends BaseDbUnitTest { tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据 // 准备参数 Long id = dbTenantPackage.getId(); + // mock 租户未使用该套餐 + when(tenantService.getTenantCountByPackageId(eq(id))).thenReturn(0); // 调用 tenantPackageService.deleteTenantPackage(id); @@ -97,31 +119,45 @@ public class TenantPackageServiceImplTest extends BaseDbUnitTest { 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() { // mock 数据 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); // 测试 name 不匹配 - tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setName(null))); + tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setName("源码"))); // 测试 status 不匹配 - tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setStatus(null))); + tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); // 测试 remark 不匹配 - tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setRemark(null))); + tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setRemark("解析"))); // 测试 createTime 不匹配 - tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setCreateTime(null))); + tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setCreateTime(buildTime(2022, 11, 11)))); // 准备参数 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 pageResult = tenantPackageService.getTenantPackagePage(reqVO); @@ -131,4 +167,37 @@ public class TenantPackageServiceImplTest extends BaseDbUnitTest { 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()); + } } diff --git a/yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceTest.java b/yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java similarity index 87% rename from yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceTest.java rename to yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java index a2b2d79a3..8f529f076 100644 --- a/yudao-module-system/yudao-module-system-impl/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceTest.java +++ b/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; +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.TenantExportReqVO; 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.dal.dataobject.tenant.TenantDO; 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 org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import javax.annotation.Resource; 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.object.ObjectUtils.cloneIgnoreId; 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.RandomUtils.*; +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.TENANT_NOT_EXISTS; import static org.junit.jupiter.api.Assertions.*; +/** + * {@link TenantServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ @Import(TenantServiceImpl.class) -public class TenantServiceTest extends BaseDbUnitTest { +public class TenantServiceImplTest extends BaseDbUnitTest { @Resource private TenantServiceImpl tenantService; @@ -32,6 +44,21 @@ public class TenantServiceTest extends BaseDbUnitTest { @Resource 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 public void testCreateTenant_success() { // 准备参数 diff --git a/更新日志.md b/更新日志.md index 0c4d3a958..30e60d451 100644 --- a/更新日志.md +++ b/更新日志.md @@ -32,6 +32,7 @@ TODO * 【新增】新增 `@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/2598c033a95d4b61d5f5ab3da5f1414f25c510d6) +* 【优化】Redis 最低版本 5.0.0 检测,解决搭建环境过程中无法理解 XREADGROUP 指令的报错 [commit]() ### 🐞 Bug Fixes