Prechádzať zdrojové kódy

feature(使用redis缓存): 功能合并
1、缓存改为Redis缓存,不在启动时缓存,使用时候根据数据源缓存
2、前端登录接口调整

lwf 1 rok pred
rodič
commit
414c1be128
79 zmenil súbory, kde vykonal 1693 pridanie a 2962 odobranie
  1. 5 0
      yudao-framework/yudao-spring-boot-starter-redis/pom.xml
  2. 27 4
      yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java
  3. 13 2
      yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoRedisAutoConfiguration.java
  4. 51 0
      yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/TimeoutRedisCacheManager.java
  5. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/permission/PermissionApiImpl.java
  6. 18 26
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
  7. 48 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java
  8. 3 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java
  9. 17 9
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java
  10. 5 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dept/DeptMapper.java
  11. 3 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/MenuMapper.java
  12. 8 6
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuMapper.java
  13. 86 14
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java
  14. 0 41
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/common/CaptchaRedisDAO.java
  15. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java
  16. 0 29
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/auth/OAuth2ClientRefreshConsumer.java
  17. 0 29
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/dept/DeptRefreshConsumer.java
  18. 0 31
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailAccountRefreshConsumer.java
  19. 0 29
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailTemplateRefreshConsumer.java
  20. 0 29
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/notify/NotifyTemplateRefreshConsumer.java
  21. 0 29
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/permission/MenuRefreshConsumer.java
  22. 0 29
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/permission/RoleMenuRefreshConsumer.java
  23. 0 29
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/permission/RoleRefreshConsumer.java
  24. 0 29
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/permission/UserRoleRefreshConsumer.java
  25. 0 29
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sms/SmsTemplateRefreshConsumer.java
  26. 0 21
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/auth/OAuth2ClientRefreshMessage.java
  27. 0 21
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/dept/DeptRefreshMessage.java
  28. 0 21
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailAccountRefreshMessage.java
  29. 0 21
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailTemplateRefreshMessage.java
  30. 0 21
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/notify/NotifyTemplateRefreshMessage.java
  31. 0 21
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/permission/MenuRefreshMessage.java
  32. 0 21
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/permission/RoleMenuRefreshMessage.java
  33. 0 21
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/permission/RoleRefreshMessage.java
  34. 0 21
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/permission/UserRoleRefreshMessage.java
  35. 0 21
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/sms/SmsTemplateRefreshMessage.java
  36. 0 26
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/auth/OAuth2ClientProducer.java
  37. 0 26
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/dept/DeptProducer.java
  38. 0 18
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java
  39. 0 33
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/notify/NotifyProducer.java
  40. 0 26
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/permission/MenuProducer.java
  41. 0 35
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/permission/PermissionProducer.java
  42. 0 28
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/permission/RoleProducer.java
  43. 2 11
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/sms/SmsProducer.java
  44. 24 25
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java
  45. 69 131
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
  46. 8 13
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailAccountService.java
  47. 12 41
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImpl.java
  48. 0 5
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateService.java
  49. 16 40
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImpl.java
  50. 8 13
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/notify/NotifyTemplateService.java
  51. 17 43
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/notify/NotifyTemplateServiceImpl.java
  52. 10 7
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientService.java
  53. 27 43
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImpl.java
  54. 10 31
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java
  55. 31 110
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java
  56. 63 59
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionService.java
  57. 150 253
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java
  58. 25 26
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java
  59. 83 105
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java
  60. 15 36
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateService.java
  61. 22 57
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImpl.java
  62. 2 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java
  63. 5 4
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java
  64. 1 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
  65. 113 167
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImplTest.java
  66. 27 51
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImplTest.java
  67. 37 62
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImplTest.java
  68. 36 46
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/notify/NotifyTemplateServiceImplTest.java
  69. 75 83
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImplTest.java
  70. 14 126
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImplTest.java
  71. 312 330
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java
  72. 119 132
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImplTest.java
  73. 48 75
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImplTest.java
  74. 2 2
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java
  75. 2 2
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java
  76. 0 9
      yudao-ui-admin/src/api/menu.js
  77. 1 1
      yudao-ui-admin/src/api/system/permission.js
  78. 3 2
      yudao-ui-admin/src/permission.js
  79. 18 16
      yudao-ui-admin/src/store/modules/permission.js

+ 5 - 0
yudao-framework/yudao-spring-boot-starter-redis/pom.xml

@@ -37,6 +37,11 @@
             <artifactId>netty-all</artifactId>
         </dependency>
 
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 27 - 4
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.framework.redis.config;
 
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.cache.CacheProperties;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -7,8 +9,15 @@ import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Primary;
 import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.serializer.RedisSerializationContext;
-import org.springframework.data.redis.serializer.RedisSerializer;
+
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration.buildRedisSerializer;
 
 /**
  * Cache 配置类,基于 Redis 实现
@@ -20,15 +29,19 @@ public class YudaoCacheAutoConfiguration {
 
     /**
      * RedisCacheConfiguration Bean
-     *
+     * <p>
      * 参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 的 createConfiguration 方法
      */
     @Bean
     @Primary
     public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
-        // 设置使用 JSON 序列化方式
         RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
-        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
+        // 设置使用 : 单冒号,而不是双 :: 冒号,避免 Redis Desktop Manager 多余空格
+        // 详细可见 https://blog.csdn.net/chuixue24/article/details/103928965 博客
+        config = config.computePrefixWith(cacheName -> cacheName + StrUtil.COLON);
+        // 设置使用 JSON 序列化方式
+        config = config.serializeValuesWith(
+                RedisSerializationContext.SerializationPair.fromSerializer(buildRedisSerializer()));
 
         // 设置 CacheProperties.Redis 的属性
         CacheProperties.Redis redisProperties = cacheProperties.getRedis();
@@ -47,4 +60,14 @@ public class YudaoCacheAutoConfiguration {
         return config;
     }
 
+    @Bean
+    public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate,
+                                               RedisCacheConfiguration redisCacheConfiguration) {
+        // 创建 RedisCacheWriter 对象
+        RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
+        RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
+        // 创建 TenantRedisCacheManager 对象
+        return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
+    }
+
 }

+ 13 - 2
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoRedisAutoConfiguration.java

@@ -1,5 +1,8 @@
 package cn.iocoder.yudao.framework.redis.config;
 
+import cn.hutool.core.util.ReflectUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.context.annotation.Bean;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -25,9 +28,17 @@ public class YudaoRedisAutoConfiguration {
         template.setKeySerializer(RedisSerializer.string());
         template.setHashKeySerializer(RedisSerializer.string());
         // 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
-        template.setValueSerializer(RedisSerializer.json());
-        template.setHashValueSerializer(RedisSerializer.json());
+        template.setValueSerializer(buildRedisSerializer());
+        template.setHashValueSerializer(buildRedisSerializer());
         return template;
     }
 
+    public static RedisSerializer<?> buildRedisSerializer() {
+        RedisSerializer<Object> json = RedisSerializer.json();
+        // 解决 LocalDateTime 的序列化
+        ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
+        objectMapper.registerModules(new JavaTimeModule());
+        return json;
+    }
+
 }

+ 51 - 0
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/TimeoutRedisCacheManager.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.framework.redis.core;
+
+import cn.hutool.core.util.StrUtil;
+import org.springframework.boot.convert.DurationStyle;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.data.redis.cache.RedisCache;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * 支持自定义过期时间的 {@link RedisCacheManager} 实现类
+ *
+ * 在 {@link Cacheable#cacheNames()} 格式为 "key#ttl" 时,# 后面的 ttl 为过期时间,单位为秒
+ *
+ * @author 芋道源码
+ */
+public class TimeoutRedisCacheManager extends RedisCacheManager {
+
+    private static final String SPLIT = "#";
+
+    public TimeoutRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
+        super(cacheWriter, defaultCacheConfiguration);
+    }
+
+    @Override
+    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
+        if (StrUtil.isEmpty(name)) {
+            return super.createRedisCache(name, cacheConfig);
+        }
+        // 如果使用 # 分隔,大小不为 2,则说明不使用自定义过期时间
+        String[] names = StrUtil.splitToArray(name, SPLIT);
+        if (names.length != 2) {
+            return super.createRedisCache(name, cacheConfig);
+        }
+
+        // 核心:通过修改 cacheConfig 的过期时间,实现自定义过期时间
+        if (cacheConfig != null) {
+            // 移除 # 后面的 : 以及后面的内容,避免影响解析
+            names[1] = StrUtil.subBefore(names[1], StrUtil.COLON, false);
+            // 解析时间
+            Duration duration = DurationStyle.detectAndParse(names[1], ChronoUnit.SECONDS);
+            cacheConfig = cacheConfig.entryTtl(duration);
+        }
+        return super.createRedisCache(names[0], cacheConfig);
+    }
+
+}

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/permission/PermissionApiImpl.java

@@ -21,7 +21,7 @@ public class PermissionApiImpl implements PermissionApi {
 
     @Override
     public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
-        return permissionService.getUserRoleIdListByRoleIds(roleIds);
+        return permissionService.getUserRoleIdListByRoleId(roleIds);
     }
 
     @Override

+ 18 - 26
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.controller.admin.auth;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
@@ -12,8 +11,8 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
-import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
 import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
+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.social.SocialUserService;
@@ -34,9 +33,9 @@ import java.util.List;
 import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;
-import static java.util.Collections.singleton;
 
 @Tag(name = "管理后台 - 认证")
 @RestController
@@ -52,6 +51,8 @@ public class AuthController {
     @Resource
     private RoleService roleService;
     @Resource
+    private MenuService menuService;
+    @Resource
     private PermissionService permissionService;
     @Resource
     private SocialUserService socialUserService;
@@ -90,33 +91,24 @@ public class AuthController {
     @GetMapping("/get-permission-info")
     @Operation(summary = "获取登录用户的权限信息")
     public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
-        // 获得用户信息
+        // 1.1 获得用户信息
         AdminUserDO user = userService.getUser(getLoginUserId());
         if (user == null) {
             return null;
         }
-        // 获得角色列表
-        Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
-        List<RoleDO> roleList = roleService.getRoleListFromCache(roleIds);
-        // 获得菜单列表
-        List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
-                SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()),
-                singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
-        // 拼接结果返回
-        return success(AuthConvert.INSTANCE.convert(user, roleList, menuList));
-    }
 
-    @GetMapping("/list-menus")
-    @Operation(summary = "获得登录用户的菜单列表")
-    public CommonResult<List<AuthMenuRespVO>> getMenuList() {
-        // 获得角色列表
-        Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
-        // 获得用户拥有的菜单列表
-        List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
-                SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
-                singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
-        // 转换成 Tree 结构返回
-        return success(AuthConvert.INSTANCE.buildMenuTree(menuList));
+        // 1.2 获得角色列表
+        Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(getLoginUserId());
+        List<RoleDO> roles = roleService.getRoleList(roleIds);
+        roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色
+
+        // 1.3 获得菜单列表
+        Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId));
+        List<MenuDO> menuList = menuService.getMenuList(menuIds);
+        menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单
+
+        // 2. 拼接结果返回
+        return success(AuthConvert.INSTANCE.convert(user, roles, menuList));
     }
 
     // ========== 短信登录相关 ==========
@@ -148,7 +140,7 @@ public class AuthController {
             @Parameter(name = "redirectUri", description = "回调路径")
     })
     public CommonResult<String> socialLogin(@RequestParam("type") Integer type,
-                                                    @RequestParam("redirectUri") String redirectUri) {
+                                            @RequestParam("redirectUri") String redirectUri) {
         return CommonResult.success(socialUserService.getAuthorizeUrl(type, redirectUri));
     }
 

+ 48 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java

@@ -6,6 +6,7 @@ import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import java.util.List;
 import java.util.Set;
 
 @Schema(description = "管理后台 - 登录用户的权限信息 Response VO,额外包括用户信息和角色列表")
@@ -24,6 +25,9 @@ public class AuthPermissionInfoRespVO {
     @Schema(description = "操作权限数组", requiredMode = Schema.RequiredMode.REQUIRED)
     private Set<String> permissions;
 
+    @Schema(description = "菜单树", required = true)
+    private List<MenuVO> menus;
+
     @Schema(description = "用户信息 VO")
     @Data
     @NoArgsConstructor
@@ -42,4 +46,48 @@ public class AuthPermissionInfoRespVO {
 
     }
 
+    @Schema(description = "管理后台 - 登录用户的菜单信息 Response VO")
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @Builder
+    public static class MenuVO {
+
+        @Schema(description = "菜单名称", required = true, example = "芋道")
+        private Long id;
+
+        @Schema(description = "父菜单 ID", required = true, example = "1024")
+        private Long parentId;
+
+        @Schema(description = "菜单名称", required = true, example = "芋道")
+        private String name;
+
+        @Schema(description = "路由地址,仅菜单类型为菜单或者目录时,才需要传", example = "post")
+        private String path;
+
+        @Schema(description = "组件路径,仅菜单类型为菜单时,才需要传", example = "system/post/index")
+        private String component;
+
+        @Schema(description = "组件名", example = "SystemUser")
+        private String componentName;
+
+        @Schema(description = "菜单图标,仅菜单类型为菜单或者目录时,才需要传", example = "/menu/list")
+        private String icon;
+
+        @Schema(description = "是否可见", required = true, example = "false")
+        private Boolean visible;
+
+        @Schema(description = "是否缓存", required = true, example = "false")
+        private Boolean keepAlive;
+
+        @Schema(description = "是否总是显示", example = "false")
+        private Boolean alwaysShow;
+
+        /**
+         * 子路由
+         */
+        private List<MenuVO> children;
+
+    }
+
 }

+ 3 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java

@@ -37,10 +37,10 @@ public class PermissionController {
 
     @Operation(summary = "获得角色拥有的菜单编号")
     @Parameter(name = "roleId", description = "角色编号", required = true)
-    @GetMapping("/list-role-resources")
+    @GetMapping("/list-role-menus")
     @PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')")
-    public CommonResult<Set<Long>> listRoleMenus(Long roleId) {
-        return success(permissionService.getRoleMenuIds(roleId));
+    public CommonResult<Set<Long>> getRoleMenuList(Long roleId) {
+        return success(permissionService.getRoleMenuListByRoleId(roleId));
     }
 
     @PostMapping("/assign-role-menu")

+ 17 - 9
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java

@@ -9,12 +9,14 @@ import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
+import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 import org.slf4j.LoggerFactory;
 
 import java.util.*;
 
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
 import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
 
@@ -27,13 +29,16 @@ public interface AuthConvert {
 
     default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
         return AuthPermissionInfoRespVO.builder()
-            .user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build())
-            .roles(CollectionUtils.convertSet(roleList, RoleDO::getCode))
-            .permissions(CollectionUtils.convertSet(menuList, MenuDO::getPermission))
-            .build();
+                .user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build())
+                .roles(CollectionUtils.convertSet(roleList, RoleDO::getCode))
+                // 权限标识信息
+                .permissions(convertSet(menuList, MenuDO::getPermission))
+                // 菜单树
+                .menus(buildMenuTree(menuList))
+                .build();
     }
 
-    AuthMenuRespVO convertTreeNode(MenuDO menu);
+    AuthPermissionInfoRespVO.MenuVO convertTreeNode(MenuDO menu);
 
     /**
      * 将菜单列表,构建成菜单树
@@ -41,20 +46,23 @@ public interface AuthConvert {
      * @param menuList 菜单列表
      * @return 菜单树
      */
-    default List<AuthMenuRespVO> buildMenuTree(List<MenuDO> menuList) {
+    default List<AuthPermissionInfoRespVO.MenuVO> buildMenuTree(List<MenuDO> menuList) {
+        // 移除按钮
+        menuList.removeIf(menu -> menu.getType().equals(MenuTypeEnum.BUTTON.getType()));
         // 排序,保证菜单的有序性
         menuList.sort(Comparator.comparing(MenuDO::getSort));
+
         // 构建菜单树
         // 使用 LinkedHashMap 的原因,是为了排序 。实际也可以用 Stream API ,就是太丑了。
-        Map<Long, AuthMenuRespVO> treeNodeMap = new LinkedHashMap<>();
+        Map<Long, AuthPermissionInfoRespVO.MenuVO> treeNodeMap = new LinkedHashMap<>();
         menuList.forEach(menu -> treeNodeMap.put(menu.getId(), AuthConvert.INSTANCE.convertTreeNode(menu)));
         // 处理父子关系
         treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(ID_ROOT)).forEach(childNode -> {
             // 获得父节点
-            AuthMenuRespVO parentNode = treeNodeMap.get(childNode.getParentId());
+            AuthPermissionInfoRespVO.MenuVO parentNode = treeNodeMap.get(childNode.getParentId());
             if (parentNode == null) {
                 LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]",
-                    childNode.getId(), childNode.getParentId());
+                        childNode.getId(), childNode.getParentId());
                 return;
             }
             // 将自己添加到父节点中

+ 5 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dept/DeptMapper.java

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqV
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.Collection;
 import java.util.List;
 
 @Mapper
@@ -25,4 +26,8 @@ public interface DeptMapper extends BaseMapperX<DeptDO> {
         return selectCount(DeptDO::getParentId, parentId);
     }
 
+    default List<DeptDO> selectListByParentId(Collection<Long> parentIds) {
+        return selectList(DeptDO::getParentId, parentIds);
+    }
+
 }

+ 3 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/MenuMapper.java

@@ -25,4 +25,7 @@ public interface MenuMapper extends BaseMapperX<MenuDO> {
                 .eqIfPresent(MenuDO::getStatus, reqVO.getStatus()));
     }
 
+    default List<MenuDO> selectListByPermission(String permission) {
+        return selectList(MenuDO::getPermission, permission);
+    }
 }

+ 8 - 6
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuMapper.java

@@ -3,9 +3,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.permission;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import org.apache.ibatis.annotations.Mapper;
-import org.springframework.stereotype.Repository;
 
 import java.util.Collection;
 import java.util.List;
@@ -13,14 +11,18 @@ import java.util.List;
 @Mapper
 public interface RoleMenuMapper extends BaseMapperX<RoleMenuDO> {
 
-    @Repository
-    class BatchInsertMapper extends ServiceImpl<RoleMenuMapper, RoleMenuDO> {
-    }
-
     default List<RoleMenuDO> selectListByRoleId(Long roleId) {
         return selectList(RoleMenuDO::getRoleId, roleId);
     }
 
+    default List<RoleMenuDO> selectListByRoleId(Collection<Long> roleIds) {
+        return selectList(RoleMenuDO::getRoleId, roleIds);
+    }
+
+    default List<RoleMenuDO> selectListByMenuId(Long menuId) {
+        return selectList(RoleMenuDO::getMenuId, menuId);
+    }
+
     default void deleteListByRoleIdAndMenuIds(Long roleId, Collection<Long> menuIds) {
         delete(new LambdaQueryWrapper<RoleMenuDO>()
                 .eq(RoleMenuDO::getRoleId, roleId)

+ 86 - 14
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java

@@ -1,12 +1,7 @@
 package cn.iocoder.yudao.module.system.dal.redis;
 
-import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
 
-import java.time.Duration;
-
-import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
-
 /**
  * System Redis Key 枚举类
  *
@@ -14,16 +9,93 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S
  */
 public interface RedisKeyConstants {
 
-    RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("验证码的缓存",
-            "captcha_code:%s", // 参数为 uuid
-            STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
+    /**
+     * 指定部门的所有子部门编号数组的缓存
+     * <p>
+     * KEY 格式:dept_children_ids:{id}
+     * VALUE 数据类型:String 子部门编号集合
+     */
+    String DEPT_CHILDREN_ID_LIST = "dept_children_ids";
+
+    /**
+     * 角色的缓存
+     * <p>
+     * KEY 格式:role:{id}
+     * VALUE 数据类型:String 角色信息
+     */
+    String ROLE = "role";
+
+    /**
+     * 用户拥有的角色编号的缓存
+     * <p>
+     * KEY 格式:user_role_ids:{userId}
+     * VALUE 数据类型:String 角色编号集合
+     */
+    String USER_ROLE_ID_LIST = "user_role_ids";
+
+    /**
+     * 拥有指定菜单的角色编号的缓存
+     * <p>
+     * KEY 格式:user_role_ids:{menuId}
+     * VALUE 数据类型:String 角色编号集合
+     */
+    String MENU_ROLE_ID_LIST = "menu_role_ids";
+
+    /**
+     * 拥有权限对应的菜单编号数组的缓存
+     * <p>
+     * KEY 格式:permission_menu_ids:{permission}
+     * VALUE 数据类型:String 菜单编号数组
+     */
+    String PERMISSION_MENU_ID_LIST = "permission_menu_ids";
+
+    /**
+     * OAuth2 客户端的缓存
+     * <p>
+     * KEY 格式:user:{id}
+     * VALUE 数据类型:String 客户端信息
+     */
+    String OAUTH_CLIENT = "oauth_client";
+
+    /**
+     * 访问令牌的缓存
+     * <p>
+     * KEY 格式:oauth2_access_token:{token}
+     * VALUE 数据类型:String 访问令牌信息 {@link OAuth2AccessTokenDO}
+     * <p>
+     * 由于动态过期时间,使用 RedisTemplate 操作
+     */
+    String OAUTH2_ACCESS_TOKEN = "oauth2_access_token:%s";
+
+    /**
+     * 站内信模版的缓存
+     * <p>
+     * KEY 格式:notify_template:{code}
+     * VALUE 数据格式:String 模版信息
+     */
+    String NOTIFY_TEMPLATE = "notify_template";
 
-    RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("访问令牌的缓存",
-            "oauth2_access_token:%s", // 参数为访问令牌 token
-            STRING, OAuth2AccessTokenDO.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
+    /**
+     * 邮件账号的缓存
+     * <p>
+     * KEY 格式:sms_template:{id}
+     * VALUE 数据格式:String 账号信息
+     */
+    String MAIL_ACCOUNT = "mail_account";
 
-    RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
-            "social_auth_state:%s", // 参数为 state
-            STRING, String.class, Duration.ofHours(24)); // 值为 state
+    /**
+     * 邮件模版的缓存
+     * <p>
+     * KEY 格式:mail_template:{code}
+     * VALUE 数据格式:String 模版信息
+     */
+    String MAIL_TEMPLATE = "mail_template";
 
+    /**
+     * 短信模版的缓存
+     * <p>
+     * KEY 格式:sms_template:{id}
+     * VALUE 数据格式:String 模版信息
+     */
+    String SMS_TEMPLATE = "sms_template";
 }

+ 0 - 41
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/common/CaptchaRedisDAO.java

@@ -1,41 +0,0 @@
-package cn.iocoder.yudao.module.system.dal.redis.common;
-
-import org.springframework.data.redis.core.StringRedisTemplate;
-import org.springframework.stereotype.Repository;
-
-import javax.annotation.Resource;
-import java.time.Duration;
-
-import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.CAPTCHA_CODE;
-
-/**
- * 验证码的 Redis DAO
- *
- * @author 芋道源码
- */
-@Repository
-public class CaptchaRedisDAO {
-
-    @Resource
-    private StringRedisTemplate stringRedisTemplate;
-
-    public String get(String uuid) {
-        String redisKey = formatKey(uuid);
-        return stringRedisTemplate.opsForValue().get(redisKey);
-    }
-
-    public void set(String uuid, String code, Duration timeout) {
-        String redisKey = formatKey(uuid);
-        stringRedisTemplate.opsForValue().set(redisKey, code, timeout);
-    }
-
-    public void delete(String uuid) {
-        String redisKey = formatKey(uuid);
-        stringRedisTemplate.delete(redisKey);
-    }
-
-    private static String formatKey(String uuid) {
-        return String.format(CAPTCHA_CODE.getKeyTemplate(), uuid);
-    }
-
-}

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java

@@ -53,7 +53,7 @@ public class OAuth2AccessTokenRedisDAO {
     }
 
     private static String formatKey(String accessToken) {
-        return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken);
+        return String.format(OAUTH2_ACCESS_TOKEN, accessToken);
     }
 
 }

+ 0 - 29
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/auth/OAuth2ClientRefreshConsumer.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.consumer.auth;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
-import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage;
-import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * 针对 {@link OAuth2ClientRefreshMessage} 的消费者
- *
- * @author 芋道源码
- */
-@Component
-@Slf4j
-public class OAuth2ClientRefreshConsumer extends AbstractChannelMessageListener<OAuth2ClientRefreshMessage> {
-
-    @Resource
-    private OAuth2ClientService oauth2ClientService;
-
-    @Override
-    public void onMessage(OAuth2ClientRefreshMessage message) {
-        log.info("[onMessage][收到 OAuth2Client 刷新消息]");
-        oauth2ClientService.initLocalCache();
-    }
-
-}

+ 0 - 29
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/dept/DeptRefreshConsumer.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.consumer.dept;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
-import cn.iocoder.yudao.module.system.mq.message.dept.DeptRefreshMessage;
-import cn.iocoder.yudao.module.system.service.dept.DeptService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * 针对 {@link DeptRefreshMessage} 的消费者
- *
- * @author 芋道源码
- */
-@Component
-@Slf4j
-public class DeptRefreshConsumer extends AbstractChannelMessageListener<DeptRefreshMessage> {
-
-    @Resource
-    private DeptService deptService;
-
-    @Override
-    public void onMessage(DeptRefreshMessage message) {
-        log.info("[onMessage][收到 Dept 刷新消息]");
-        deptService.initLocalCache();
-    }
-
-}

+ 0 - 31
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailAccountRefreshConsumer.java

@@ -1,31 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.consumer.mail;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
-import cn.iocoder.yudao.module.system.mq.message.mail.MailAccountRefreshMessage;
-import cn.iocoder.yudao.module.system.mq.message.mail.MailTemplateRefreshMessage;
-import cn.iocoder.yudao.module.system.service.mail.MailAccountService;
-import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * 针对 {@link MailAccountRefreshMessage} 的消费者
- *
- * @author wangjingyi
- */
-@Component
-@Slf4j
-public class MailAccountRefreshConsumer extends AbstractChannelMessageListener<MailAccountRefreshMessage> {
-
-    @Resource
-    private MailAccountService mailAccountService;
-
-    @Override
-    public void onMessage(MailAccountRefreshMessage message) {
-        log.info("[onMessage][收到 Mail Account 刷新信息]");
-        mailAccountService.initLocalCache();
-    }
-
-}

+ 0 - 29
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailTemplateRefreshConsumer.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.consumer.mail;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
-import cn.iocoder.yudao.module.system.mq.message.mail.MailTemplateRefreshMessage;
-import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * 针对 {@link MailTemplateRefreshMessage} 的消费者
- *
- * @author wangjingyi
- */
-@Component
-@Slf4j
-public class MailTemplateRefreshConsumer extends AbstractChannelMessageListener<MailTemplateRefreshMessage> {
-
-    @Resource
-    private MailTemplateService mailTemplateService;
-
-    @Override
-    public void onMessage(MailTemplateRefreshMessage message) {
-        log.info("[onMessage][收到 Mail Template 刷新信息]");
-        mailTemplateService.initLocalCache();
-    }
-
-}

+ 0 - 29
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/notify/NotifyTemplateRefreshConsumer.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.consumer.notify;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
-import cn.iocoder.yudao.module.system.mq.message.notify.NotifyTemplateRefreshMessage;
-import cn.iocoder.yudao.module.system.service.notify.NotifyTemplateService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * 针对 {@link NotifyTemplateRefreshMessage} 的消费者
- *
- * @author xrcoder
- */
-@Component
-@Slf4j
-public class NotifyTemplateRefreshConsumer extends AbstractChannelMessageListener<NotifyTemplateRefreshMessage> {
-
-    @Resource
-    private NotifyTemplateService notifyTemplateService;
-
-    @Override
-    public void onMessage(NotifyTemplateRefreshMessage message) {
-        log.info("[onMessage][收到 NotifyTemplate 刷新消息]");
-        notifyTemplateService.initLocalCache();
-    }
-
-}

+ 0 - 29
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/permission/MenuRefreshConsumer.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.consumer.permission;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
-import cn.iocoder.yudao.module.system.mq.message.permission.MenuRefreshMessage;
-import cn.iocoder.yudao.module.system.service.permission.MenuService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * 针对 {@link MenuRefreshMessage} 的消费者
- *
- * @author 芋道源码
- */
-@Component
-@Slf4j
-public class MenuRefreshConsumer extends AbstractChannelMessageListener<MenuRefreshMessage> {
-
-    @Resource
-    private MenuService menuService;
-
-    @Override
-    public void onMessage(MenuRefreshMessage message) {
-        log.info("[onMessage][收到 Menu 刷新消息]");
-        menuService.initLocalCache();
-    }
-
-}

+ 0 - 29
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/permission/RoleMenuRefreshConsumer.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.consumer.permission;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
-import cn.iocoder.yudao.module.system.mq.message.permission.RoleMenuRefreshMessage;
-import cn.iocoder.yudao.module.system.service.permission.PermissionService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * 针对 {@link RoleMenuRefreshMessage} 的消费者
- *
- * @author 芋道源码
- */
-@Component
-@Slf4j
-public class RoleMenuRefreshConsumer extends AbstractChannelMessageListener<RoleMenuRefreshMessage> {
-
-    @Resource
-    private PermissionService permissionService;
-
-    @Override
-    public void onMessage(RoleMenuRefreshMessage message) {
-        log.info("[onMessage][收到 Role 与 Menu 的关联刷新消息]");
-        permissionService.initLocalCache();
-    }
-
-}

+ 0 - 29
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/permission/RoleRefreshConsumer.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.consumer.permission;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
-import cn.iocoder.yudao.module.system.mq.message.permission.RoleRefreshMessage;
-import cn.iocoder.yudao.module.system.service.permission.RoleService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * 针对 {@link RoleRefreshMessage} 的消费者
- *
- * @author 芋道源码
- */
-@Component
-@Slf4j
-public class RoleRefreshConsumer extends AbstractChannelMessageListener<RoleRefreshMessage> {
-
-    @Resource
-    private RoleService roleService;
-
-    @Override
-    public void onMessage(RoleRefreshMessage message) {
-        log.info("[onMessage][收到 Role 刷新消息]");
-        roleService.initLocalCache();
-    }
-
-}

+ 0 - 29
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/permission/UserRoleRefreshConsumer.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.consumer.permission;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
-import cn.iocoder.yudao.module.system.mq.message.permission.UserRoleRefreshMessage;
-import cn.iocoder.yudao.module.system.service.permission.PermissionService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * 针对 {@link UserRoleRefreshMessage} 的消费者
- *
- * @author 芋道源码
- */
-@Component
-@Slf4j
-public class UserRoleRefreshConsumer extends AbstractChannelMessageListener<UserRoleRefreshMessage> {
-
-    @Resource
-    private PermissionService permissionService;
-
-    @Override
-    public void onMessage(UserRoleRefreshMessage message) {
-        log.info("[onMessage][收到 User 与 Role 的关联刷新消息]");
-        permissionService.initLocalCache();
-    }
-
-}

+ 0 - 29
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sms/SmsTemplateRefreshConsumer.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.consumer.sms;
-
-import cn.iocoder.yudao.module.system.mq.message.sms.SmsTemplateRefreshMessage;
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
-import cn.iocoder.yudao.module.system.service.sms.SmsTemplateService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * 针对 {@link SmsTemplateRefreshMessage} 的消费者
- *
- * @author 芋道源码
- */
-@Component
-@Slf4j
-public class SmsTemplateRefreshConsumer extends AbstractChannelMessageListener<SmsTemplateRefreshMessage> {
-
-    @Resource
-    private SmsTemplateService smsTemplateService;
-
-    @Override
-    public void onMessage(SmsTemplateRefreshMessage message) {
-        log.info("[onMessage][收到 SmsTemplate 刷新消息]");
-        smsTemplateService.initLocalCache();
-    }
-
-}

+ 0 - 21
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/auth/OAuth2ClientRefreshMessage.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.message.auth;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * OAuth 2.0 客户端的数据刷新 Message
- *
- * @author 芋道源码
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class OAuth2ClientRefreshMessage extends AbstractChannelMessage {
-
-    @Override
-    public String getChannel() {
-        return "system.oauth2-client.refresh";
-    }
-
-}

+ 0 - 21
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/dept/DeptRefreshMessage.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.message.dept;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * 部门数据刷新 Message
- *
- * @author 芋道源码
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class DeptRefreshMessage extends AbstractChannelMessage {
-
-    @Override
-    public String getChannel() {
-        return "system.dept.refresh";
-    }
-
-}

+ 0 - 21
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailAccountRefreshMessage.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.message.mail;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * 邮箱账号的数据刷新 Message
- *
- * @author wangjingyi
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class MailAccountRefreshMessage extends AbstractChannelMessage {
-
-    @Override
-    public String getChannel() {
-        return "system.mail-account.refresh";
-    }
-
-}

+ 0 - 21
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailTemplateRefreshMessage.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.message.mail;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * 邮箱模板的数据刷新 Message
- *
- * @author wangjingyi
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class MailTemplateRefreshMessage extends AbstractChannelMessage {
-
-    @Override
-    public String getChannel() {
-        return "system.mail-template.refresh";
-    }
-
-}

+ 0 - 21
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/notify/NotifyTemplateRefreshMessage.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.message.notify;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * 站内信模板的数据刷新 Message
- *
- * @author xrcoder
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class NotifyTemplateRefreshMessage extends AbstractChannelMessage {
-
-    @Override
-    public String getChannel() {
-        return "system.notify-template.refresh";
-    }
-
-}

+ 0 - 21
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/permission/MenuRefreshMessage.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.message.permission;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * 菜单数据刷新 Message
- *
- * @author 芋道源码
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class MenuRefreshMessage extends AbstractChannelMessage {
-
-    @Override
-    public String getChannel() {
-        return "system.menu.refresh";
-    }
-
-}

+ 0 - 21
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/permission/RoleMenuRefreshMessage.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.message.permission;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * 角色与菜单数据刷新 Message
- *
- * @author 芋道源码
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class RoleMenuRefreshMessage extends AbstractChannelMessage {
-
-    @Override
-    public String getChannel() {
-        return "system.role-menu.refresh";
-    }
-
-}

+ 0 - 21
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/permission/RoleRefreshMessage.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.message.permission;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * 角色数据刷新 Message
- *
- * @author 芋道源码
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class RoleRefreshMessage extends AbstractChannelMessage {
-
-    @Override
-    public String getChannel() {
-        return "system.role.refresh";
-    }
-
-}

+ 0 - 21
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/permission/UserRoleRefreshMessage.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.message.permission;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * 用户与角色的数据刷新 Message
- *
- * @author 芋道源码
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class UserRoleRefreshMessage extends AbstractChannelMessage {
-
-    @Override
-    public String getChannel() {
-        return "system.user-role.refresh";
-    }
-
-}

+ 0 - 21
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/sms/SmsTemplateRefreshMessage.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.message.sms;
-
-import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * 短信模板的数据刷新 Message
- *
- * @author 芋道源码
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-public class SmsTemplateRefreshMessage extends AbstractChannelMessage {
-
-    @Override
-    public String getChannel() {
-        return "system.sms-template.refresh";
-    }
-
-}

+ 0 - 26
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/auth/OAuth2ClientProducer.java

@@ -1,26 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.producer.auth;
-
-import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
-import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * OAuth 2.0 客户端相关消息的 Producer
- */
-@Component
-public class OAuth2ClientProducer {
-
-    @Resource
-    private RedisMQTemplate redisMQTemplate;
-
-    /**
-     * 发送 {@link OAuth2ClientRefreshMessage} 消息
-     */
-    public void sendOAuth2ClientRefreshMessage() {
-        OAuth2ClientRefreshMessage message = new OAuth2ClientRefreshMessage();
-        redisMQTemplate.send(message);
-    }
-
-}

+ 0 - 26
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/dept/DeptProducer.java

@@ -1,26 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.producer.dept;
-
-import cn.iocoder.yudao.module.system.mq.message.dept.DeptRefreshMessage;
-import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * Dept 部门相关消息的 Producer
- */
-@Component
-public class DeptProducer {
-
-    @Resource
-    private RedisMQTemplate redisMQTemplate;
-
-    /**
-     * 发送 {@link DeptRefreshMessage} 消息
-     */
-    public void sendDeptRefreshMessage() {
-        DeptRefreshMessage message = new DeptRefreshMessage();
-        redisMQTemplate.send(message);
-    }
-
-}

+ 0 - 18
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java

@@ -1,9 +1,7 @@
 package cn.iocoder.yudao.module.system.mq.producer.mail;
 
 import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
-import cn.iocoder.yudao.module.system.mq.message.mail.MailAccountRefreshMessage;
 import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
-import cn.iocoder.yudao.module.system.mq.message.mail.MailTemplateRefreshMessage;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
@@ -22,22 +20,6 @@ public class MailProducer {
     @Resource
     private RedisMQTemplate redisMQTemplate;
 
-    /**
-     * 发送 {@link MailTemplateRefreshMessage} 消息
-     */
-    public void sendMailTemplateRefreshMessage() {
-        MailTemplateRefreshMessage message = new MailTemplateRefreshMessage();
-        redisMQTemplate.send(message);
-    }
-
-    /**
-     * 发送 {@link MailAccountRefreshMessage} 消息
-     */
-    public void sendMailAccountRefreshMessage() {
-        MailAccountRefreshMessage message = new MailAccountRefreshMessage();
-        redisMQTemplate.send(message);
-    }
-
     /**
      * 发送 {@link MailSendMessage} 消息
      *

+ 0 - 33
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/notify/NotifyProducer.java

@@ -1,33 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.producer.notify;
-
-import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
-import cn.iocoder.yudao.module.system.mq.message.notify.NotifyTemplateRefreshMessage;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * Notify 站内信相关消息的 Producer
- *
- * @author xrcoder
- * @since 2022-08-06
- */
-@Slf4j
-@Component
-public class NotifyProducer {
-
-    @Resource
-    private RedisMQTemplate redisMQTemplate;
-
-
-    /**
-     * 发送 {@link NotifyTemplateRefreshMessage} 消息
-     */
-    public void sendNotifyTemplateRefreshMessage() {
-        NotifyTemplateRefreshMessage message = new NotifyTemplateRefreshMessage();
-        redisMQTemplate.send(message);
-    }
-
-
-}

+ 0 - 26
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/permission/MenuProducer.java

@@ -1,26 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.producer.permission;
-
-import cn.iocoder.yudao.module.system.mq.message.permission.MenuRefreshMessage;
-import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * Menu 菜单相关消息的 Producer
- */
-@Component
-public class MenuProducer {
-
-    @Resource
-    private RedisMQTemplate redisMQTemplate;
-
-    /**
-     * 发送 {@link MenuRefreshMessage} 消息
-     */
-    public void sendMenuRefreshMessage() {
-        MenuRefreshMessage message = new MenuRefreshMessage();
-        redisMQTemplate.send(message);
-    }
-
-}

+ 0 - 35
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/permission/PermissionProducer.java

@@ -1,35 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.producer.permission;
-
-import cn.iocoder.yudao.module.system.mq.message.permission.RoleMenuRefreshMessage;
-import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
-import cn.iocoder.yudao.module.system.mq.message.permission.UserRoleRefreshMessage;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * Permission 权限相关消息的 Producer
- */
-@Component
-public class PermissionProducer {
-
-    @Resource
-    private RedisMQTemplate redisMQTemplate;
-
-    /**
-     * 发送 {@link RoleMenuRefreshMessage} 消息
-     */
-    public void sendRoleMenuRefreshMessage() {
-        RoleMenuRefreshMessage message = new RoleMenuRefreshMessage();
-        redisMQTemplate.send(message);
-    }
-
-    /**
-     * 发送 {@link UserRoleRefreshMessage} 消息
-     */
-    public void sendUserRoleRefreshMessage() {
-        UserRoleRefreshMessage message = new UserRoleRefreshMessage();
-        redisMQTemplate.send(message);
-    }
-
-}

+ 0 - 28
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/permission/RoleProducer.java

@@ -1,28 +0,0 @@
-package cn.iocoder.yudao.module.system.mq.producer.permission;
-
-import cn.iocoder.yudao.module.system.mq.message.permission.RoleRefreshMessage;
-import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * Role 角色相关消息的 Producer
- *
- * @author 芋道源码
- */
-@Component
-public class RoleProducer {
-
-    @Resource
-    private RedisMQTemplate redisMQTemplate;
-
-    /**
-     * 发送 {@link RoleRefreshMessage} 消息
-     */
-    public void sendRoleRefreshMessage() {
-        RoleRefreshMessage message = new RoleRefreshMessage();
-        redisMQTemplate.send(message);
-    }
-
-}

+ 2 - 11
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/sms/SmsProducer.java

@@ -1,10 +1,9 @@
 package cn.iocoder.yudao.module.system.mq.producer.sms;
 
 import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
 import cn.iocoder.yudao.module.system.mq.message.sms.SmsChannelRefreshMessage;
 import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
-import cn.iocoder.yudao.module.system.mq.message.sms.SmsTemplateRefreshMessage;
-import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
@@ -15,7 +14,7 @@ import java.util.List;
  * Sms 短信相关消息的 Producer
  *
  * @author zzf
- * @date 2021/3/9 16:35
+ * @since 2021/3/9 16:35
  */
 @Slf4j
 @Component
@@ -32,14 +31,6 @@ public class SmsProducer {
         redisMQTemplate.send(message);
     }
 
-    /**
-     * 发送 {@link SmsTemplateRefreshMessage} 消息
-     */
-    public void sendSmsTemplateRefreshMessage() {
-        SmsTemplateRefreshMessage message = new SmsTemplateRefreshMessage();
-        redisMQTemplate.send(message);
-    }
-
     /**
      * 发送 {@link SmsSendMessage} 消息
      *

+ 24 - 25
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java

@@ -7,10 +7,7 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqV
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 /**
  * 部门 Service 接口
@@ -19,11 +16,6 @@ import java.util.Map;
  */
 public interface DeptService {
 
-    /**
-     * 初始化部门的本地缓存
-     */
-    void initLocalCache();
-
     /**
      * 创建部门
      *
@@ -47,21 +39,12 @@ public interface DeptService {
     void deleteDept(Long id);
 
     /**
-     * 筛选部门列表
-     *
-     * @param reqVO 筛选条件请求 VO
-     * @return 部门列表
-     */
-    List<DeptDO> getDeptList(DeptListReqVO reqVO);
-
-    /**
-     * 获得所有子部门,从缓存中
+     * 获得部门信息
      *
-     * @param parentId 部门编号
-     * @param recursive 是否递归获取所有
-     * @return 子部门列表
+     * @param id 部门编号
+     * @return 部门信息
      */
-    List<DeptDO> getDeptListByParentIdFromCache(Long parentId, boolean recursive);
+    DeptDO getDept(Long id);
 
     /**
      * 获得部门信息数组
@@ -71,6 +54,14 @@ public interface DeptService {
      */
     List<DeptDO> getDeptList(Collection<Long> ids);
 
+    /**
+     * 筛选部门列表
+     *
+     * @param reqVO 筛选条件请求 VO
+     * @return 部门列表
+     */
+    List<DeptDO> getDeptList(DeptListReqVO reqVO);
+
     /**
      * 获得指定编号的部门 Map
      *
@@ -86,12 +77,20 @@ public interface DeptService {
     }
 
     /**
-     * 获得部门信息
+     * 获得指定部门的所有子部门
      *
      * @param id 部门编号
-     * @return 部门信息
+     * @return 子部门列表
      */
-    DeptDO getDept(Long id);
+    List<DeptDO> getChildDeptList(Long id);
+
+    /**
+     * 获得所有子部门,从缓存中
+     *
+     * @param id 父部门编号
+     * @return 子部门列表
+     */
+    Set<Long> getChildDeptIdListFromCache(Long id);
 
     /**
      * 校验部门们是否有效。如下情况,视为无效:

+ 69 - 131
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java

@@ -1,30 +1,29 @@
 package cn.iocoder.yudao.module.system.service.dept;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
-import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
+import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
 import cn.iocoder.yudao.module.system.convert.dept.DeptConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
 import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
+import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
 import cn.iocoder.yudao.module.system.enums.dept.DeptIdEnum;
-import cn.iocoder.yudao.module.system.mq.producer.dept.DeptProducer;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.Multimap;
-import lombok.Getter;
+import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 
 /**
@@ -37,55 +36,12 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 @Slf4j
 public class DeptServiceImpl implements DeptService {
 
-    /**
-     * 部门缓存
-     * key:部门编号 {@link DeptDO#getId()}
-     *
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    @Getter
-    private volatile Map<Long, DeptDO> deptCache;
-    /**
-     * 父部门缓存
-     * key:部门编号 {@link DeptDO#getParentId()}
-     * value: 直接子部门列表
-     *
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    @Getter
-    private volatile Multimap<Long, DeptDO> parentDeptCache;
-
     @Resource
     private DeptMapper deptMapper;
 
-    @Resource
-    private DeptProducer deptProducer;
-
-    /**
-     * 初始化 {@link #parentDeptCache} 和 {@link #deptCache} 缓存
-     */
-    @Override
-    @PostConstruct
-    public synchronized void initLocalCache() {
-        // 注意:忽略自动多租户,因为要全局初始化缓存
-        TenantUtils.executeIgnore(() -> {
-            // 第一步:查询数据
-            List<DeptDO> depts = deptMapper.selectList();
-            log.info("[initLocalCache][缓存部门,数量为:{}]", depts.size());
-
-            // 第二步:构建缓存
-            ImmutableMap.Builder<Long, DeptDO> builder = ImmutableMap.builder();
-            ImmutableMultimap.Builder<Long, DeptDO> parentBuilder = ImmutableMultimap.builder();
-            depts.forEach(deptDO -> {
-                builder.put(deptDO.getId(), deptDO);
-                parentBuilder.put(deptDO.getParentId(), deptDO);
-            });
-            deptCache = builder.build();
-            parentDeptCache = parentBuilder.build();
-        });
-    }
-
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
+            allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
     public Long createDept(DeptCreateReqVO reqVO) {
         // 校验正确性
         if (reqVO.getParentId() == null) {
@@ -95,12 +51,12 @@ public class DeptServiceImpl implements DeptService {
         // 插入部门
         DeptDO dept = DeptConvert.INSTANCE.convert(reqVO);
         deptMapper.insert(dept);
-        // 发送刷新消息
-        deptProducer.sendDeptRefreshMessage();
         return dept.getId();
     }
 
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
+            allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
     public void updateDept(DeptUpdateReqVO reqVO) {
         // 校验正确性
         if (reqVO.getParentId() == null) {
@@ -110,11 +66,11 @@ public class DeptServiceImpl implements DeptService {
         // 更新部门
         DeptDO updateObj = DeptConvert.INSTANCE.convert(reqVO);
         deptMapper.updateById(updateObj);
-        // 发送刷新消息
-        deptProducer.sendDeptRefreshMessage();
     }
 
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
+            allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
     public void deleteDept(Long id) {
         // 校验是否存在
         validateDeptExists(id);
@@ -124,70 +80,30 @@ public class DeptServiceImpl implements DeptService {
         }
         // 删除部门
         deptMapper.deleteById(id);
-        // 发送刷新消息
-        deptProducer.sendDeptRefreshMessage();
-    }
-
-    @Override
-    public List<DeptDO> getDeptList(DeptListReqVO reqVO) {
-        return deptMapper.selectList(reqVO);
-    }
-
-    @Override
-    public List<DeptDO> getDeptListByParentIdFromCache(Long parentId, boolean recursive) {
-        if (parentId == null) {
-            return Collections.emptyList();
-        }
-        List<DeptDO> result = new ArrayList<>();
-        // 递归,简单粗暴
-       getDeptsByParentIdFromCache(result, parentId,
-               recursive ? Integer.MAX_VALUE : 1, // 如果递归获取,则无限;否则,只递归 1 次
-               parentDeptCache);
-        return result;
-    }
-
-    /**
-     * 递归获取所有的子部门,添加到 result 结果
-     *
-     * @param result 结果
-     * @param parentId 父编号
-     * @param recursiveCount 递归次数
-     * @param parentDeptMap 父部门 Map,使用缓存,避免变化
-     */
-    private void getDeptsByParentIdFromCache(List<DeptDO> result, Long parentId, int recursiveCount,
-                                             Multimap<Long, DeptDO> parentDeptMap) {
-        // 递归次数为 0,结束!
-        if (recursiveCount == 0) {
-            return;
-        }
-
-        // 获得子部门
-        Collection<DeptDO> depts = parentDeptMap.get(parentId);
-        if (CollUtil.isEmpty(depts)) {
-            return;
-        }
-        // 针对多租户,过滤掉非当前租户的部门
-        Long tenantId = TenantContextHolder.getTenantId();
-        if (tenantId != null) {
-            depts = CollUtil.filterNew(depts, dept -> tenantId.equals(dept.getTenantId()));
-        }
-        result.addAll(depts);
-
-        // 继续递归
-        depts.forEach(dept -> getDeptsByParentIdFromCache(result, dept.getId(),
-                recursiveCount - 1, parentDeptMap));
     }
 
     private void validateForCreateOrUpdate(Long id, Long parentId, String name) {
         // 校验自己存在
         validateDeptExists(id);
         // 校验父部门的有效性
-        validateParentDeptEnable(id, parentId);
+        validateParentDept(id, parentId);
         // 校验部门名的唯一性
         validateDeptNameUnique(id, parentId, name);
     }
 
-    private void validateParentDeptEnable(Long id, Long parentId) {
+    @VisibleForTesting
+    void validateDeptExists(Long id) {
+        if (id == null) {
+            return;
+        }
+        DeptDO dept = deptMapper.selectById(id);
+        if (dept == null) {
+            throw exception(DEPT_NOT_FOUND);
+        }
+    }
+
+    @VisibleForTesting
+    void validateParentDept(Long id, Long parentId) {
         if (parentId == null || DeptIdEnum.ROOT.getId().equals(parentId)) {
             return;
         }
@@ -200,49 +116,71 @@ public class DeptServiceImpl implements DeptService {
         if (dept == null) {
             throw exception(DEPT_PARENT_NOT_EXITS);
         }
-        // 父部门被禁用
-        if (!CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) {
-            throw exception(DEPT_NOT_ENABLE);
-        }
         // 父部门不能是原来的子部门
-        List<DeptDO> children = getDeptListByParentIdFromCache(id, true);
+        List<DeptDO> children = getChildDeptList(id);
         if (children.stream().anyMatch(dept1 -> dept1.getId().equals(parentId))) {
             throw exception(DEPT_PARENT_IS_CHILD);
         }
     }
 
-    private void validateDeptExists(Long id) {
-        if (id == null) {
-            return;
-        }
-        DeptDO dept = deptMapper.selectById(id);
+    @VisibleForTesting
+    void validateDeptNameUnique(Long id, Long parentId, String name) {
+        DeptDO dept = deptMapper.selectByParentIdAndName(parentId, name);
         if (dept == null) {
-            throw exception(DEPT_NOT_FOUND);
-        }
-    }
-
-    private void validateDeptNameUnique(Long id, Long parentId, String name) {
-        DeptDO menu = deptMapper.selectByParentIdAndName(parentId, name);
-        if (menu == null) {
             return;
         }
         // 如果 id 为空,说明不用比较是否为相同 id 的岗位
         if (id == null) {
             throw exception(DEPT_NAME_DUPLICATE);
         }
-        if (!menu.getId().equals(id)) {
+        if (ObjectUtil.notEqual(dept.getId(), id)) {
             throw exception(DEPT_NAME_DUPLICATE);
         }
     }
 
+    @Override
+    public DeptDO getDept(Long id) {
+        return deptMapper.selectById(id);
+    }
+
     @Override
     public List<DeptDO> getDeptList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
         return deptMapper.selectBatchIds(ids);
     }
 
     @Override
-    public DeptDO getDept(Long id) {
-        return deptMapper.selectById(id);
+    public List<DeptDO> getDeptList(DeptListReqVO reqVO) {
+        return deptMapper.selectList(reqVO);
+    }
+
+    @Override
+    public List<DeptDO> getChildDeptList(Long id) {
+        List<DeptDO> children = new LinkedList<>();
+        // 遍历每一层
+        Collection<Long> parentIds = Collections.singleton(id);
+        for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下,存在死循环
+            // 查询当前层,所有的子部门
+            List<DeptDO> depts = deptMapper.selectListByParentId(parentIds);
+            // 1. 如果没有子部门,则结束遍历
+            if (CollUtil.isEmpty(depts)) {
+                break;
+            }
+            // 2. 如果有子部门,继续遍历
+            children.addAll(depts);
+            parentIds = convertSet(depts, DeptDO::getId);
+        }
+        return children;
+    }
+
+    @Override
+    @DataPermission(enable = false) // 禁用数据权限,避免简历不正确的缓存
+    @Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, key = "#id")
+    public Set<Long> getChildDeptIdListFromCache(Long id) {
+        List<DeptDO> children = getChildDeptList(id);
+        return convertSet(children, DeptDO::getId);
     }
 
     @Override

+ 8 - 13
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailAccountService.java

@@ -17,19 +17,6 @@ import java.util.List;
  */
 public interface MailAccountService {
 
-    /**
-     * 初始化邮箱账号的本地缓存
-     */
-    void initLocalCache();
-
-    /**
-     * 从缓存中获取邮箱账号
-     *
-     * @param id 编号
-     * @return 邮箱账号
-     */
-    MailAccountDO getMailAccountFromCache(Long id);
-
     /**
      * 创建邮箱账号
      *
@@ -60,6 +47,14 @@ public interface MailAccountService {
      */
     MailAccountDO getMailAccount(Long id);
 
+    /**
+     * 从缓存中获取邮箱账号
+     *
+     * @param id 编号
+     * @return 邮箱账号
+     */
+    MailAccountDO getMailAccountFromCache(Long id);
+
     /**
      * 获取邮箱账号分页信息
      *

+ 12 - 41
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImpl.java

@@ -7,20 +7,18 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccou
 import cn.iocoder.yudao.module.system.convert.mail.MailAccountConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
 import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
-import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
-import lombok.Getter;
+import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.util.List;
-import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS;
 
 /**
  * 邮箱账号 Service 实现类
@@ -39,46 +37,16 @@ public class MailAccountServiceImpl implements MailAccountService {
     @Resource
     private MailTemplateService mailTemplateService;
 
-    @Resource
-    private MailProducer mailProducer;
-
-    /**
-     * 邮箱账号缓存
-     * key:邮箱账号编码 {@link MailAccountDO#getId()}
-     *
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    @Getter
-    private volatile Map<Long, MailAccountDO> mailAccountCache;
-
-    @Override
-    @PostConstruct
-    public void initLocalCache() {
-        // 第一步:查询数据
-        List<MailAccountDO> accounts = mailAccountMapper.selectList();
-        log.info("[initLocalCache][缓存邮箱账号,数量:{}]", accounts.size());
-
-        // 第二步:构建缓存
-        mailAccountCache = convertMap(accounts, MailAccountDO::getId);
-    }
-
-    @Override
-    public MailAccountDO getMailAccountFromCache(Long id) {
-        return mailAccountCache.get(id);
-    }
-
     @Override
     public Long createMailAccount(MailAccountCreateReqVO createReqVO) {
         // 插入
         MailAccountDO account = MailAccountConvert.INSTANCE.convert(createReqVO);
         mailAccountMapper.insert(account);
-
-        // 发送刷新消息
-        mailProducer.sendMailAccountRefreshMessage();
         return account.getId();
     }
 
     @Override
+    @Cacheable(value = RedisKeyConstants.MAIL_ACCOUNT, key = "#updateReqVO.id")
     public void updateMailAccount(MailAccountUpdateReqVO updateReqVO) {
         // 校验是否存在
         validateMailAccountExists(updateReqVO.getId());
@@ -86,11 +54,10 @@ public class MailAccountServiceImpl implements MailAccountService {
         // 更新
         MailAccountDO updateObj = MailAccountConvert.INSTANCE.convert(updateReqVO);
         mailAccountMapper.updateById(updateObj);
-        // 发送刷新消息
-        mailProducer.sendMailAccountRefreshMessage();
     }
 
     @Override
+    @Cacheable(value = RedisKeyConstants.MAIL_ACCOUNT, key = "#id")
     public void deleteMailAccount(Long id) {
         // 校验是否存在账号
         validateMailAccountExists(id);
@@ -101,8 +68,6 @@ public class MailAccountServiceImpl implements MailAccountService {
 
         // 删除
         mailAccountMapper.deleteById(id);
-        // 发送刷新消息
-        mailProducer.sendMailAccountRefreshMessage();
     }
 
     private void validateMailAccountExists(Long id) {
@@ -116,6 +81,12 @@ public class MailAccountServiceImpl implements MailAccountService {
         return mailAccountMapper.selectById(id);
     }
 
+    @Override
+    @Cacheable(value = RedisKeyConstants.MAIL_ACCOUNT, key = "#id", unless = "#result == null")
+    public MailAccountDO getMailAccountFromCache(Long id) {
+        return getMailAccount(id);
+    }
+
     @Override
     public PageResult<MailAccountDO> getMailAccountPage(MailAccountPageReqVO pageReqVO) {
         return mailAccountMapper.selectPage(pageReqVO);

+ 0 - 5
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateService.java

@@ -18,11 +18,6 @@ import java.util.Map;
  */
 public interface MailTemplateService {
 
-    /**
-     * 初始化邮件模版的本地缓存
-     */
-    void initLocalCache();
-
     /**
      * 邮件模版创建
      *

+ 16 - 40
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImpl.java

@@ -10,14 +10,14 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemp
 import cn.iocoder.yudao.module.system.convert.mail.MailTemplateConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
 import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
-import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
+import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
 import com.google.common.annotations.VisibleForTesting;
-import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import javax.validation.Valid;
 import java.util.List;
@@ -25,8 +25,8 @@ import java.util.Map;
 import java.util.regex.Pattern;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_CODE_EXISTS;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_NOT_EXISTS;
 
 /**
  * 邮箱模版 Service 实现类
@@ -47,29 +47,6 @@ public class MailTemplateServiceImpl implements MailTemplateService {
     @Resource
     private MailTemplateMapper mailTemplateMapper;
 
-    @Resource
-    private MailProducer mailProducer;
-
-    /**
-     * 邮件模板缓存
-     * key:邮件模版标识 {@link MailTemplateDO#getCode()}
-     *
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    @Getter
-    private volatile Map<String, MailTemplateDO> mailTemplateCache;
-
-    @Override
-    @PostConstruct
-    public void initLocalCache() {
-        // 第一步:查询数据
-        List<MailTemplateDO> templates = mailTemplateMapper.selectList();
-        log.info("[initLocalCache][缓存邮件模版,数量:{}]", templates.size());
-
-        // 第二步:构建缓存
-        mailTemplateCache = convertMap(templates, MailTemplateDO::getCode);
-    }
-
     @Override
     public Long createMailTemplate(MailTemplateCreateReqVO createReqVO) {
         // 校验 code 是否唯一
@@ -79,12 +56,12 @@ public class MailTemplateServiceImpl implements MailTemplateService {
         MailTemplateDO template = MailTemplateConvert.INSTANCE.convert(createReqVO)
                 .setParams(parseTemplateContentParams(createReqVO.getContent()));
         mailTemplateMapper.insert(template);
-        // 发送刷新消息
-        mailProducer.sendMailTemplateRefreshMessage();
         return template.getId();
     }
 
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,
+            allEntries = true) // allEntries 清空所有缓存,因为可能修改到 code 字段,不好清理
     public void updateMailTemplate(@Valid MailTemplateUpdateReqVO updateReqVO) {
         // 校验是否存在
         validateMailTemplateExists(updateReqVO.getId());
@@ -95,12 +72,10 @@ public class MailTemplateServiceImpl implements MailTemplateService {
         MailTemplateDO updateObj = MailTemplateConvert.INSTANCE.convert(updateReqVO)
                 .setParams(parseTemplateContentParams(updateReqVO.getContent()));
         mailTemplateMapper.updateById(updateObj);
-        // 发送刷新消息
-        mailProducer.sendMailTemplateRefreshMessage();
     }
 
     @VisibleForTesting
-    public void validateCodeUnique(Long id, String code) {
+    void validateCodeUnique(Long id, String code) {
         MailTemplateDO template = mailTemplateMapper.selectByCode(code);
         if (template == null) {
             return;
@@ -113,14 +88,14 @@ public class MailTemplateServiceImpl implements MailTemplateService {
     }
 
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,
+            allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 code,不好清理
     public void deleteMailTemplate(Long id) {
         // 校验是否存在
         validateMailTemplateExists(id);
 
         // 删除
         mailTemplateMapper.deleteById(id);
-        // 发送刷新消息
-        mailProducer.sendMailTemplateRefreshMessage();
     }
 
     private void validateMailTemplateExists(Long id) {
@@ -132,6 +107,12 @@ public class MailTemplateServiceImpl implements MailTemplateService {
     @Override
     public MailTemplateDO getMailTemplate(Long id) {return mailTemplateMapper.selectById(id);}
 
+    @Override
+    @Cacheable(value = RedisKeyConstants.MAIL_TEMPLATE, key = "#code", unless = "#result == null")
+    public MailTemplateDO getMailTemplateByCodeFromCache(String code) {
+        return mailTemplateMapper.selectByCode(code);
+    }
+
     @Override
     public PageResult<MailTemplateDO> getMailTemplatePage(MailTemplatePageReqVO pageReqVO) {
         return mailTemplateMapper.selectPage(pageReqVO);
@@ -140,11 +121,6 @@ public class MailTemplateServiceImpl implements MailTemplateService {
     @Override
     public List<MailTemplateDO> getMailTemplateList() {return mailTemplateMapper.selectList();}
 
-    @Override
-    public MailTemplateDO getMailTemplateByCodeFromCache(String code) {
-        return mailTemplateCache.get(code);
-    }
-
     @Override
     public String formatMailTemplateContent(String content, Map<String, Object> params) {
         return StrUtil.format(content, params);

+ 8 - 13
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/notify/NotifyTemplateService.java

@@ -16,19 +16,6 @@ import java.util.Map;
  */
 public interface NotifyTemplateService {
 
-    /**
-     * 初始化站内信模板的本地缓存
-     */
-    void initLocalCache();
-
-    /**
-     * 获得站内信模板,从缓存中
-     *
-     * @param code 模板编码
-     * @return 站内信模板
-     */
-    NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code);
-
     /**
      * 创建站内信模版
      *
@@ -59,6 +46,14 @@ public interface NotifyTemplateService {
      */
     NotifyTemplateDO getNotifyTemplate(Long id);
 
+    /**
+     * 获得站内信模板,从缓存中
+     *
+     * @param code 模板编码
+     * @return 站内信模板
+     */
+    NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code);
+
     /**
      * 获得站内信模版分页
      *

+ 17 - 43
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/notify/NotifyTemplateServiceImpl.java

@@ -3,27 +3,28 @@ package cn.iocoder.yudao.module.system.service.notify;
 import cn.hutool.core.util.ReUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateUpdateReqVO;
 import cn.iocoder.yudao.module.system.convert.notify.NotifyTemplateConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
 import cn.iocoder.yudao.module.system.dal.mysql.notify.NotifyTemplateMapper;
-import cn.iocoder.yudao.module.system.mq.producer.notify.NotifyProducer;
+import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
 import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Pattern;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_CODE_DUPLICATE;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_NOT_EXISTS;
 
 /**
  * 站内信模版 Service 实现类
@@ -43,36 +44,6 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
     @Resource
     private NotifyTemplateMapper notifyTemplateMapper;
 
-    @Resource
-    private NotifyProducer notifyProducer;
-
-    /**
-     * 站内信模板缓存
-     * key:站内信模板编码 {@link NotifyTemplateDO#getCode()}
-     * <p>
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    private volatile Map<String, NotifyTemplateDO> notifyTemplateCache;
-
-    /**
-     * 初始化站内信模板的本地缓存
-     */
-    @Override
-    @PostConstruct
-    public void initLocalCache() {
-        // 第一步:查询数据
-        List<NotifyTemplateDO> templates = notifyTemplateMapper.selectList();
-        log.info("[initLocalCache][缓存站内信模版,数量为:{}]", templates.size());
-
-        // 第二步:构建缓存
-        notifyTemplateCache = CollectionUtils.convertMap(templates, NotifyTemplateDO::getCode);
-    }
-
-    @Override
-    public NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code) {
-        return notifyTemplateCache.get(code);
-    }
-
     @Override
     public Long createNotifyTemplate(NotifyTemplateCreateReqVO createReqVO) {
         // 校验站内信编码是否重复
@@ -82,13 +53,12 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
         NotifyTemplateDO notifyTemplate = NotifyTemplateConvert.INSTANCE.convert(createReqVO);
         notifyTemplate.setParams(parseTemplateContentParams(notifyTemplate.getContent()));
         notifyTemplateMapper.insert(notifyTemplate);
-
-        // 发送刷新消息
-        notifyProducer.sendNotifyTemplateRefreshMessage();
         return notifyTemplate.getId();
     }
 
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,
+            allEntries = true) // allEntries 清空所有缓存,因为可能修改到 code 字段,不好清理
     public void updateNotifyTemplate(NotifyTemplateUpdateReqVO updateReqVO) {
         // 校验存在
         validateNotifyTemplateExists(updateReqVO.getId());
@@ -99,9 +69,6 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
         NotifyTemplateDO updateObj = NotifyTemplateConvert.INSTANCE.convert(updateReqVO);
         updateObj.setParams(parseTemplateContentParams(updateObj.getContent()));
         notifyTemplateMapper.updateById(updateObj);
-
-        // 发送刷新消息
-        notifyProducer.sendNotifyTemplateRefreshMessage();
     }
 
     @VisibleForTesting
@@ -110,13 +77,13 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
     }
 
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,
+            allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 code,不好清理
     public void deleteNotifyTemplate(Long id) {
         // 校验存在
         validateNotifyTemplateExists(id);
         // 删除
         notifyTemplateMapper.deleteById(id);
-        // 发送刷新消息
-        notifyProducer.sendNotifyTemplateRefreshMessage();
     }
 
     private void validateNotifyTemplateExists(Long id) {
@@ -130,13 +97,20 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
         return notifyTemplateMapper.selectById(id);
     }
 
+    @Override
+    @Cacheable(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE, key = "#code",
+            unless = "#result == null")
+    public NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code) {
+        return notifyTemplateMapper.selectByCode(code);
+    }
+
     @Override
     public PageResult<NotifyTemplateDO> getNotifyTemplatePage(NotifyTemplatePageReqVO pageReqVO) {
         return notifyTemplateMapper.selectPage(pageReqVO);
     }
 
     @VisibleForTesting
-    public void validateNotifyTemplateCodeDuplicate(Long id, String code) {
+    void validateNotifyTemplateCodeDuplicate(Long id, String code) {
         NotifyTemplateDO template = notifyTemplateMapper.selectByCode(code);
         if (template == null) {
             return;

+ 10 - 7
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientService.java

@@ -18,11 +18,6 @@ import java.util.Collection;
  */
 public interface OAuth2ClientService {
 
-    /**
-     * 初始化 OAuth2Client 的本地缓存
-     */
-    void initLocalCache();
-
     /**
      * 创建 OAuth2 客户端
      *
@@ -53,6 +48,14 @@ public interface OAuth2ClientService {
      */
     OAuth2ClientDO getOAuth2Client(Long id);
 
+    /**
+     * 获得 OAuth2 客户端,从缓存中
+     *
+     * @param clientId 客户端编号
+     * @return OAuth2 客户端
+     */
+    OAuth2ClientDO getOAuth2ClientFromCache(String clientId);
+
     /**
      * 获得 OAuth2 客户端分页
      *
@@ -82,7 +85,7 @@ public interface OAuth2ClientService {
      * @param redirectUri 重定向地址
      * @return 客户端
      */
-    OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret,
-                                             String authorizedGrantType, Collection<String> scopes, String redirectUri);
+    OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret, String authorizedGrantType,
+                                             Collection<String> scopes, String redirectUri);
 
 }

+ 27 - 43
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImpl.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.service.oauth2;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.string.StrUtils;
@@ -12,22 +13,18 @@ import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2Cl
 import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
 import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2ClientMapper;
-import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer;
+import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
 import com.google.common.annotations.VisibleForTesting;
-import lombok.Getter;
-import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.util.Collection;
-import java.util.List;
-import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 
 /**
@@ -40,48 +37,21 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 @Slf4j
 public class OAuth2ClientServiceImpl implements OAuth2ClientService {
 
-    /**
-     * 客户端缓存
-     * key:客户端编号 {@link OAuth2ClientDO#getClientId()} ()}
-     *
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    @Getter // 解决单测
-    @Setter // 解决单测
-    private volatile Map<String, OAuth2ClientDO> clientCache;
-
     @Resource
     private OAuth2ClientMapper oauth2ClientMapper;
 
-    @Resource
-    private OAuth2ClientProducer oauth2ClientProducer;
-
-    /**
-     * 初始化 {@link #clientCache} 缓存
-     */
-    @Override
-    @PostConstruct
-    public void initLocalCache() {
-        // 第一步:查询数据
-        List<OAuth2ClientDO> clients = oauth2ClientMapper.selectList();
-        log.info("[initLocalCache][缓存 OAuth2 客户端,数量为:{}]", clients.size());
-
-        // 第二步:构建缓存。
-        clientCache = convertMap(clients, OAuth2ClientDO::getClientId);
-    }
-
     @Override
     public Long createOAuth2Client(OAuth2ClientCreateReqVO createReqVO) {
         validateClientIdExists(null, createReqVO.getClientId());
         // 插入
         OAuth2ClientDO oauth2Client = OAuth2ClientConvert.INSTANCE.convert(createReqVO);
         oauth2ClientMapper.insert(oauth2Client);
-        // 发送刷新消息
-        oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
         return oauth2Client.getId();
     }
 
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.OAUTH_CLIENT,
+            allEntries = true) // allEntries 清空所有缓存,因为可能修改到 clientId 字段,不好清理
     public void updateOAuth2Client(OAuth2ClientUpdateReqVO updateReqVO) {
         // 校验存在
         validateOAuth2ClientExists(updateReqVO.getId());
@@ -91,18 +61,16 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
         // 更新
         OAuth2ClientDO updateObj = OAuth2ClientConvert.INSTANCE.convert(updateReqVO);
         oauth2ClientMapper.updateById(updateObj);
-        // 发送刷新消息
-        oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
     }
 
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.OAUTH_CLIENT,
+            allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 key,不好清理
     public void deleteOAuth2Client(Long id) {
         // 校验存在
         validateOAuth2ClientExists(id);
         // 删除
         oauth2ClientMapper.deleteById(id);
-        // 发送刷新消息
-        oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
     }
 
     private void validateOAuth2ClientExists(Long id) {
@@ -131,16 +99,23 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
         return oauth2ClientMapper.selectById(id);
     }
 
+    @Override
+    @Cacheable(cacheNames = RedisKeyConstants.OAUTH_CLIENT, key = "#clientId",
+            unless = "#result == null")
+    public OAuth2ClientDO getOAuth2ClientFromCache(String clientId) {
+        return oauth2ClientMapper.selectByClientId(clientId);
+    }
+
     @Override
     public PageResult<OAuth2ClientDO> getOAuth2ClientPage(OAuth2ClientPageReqVO pageReqVO) {
         return oauth2ClientMapper.selectPage(pageReqVO);
     }
 
     @Override
-    public OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret,
-                                                    String authorizedGrantType, Collection<String> scopes, String redirectUri) {
+    public OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret, String authorizedGrantType,
+                                                    Collection<String> scopes, String redirectUri) {
         // 校验客户端存在、且开启
-        OAuth2ClientDO client = clientCache.get(clientId);
+        OAuth2ClientDO client = getSelf().getOAuth2ClientFromCache(clientId);
         if (client == null) {
             throw exception(OAUTH2_CLIENT_NOT_EXISTS);
         }
@@ -167,4 +142,13 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
         return client;
     }
 
+    /**
+     * 获得自身的代理对象,解决 AOP 生效问题
+     *
+     * @return 自己
+     */
+    private OAuth2ClientServiceImpl getSelf() {
+        return SpringUtil.getBean(getClass());
+    }
+
 }

+ 10 - 31
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java

@@ -15,11 +15,6 @@ import java.util.List;
  */
 public interface MenuService {
 
-    /**
-     * 初始化菜单的本地缓存
-     */
-    void initLocalCache();
-
     /**
      * 创建菜单
      *
@@ -67,36 +62,12 @@ public interface MenuService {
     List<MenuDO> getMenuList(MenuListReqVO reqVO);
 
     /**
-     * 获得所有菜单,从缓存中
-     *
-     * 任一参数为空时,则返回为空
-     *
-     * @param menuTypes 菜单类型数组
-     * @param menusStatuses 菜单状态数组
-     * @return 菜单列表
-     */
-    List<MenuDO> getMenuListFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses);
-
-    /**
-     * 获得指定编号的菜单数组,从缓存中
-     *
-     * 任一参数为空时,则返回为空
-     *
-     * @param menuIds 菜单编号数组
-     * @param menuTypes 菜单类型数组
-     * @param menusStatuses 菜单状态数组
-     * @return 菜单数组
-     */
-    List<MenuDO> getMenuListFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
-                                      Collection<Integer> menusStatuses);
-
-    /**
-     * 获得权限对应的菜单数组
+     * 获得权限对应的菜单编号数组
      *
      * @param permission 权限标识
      * @return 数组
      */
-    List<MenuDO> getMenuListByPermissionFromCache(String permission);
+    List<Long> getMenuIdListByPermissionFromCache(String permission);
 
     /**
      * 获得菜单
@@ -106,4 +77,12 @@ public interface MenuService {
      */
     MenuDO getMenu(Long id);
 
+    /**
+     * 获得菜单数组
+     *
+     * @param ids 菜单编号数组
+     * @return 菜单数组
+     */
+    List<MenuDO> getMenuList(Collection<Long> ids);
+
 }

+ 31 - 110
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java

@@ -1,36 +1,29 @@
 package cn.iocoder.yudao.module.system.service.permission;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuUpdateReqVO;
 import cn.iocoder.yudao.module.system.convert.permission.MenuConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.MenuMapper;
+import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
 import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
-import cn.iocoder.yudao.module.system.mq.producer.permission.MenuProducer;
 import cn.iocoder.yudao.module.system.service.tenant.TenantService;
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.Multimap;
-import lombok.Getter;
-import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.transaction.support.TransactionSynchronization;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
-import java.util.*;
-import java.util.stream.Collectors;
+import java.util.Collection;
+import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 
@@ -43,26 +36,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 @Slf4j
 public class MenuServiceImpl implements MenuService {
 
-    /**
-     * 菜单缓存
-     * key:菜单编号
-     *
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    @Getter
-    @Setter
-    private volatile Map<Long, MenuDO> menuCache;
-    /**
-     * 权限与菜单缓存
-     * key:权限 {@link MenuDO#getPermission()}
-     * value:MenuDO 数组,因为一个权限可能对应多个 MenuDO 对象
-     *
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    @Getter
-    @Setter
-    private volatile Multimap<String, MenuDO> permissionMenuCache;
-
     @Resource
     private MenuMapper menuMapper;
     @Resource
@@ -71,33 +44,8 @@ public class MenuServiceImpl implements MenuService {
     @Lazy // 延迟,避免循环依赖报错
     private TenantService tenantService;
 
-    @Resource
-    private MenuProducer menuProducer;
-
-    /**
-     * 初始化 {@link #menuCache} 和 {@link #permissionMenuCache} 缓存
-     */
-    @Override
-    @PostConstruct
-    public synchronized void initLocalCache() {
-        // 第一步:查询数据
-        List<MenuDO> menuList = menuMapper.selectList();
-        log.info("[initLocalCache][缓存菜单,数量为:{}]", menuList.size());
-
-        // 第二步:构建缓存
-        ImmutableMap.Builder<Long, MenuDO> menuCacheBuilder = ImmutableMap.builder();
-        ImmutableMultimap.Builder<String, MenuDO> permMenuCacheBuilder = ImmutableMultimap.builder();
-        menuList.forEach(menuDO -> {
-            menuCacheBuilder.put(menuDO.getId(), menuDO);
-            if (StrUtil.isNotEmpty(menuDO.getPermission())) { // 会存在 permission 为 null 的情况,导致 put 报 NPE 异常
-                permMenuCacheBuilder.put(menuDO.getPermission(), menuDO);
-            }
-        });
-        menuCache = menuCacheBuilder.build();
-        permissionMenuCache = permMenuCacheBuilder.build();
-    }
-
     @Override
+    @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#reqVO.permission")
     public Long createMenu(MenuCreateReqVO reqVO) {
         // 校验父菜单存在
         validateParentMenu(reqVO.getParentId(), null);
@@ -108,13 +56,13 @@ public class MenuServiceImpl implements MenuService {
         MenuDO menu = MenuConvert.INSTANCE.convert(reqVO);
         initMenuProperty(menu);
         menuMapper.insert(menu);
-        // 发送刷新消息
-        menuProducer.sendMenuRefreshMessage();
         // 返回
         return menu.getId();
     }
 
     @Override
+    @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
+            allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理,简单有效
     public void updateMenu(MenuUpdateReqVO reqVO) {
         // 校验更新的菜单是否存在
         if (menuMapper.selectById(reqVO.getId()) == null) {
@@ -129,34 +77,25 @@ public class MenuServiceImpl implements MenuService {
         MenuDO updateObject = MenuConvert.INSTANCE.convert(reqVO);
         initMenuProperty(updateObject);
         menuMapper.updateById(updateObject);
-        // 发送刷新消息
-        menuProducer.sendMenuRefreshMessage();
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void deleteMenu(Long menuId) {
+    @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
+            allEntries = true) // allEntries 清空所有缓存,因为此时不知道 id 对应的 permission 是多少。直接清理,简单有效
+    public void deleteMenu(Long id) {
         // 校验是否还有子菜单
-        if (menuMapper.selectCountByParentId(menuId) > 0) {
+        if (menuMapper.selectCountByParentId(id) > 0) {
             throw exception(MENU_EXISTS_CHILDREN);
         }
         // 校验删除的菜单是否存在
-        if (menuMapper.selectById(menuId) == null) {
+        if (menuMapper.selectById(id) == null) {
             throw exception(MENU_NOT_EXISTS);
         }
         // 标记删除
-        menuMapper.deleteById(menuId);
+        menuMapper.deleteById(id);
         // 删除授予给角色的权限
-        permissionService.processMenuDeleted(menuId);
-        // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
-        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
-
-            @Override
-            public void afterCommit() {
-                menuProducer.sendMenuRefreshMessage();
-            }
-
-        });
+        permissionService.processMenuDeleted(id);
     }
 
     @Override
@@ -178,49 +117,31 @@ public class MenuServiceImpl implements MenuService {
     }
 
     @Override
-    public List<MenuDO> getMenuListFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses) {
-        // 任一一个参数为空,则返回空
-        if (CollectionUtils.isAnyEmpty(menuTypes, menusStatuses)) {
-            return Collections.emptyList();
-        }
-        // 创建新数组,避免缓存被修改
-        return menuCache.values().stream().filter(menu -> menuTypes.contains(menu.getType())
-                && menusStatuses.contains(menu.getStatus()))
-                .collect(Collectors.toList());
-    }
-
-    @Override
-    public List<MenuDO> getMenuListFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
-                                             Collection<Integer> menusStatuses) {
-        // 任一一个参数为空,则返回空
-        if (CollectionUtils.isAnyEmpty(menuIds, menuTypes, menusStatuses)) {
-            return Collections.emptyList();
-        }
-        return menuCache.values().stream().filter(menu -> menuIds.contains(menu.getId())
-                && menuTypes.contains(menu.getType())
-                && menusStatuses.contains(menu.getStatus()))
-                .collect(Collectors.toList());
+    @Cacheable(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#permission")
+    public List<Long> getMenuIdListByPermissionFromCache(String permission) {
+        List<MenuDO> menus = menuMapper.selectListByPermission(permission);
+        return convertList(menus, MenuDO::getId);
     }
 
     @Override
-    public List<MenuDO> getMenuListByPermissionFromCache(String permission) {
-        return new ArrayList<>(permissionMenuCache.get(permission));
+    public MenuDO getMenu(Long id) {
+        return menuMapper.selectById(id);
     }
 
     @Override
-    public MenuDO getMenu(Long id) {
-        return menuMapper.selectById(id);
+    public List<MenuDO> getMenuList(Collection<Long> ids) {
+        return menuMapper.selectBatchIds(ids);
     }
 
     /**
      * 校验父菜单是否合法
-     *
+     * <p>
      * 1. 不能设置自己为父菜单
      * 2. 父菜单不存在
      * 3. 父菜单必须是 {@link MenuTypeEnum#MENU} 菜单类型
      *
      * @param parentId 父菜单编号
-     * @param childId 当前菜单编号
+     * @param childId  当前菜单编号
      */
     @VisibleForTesting
     void validateParentMenu(Long parentId, Long childId) {
@@ -238,19 +159,19 @@ public class MenuServiceImpl implements MenuService {
         }
         // 父菜单必须是目录或者菜单类型
         if (!MenuTypeEnum.DIR.getType().equals(menu.getType())
-            && !MenuTypeEnum.MENU.getType().equals(menu.getType())) {
+                && !MenuTypeEnum.MENU.getType().equals(menu.getType())) {
             throw exception(MENU_PARENT_NOT_DIR_OR_MENU);
         }
     }
 
     /**
      * 校验菜单是否合法
-     *
+     * <p>
      * 1. 校验相同父菜单编号下,是否存在相同的菜单名
      *
-     * @param name 菜单名字
+     * @param name     菜单名字
      * @param parentId 父菜单编号
-     * @param id 菜单编号
+     * @param id       菜单编号
      */
     @VisibleForTesting
     void validateMenu(Long parentId, String name, Long id) {
@@ -269,7 +190,7 @@ public class MenuServiceImpl implements MenuService {
 
     /**
      * 初始化菜单的通用属性。
-     *
+     * <p>
      * 例如说,只有目录或者菜单类型的菜单,才设置 icon
      *
      * @param menu 菜单

+ 63 - 59
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionService.java

@@ -1,16 +1,15 @@
 package cn.iocoder.yudao.module.system.service.permission;
 
 import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
-import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
-import org.springframework.lang.Nullable;
 
 import java.util.Collection;
-import java.util.List;
 import java.util.Set;
 
+import static java.util.Collections.singleton;
+
 /**
  * 权限 Service 接口
- *
+ * <p>
  * 提供用户-角色、角色-菜单、角色-部门的关联权限处理
  *
  * @author 芋道源码
@@ -18,118 +17,123 @@ import java.util.Set;
 public interface PermissionService {
 
     /**
-     * 初始化权限的本地缓存
+     * 判断是否有权限,任一一个即可
+     *
+     * @param userId      用户编号
+     * @param permissions 权限
+     * @return 是否
      */
-    void initLocalCache();
+    boolean hasAnyPermissions(Long userId, String... permissions);
 
     /**
-     * 获得角色们拥有的菜单列表,从缓存中获取
-     *
-     * 任一参数为空时,则返回为空
+     * 判断是否有角色,任一一个即可
      *
-     * @param roleIds 角色编号数组
-     * @param menuTypes 菜单类型数组
-     * @param menusStatuses 菜单状态数组
-     * @return 菜单列表
+     * @param roles 角色数组
+     * @return 是否
      */
-    List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
-                                          Collection<Integer> menusStatuses);
+    boolean hasAnyRoles(Long userId, String... roles);
+
+    // ========== 角色-菜单的相关方法  ==========
 
     /**
-     * 获得用户拥有的角色编号集合,从缓存中获取
+     * 设置角色菜单
      *
-     * @param userId 用户编号
-     * @param roleStatuses 角色状态集合. 允许为空,为空时不过滤
-     * @return 角色编号集合
+     * @param roleId  角色编号
+     * @param menuIds 菜单编号集合
      */
-    Set<Long> getUserRoleIdsFromCache(Long userId, @Nullable Collection<Integer> roleStatuses);
+    void assignRoleMenu(Long roleId, Set<Long> menuIds);
 
     /**
-     * 获得角色拥有的菜单编号集合
+     * 处理角色删除时,删除关联授权数据
      *
      * @param roleId 角色编号
-     * @return 菜单编号集合
      */
-    Set<Long> getRoleMenuIds(Long roleId);
+    void processRoleDeleted(Long roleId);
 
     /**
-     * 获得拥有多个角色的用户编号集合
+     * 处理菜单删除时,删除关联授权数据
      *
-     * @param roleIds 角色编号集合
-     * @return 用户编号集合
+     * @param menuId 菜单编号
      */
-    Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds);
+    void processMenuDeleted(Long menuId);
 
     /**
-     * 设置角色菜单
+     * 获得角色拥有的菜单编号集合
      *
      * @param roleId 角色编号
-     * @param menuIds 菜单编号集合
+     * @return 菜单编号集合
      */
-    void assignRoleMenu(Long roleId, Set<Long> menuIds);
+    default Set<Long> getRoleMenuListByRoleId(Long roleId) {
+        return getRoleMenuListByRoleId(singleton(roleId));
+    }
 
     /**
-     * 获得用户拥有的角色编号集合
+     * 获得角色们拥有的菜单编号集合
      *
-     * @param userId 用户编号
-     * @return 角色编号集合
+     * @param roleIds 角色编号数组
+     * @return 菜单编号集合
      */
-    Set<Long> getUserRoleIdListByUserId(Long userId);
+    Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds);
 
     /**
-     * 设置用户角色
+     * 获得拥有指定菜单的角色编号数组,从缓存中获取
      *
-     * @param userId 角色编号
-     * @param roleIds 角色编号集合
+     * @param menuId 菜单编号
+     * @return 角色编号数组
      */
-    void assignUserRole(Long userId, Set<Long> roleIds);
+    Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId);
+
+    // ========== 用户-角色的相关方法  ==========
 
     /**
-     * 设置角色的数据权限
+     * 设置用户角色
      *
-     * @param roleId 角色编号
-     * @param dataScope 数据范围
-     * @param dataScopeDeptIds 部门编号数组
+     * @param userId  角色编号
+     * @param roleIds 角色编号集合
      */
-    void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds);
+    void assignUserRole(Long userId, Set<Long> roleIds);
 
     /**
-     * 处理角色删除时,删除关联授权数据
+     * 处理用户删除时,删除关联授权数据
      *
-     * @param roleId 角色编号
+     * @param userId 用户编号
      */
-    void processRoleDeleted(Long roleId);
+    void processUserDeleted(Long userId);
 
     /**
-     * 处理菜单删除时,删除关联授权数据
+     * 获得拥有多个角色的用户编号集合
      *
-     * @param menuId 菜单编号
+     * @param roleIds 角色编号集合
+     * @return 用户编号集合
      */
-    void processMenuDeleted(Long menuId);
+    Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds);
 
     /**
-     * 处理用户删除是,删除关联授权数据
+     * 获得用户拥有的角色编号集合
      *
      * @param userId 用户编号
+     * @return 角色编号集合
      */
-    void processUserDeleted(Long userId);
+    Set<Long> getUserRoleIdListByUserId(Long userId);
 
     /**
-     * 判断是否有权限,任一一个即可
+     * 获得用户拥有的角色编号集合,从缓存中获取
      *
      * @param userId 用户编号
-     * @param permissions 权限
-     * @return 是否
+     * @return 角色编号集合
      */
-    boolean hasAnyPermissions(Long userId, String... permissions);
+    Set<Long> getUserRoleIdListByUserIdFromCache(Long userId);
+
+    // ========== 用户-部门的相关方法  ==========
 
     /**
-     * 判断是否有角色,任一一个即可
+     * 设置角色的数据权限
      *
-     * @param roles 角色数组
-     * @return 是否
+     * @param roleId           角色编号
+     * @param dataScope        数据范围
+     * @param dataScopeDeptIds 部门编号数组
      */
-    boolean hasAnyRoles(Long userId, String... roles);
+    void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds);
 
     /**
      * 获得登陆用户的部门数据权限

+ 150 - 253
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java

@@ -3,45 +3,38 @@ package cn.iocoder.yudao.module.system.service.permission;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
-import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
-import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
-import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
+import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
 import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
-import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
 import cn.iocoder.yudao.module.system.service.dept.DeptService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
+import com.baomidou.dynamic.datasource.annotation.DSTransactional;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Suppliers;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
-import lombok.Getter;
-import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.cache.annotation.Caching;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.transaction.support.TransactionSynchronization;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.util.*;
 import java.util.function.Supplier;
 
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
-import static java.util.Collections.singleton;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 
 /**
  * 权限 Service 实现类
@@ -52,38 +45,6 @@ import static java.util.Collections.singleton;
 @Slf4j
 public class PermissionServiceImpl implements PermissionService {
 
-    /**
-     * 角色编号与菜单编号的缓存映射
-     * key:角色编号
-     * value:菜单编号的数组
-     *
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    @Getter
-    @Setter // 单元测试需要
-    private volatile Multimap<Long, Long> roleMenuCache;
-    /**
-     * 菜单编号与角色编号的缓存映射
-     * key:菜单编号
-     * value:角色编号的数组
-     *
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    @Getter
-    @Setter // 单元测试需要
-    private volatile Multimap<Long, Long> menuRoleCache;
-
-    /**
-     * 用户编号与角色编号的缓存映射
-     * key:用户编号
-     * value:角色编号的数组
-     *
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    @Getter
-    @Setter // 单元测试需要
-    private volatile Map<Long, Set<Long>> userRoleCache;
-
     @Resource
     private RoleMenuMapper roleMenuMapper;
     @Resource
@@ -98,115 +59,89 @@ public class PermissionServiceImpl implements PermissionService {
     @Resource
     private AdminUserService userService;
 
-    @Resource
-    private PermissionProducer permissionProducer;
-
     @Override
-    @PostConstruct
-    public void initLocalCache() {
-        initLocalCacheForRoleMenu();
-        initLocalCacheForUserRole();
-    }
+    public boolean hasAnyPermissions(Long userId, String... permissions) {
+        // 如果为空,说明已经有权限
+        if (ArrayUtil.isEmpty(permissions)) {
+            return true;
+        }
 
-    /**
-     * 刷新 RoleMenu 本地缓存
-     */
-    @VisibleForTesting
-    void initLocalCacheForRoleMenu() {
-        // 注意:忽略自动多租户,因为要全局初始化缓存
-        TenantUtils.executeIgnore(() -> {
-            // 第一步:查询数据
-            List<RoleMenuDO> roleMenus = roleMenuMapper.selectList();
-            log.info("[initLocalCacheForRoleMenu][缓存角色与菜单,数量为:{}]", roleMenus.size());
-
-            // 第二步:构建缓存
-            ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder();
-            ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
-            roleMenus.forEach(roleMenuDO -> {
-                roleMenuCacheBuilder.put(roleMenuDO.getRoleId(), roleMenuDO.getMenuId());
-                menuRoleCacheBuilder.put(roleMenuDO.getMenuId(), roleMenuDO.getRoleId());
-            });
-            roleMenuCache = roleMenuCacheBuilder.build();
-            menuRoleCache = menuRoleCacheBuilder.build();
-        });
+        // 获得当前登录的角色。如果为空,说明没有权限
+        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
+        if (CollUtil.isEmpty(roles)) {
+            return false;
+        }
+
+        // 情况一:遍历判断每个权限,如果有一满足,说明有权限
+        for (String permission : permissions) {
+            if (hasAnyPermission(roles, permission)) {
+                return true;
+            }
+        }
+
+        // 情况二:如果是超管,也说明有权限
+        return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId));
     }
 
     /**
-     * 刷新 UserRole 本地缓存
+     * 判断指定角色,是否拥有该 permission 权限
+     *
+     * @param roles 指定角色数组
+     * @param permission 权限标识
+     * @return 是否拥有
      */
-    @VisibleForTesting
-    void initLocalCacheForUserRole() {
-        // 注意:忽略自动多租户,因为要全局初始化缓存
-        TenantUtils.executeIgnore(() -> {
-            // 第一步:加载数据
-            List<UserRoleDO> userRoles = userRoleMapper.selectList();
-            log.info("[initLocalCacheForUserRole][缓存用户与角色,数量为:{}]", userRoles.size());
-
-            // 第二步:构建缓存。
-            ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
-            userRoles.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
-            userRoleCache = CollectionUtils.convertMultiMap2(userRoles, UserRoleDO::getUserId, UserRoleDO::getRoleId);
-        });
-    }
-
-    @Override
-    public List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
-                                                 Collection<Integer> menusStatuses) {
-        // 任一一个参数为空时,不返回任何菜单
-        if (CollectionUtils.isAnyEmpty(roleIds, menuTypes, menusStatuses)) {
-            return Collections.emptyList();
+    private boolean hasAnyPermission(List<RoleDO> roles, String permission) {
+        List<Long> menuIds = menuService.getMenuIdListByPermissionFromCache(permission);
+        // 采用严格模式,如果权限找不到对应的 Menu 的话,也认为没有权限
+        if (CollUtil.isEmpty(menuIds)) {
+            return false;
         }
 
-        // 判断角色是否包含超级管理员。如果是超级管理员,获取到全部
-        List<RoleDO> roleList = roleService.getRoleListFromCache(roleIds);
-        if (roleService.hasAnySuperAdmin(roleList)) {
-            return menuService.getMenuListFromCache(menuTypes, menusStatuses);
+        // 判断是否有权限
+        Set<Long> roleIds = convertSet(roles, RoleDO::getId);
+        for (Long menuId : menuIds) {
+            // 获得拥有该菜单的角色编号集合
+            Set<Long> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menuId);
+            // 如果有交集,说明有权限
+            if (CollUtil.containsAny(menuRoleIds, roleIds)) {
+                return true;
+            }
         }
-
-        // 获得角色拥有的菜单关联
-        List<Long> menuIds = MapUtils.getList(roleMenuCache, roleIds);
-        return menuService.getMenuListFromCache(menuIds, menuTypes, menusStatuses);
+        return false;
     }
 
     @Override
-    public Set<Long> getUserRoleIdsFromCache(Long userId, Collection<Integer> roleStatuses) {
-        Set<Long> cacheRoleIds = userRoleCache.get(userId);
-        // 创建用户的时候没有分配角色,会存在空指针异常
-        if (CollUtil.isEmpty(cacheRoleIds)) {
-            return Collections.emptySet();
-        }
-        Set<Long> roleIds = new HashSet<>(cacheRoleIds);
-        // 过滤角色状态
-        if (CollectionUtil.isNotEmpty(roleStatuses)) {
-            roleIds.removeIf(roleId -> {
-                RoleDO role = roleService.getRoleFromCache(roleId);
-                return role == null || !roleStatuses.contains(role.getStatus());
-            });
+    public boolean hasAnyRoles(Long userId, String... roles) {
+        // 如果为空,说明已经有权限
+        if (ArrayUtil.isEmpty(roles)) {
+            return true;
         }
-        return roleIds;
-    }
 
-    @Override
-    public Set<Long> getRoleMenuIds(Long roleId) {
-        // 如果是管理员的情况下,获取全部菜单编号
-        if (roleService.hasAnySuperAdmin(Collections.singleton(roleId))) {
-            return convertSet(menuService.getMenuList(), MenuDO::getId);
+        // 获得当前登录的角色。如果为空,说明没有权限
+        List<RoleDO> roleList = getEnableUserRoleListByUserIdFromCache(userId);
+        if (CollUtil.isEmpty(roleList)) {
+            return false;
         }
-        // 如果是非管理员的情况下,获得拥有的菜单编号
-        return convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
+
+        // 判断是否有角色
+        Set<String> userRoles = convertSet(roleList, RoleDO::getCode);
+        return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
     }
 
+    // ========== 角色-菜单的相关方法  ==========
+
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
+    @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
+            allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快
     public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
         // 获得角色拥有菜单编号
-        Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId),
-                RoleMenuDO::getMenuId);
+        Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
         // 计算新增和删除的菜单编号
         Collection<Long> createMenuIds = CollUtil.subtract(menuIds, dbMenuIds);
         Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIds);
         // 执行新增和删除。对于已经授权的菜单,不用做任何处理
-        if (!CollectionUtil.isEmpty(createMenuIds)) {
+        if (CollUtil.isNotEmpty(createMenuIds)) {
             roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
                 RoleMenuDO entity = new RoleMenuDO();
                 entity.setRoleId(roleId);
@@ -214,34 +149,57 @@ public class PermissionServiceImpl implements PermissionService {
                 return entity;
             }));
         }
-        if (!CollectionUtil.isEmpty(deleteMenuIds)) {
+        if (CollUtil.isNotEmpty(deleteMenuIds)) {
             roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
         }
-        // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
-        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+    }
 
-            @Override
-            public void afterCommit() {
-                permissionProducer.sendRoleMenuRefreshMessage();
-            }
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @Caching(evict = {
+            @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
+                    allEntries = true), // allEntries 清空所有缓存,此处无法方便获得 roleId 对应的 menu 缓存们
+            @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST,
+                    allEntries = true) // allEntries 清空所有缓存,此处无法方便获得 roleId 对应的 user 缓存们
+    })
+    public void processRoleDeleted(Long roleId) {
+        // 标记删除 UserRole
+        userRoleMapper.deleteListByRoleId(roleId);
+        // 标记删除 RoleMenu
+        roleMenuMapper.deleteListByRoleId(roleId);
+    }
 
-        });
+    @Override
+    @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
+    public void processMenuDeleted(Long menuId) {
+        roleMenuMapper.deleteListByMenuId(menuId);
     }
 
     @Override
-    public Set<Long> getUserRoleIdListByUserId(Long userId) {
-        return convertSet(userRoleMapper.selectListByUserId(userId),
-                UserRoleDO::getRoleId);
+    public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) {
+        if (CollUtil.isEmpty(roleIds)) {
+            return Collections.emptySet();
+        }
+
+        // 如果是管理员的情况下,获取全部菜单编号
+        if (roleService.hasAnySuperAdmin(roleIds)) {
+            return convertSet(menuService.getMenuList(), MenuDO::getId);
+        }
+        // 如果是非管理员的情况下,获得拥有的菜单编号
+        return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
     }
 
     @Override
-    public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
-        return convertSet(userRoleMapper.selectListByRoleIds(roleIds),
-                UserRoleDO::getUserId);
+    @Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
+    public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) {
+        return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId);
     }
 
+    // ========== 用户-角色的相关方法  ==========
+
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
+    @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
     public void assignUserRole(Long userId, Set<Long> roleIds) {
         // 获得角色拥有角色编号
         Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId),
@@ -261,137 +219,68 @@ public class PermissionServiceImpl implements PermissionService {
         if (!CollectionUtil.isEmpty(deleteMenuIds)) {
             userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);
         }
-        // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
-        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
-
-            @Override
-            public void afterCommit() {
-                permissionProducer.sendUserRoleRefreshMessage();
-            }
-
-        });
     }
 
     @Override
-    public void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds) {
-        roleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds);
+    @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
+    public void processUserDeleted(Long userId) {
+        userRoleMapper.deleteListByUserId(userId);
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void processRoleDeleted(Long roleId) {
-        // 标记删除 UserRole
-        userRoleMapper.deleteListByRoleId(roleId);
-        // 标记删除 RoleMenu
-        roleMenuMapper.deleteListByRoleId(roleId);
-        // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
-        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
-
-            @Override
-            public void afterCommit() {
-                permissionProducer.sendRoleMenuRefreshMessage();
-                permissionProducer.sendUserRoleRefreshMessage();
-            }
-
-        });
+    public Set<Long> getUserRoleIdListByUserId(Long userId) {
+        return convertSet(userRoleMapper.selectListByUserId(userId), UserRoleDO::getRoleId);
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void processMenuDeleted(Long menuId) {
-        roleMenuMapper.deleteListByMenuId(menuId);
-        // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
-        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
-
-            @Override
-            public void afterCommit() {
-                permissionProducer.sendRoleMenuRefreshMessage();
-            }
-
-        });
+    @Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
+    public Set<Long> getUserRoleIdListByUserIdFromCache(Long userId) {
+        return getUserRoleIdListByUserId(userId);
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void processUserDeleted(Long userId) {
-        userRoleMapper.deleteListByUserId(userId);
-        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
-
-            @Override
-            public void afterCommit() {
-                permissionProducer.sendUserRoleRefreshMessage();
-            }
-
-        });
+    public Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds) {
+        return convertSet(userRoleMapper.selectListByRoleIds(roleIds), UserRoleDO::getUserId);
     }
 
-    @Override
-    public boolean hasAnyPermissions(Long userId, String... permissions) {
-        // 如果为空,说明已经有权限
-        if (ArrayUtil.isEmpty(permissions)) {
-            return true;
-        }
-
-        // 获得当前登录的角色。如果为空,说明没有权限
-        Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
-        if (CollUtil.isEmpty(roleIds)) {
-            return false;
-        }
-        // 判断是否是超管。如果是,当然符合条件
-        if (roleService.hasAnySuperAdmin(roleIds)) {
-            return true;
-        }
-
-        // 遍历权限,判断是否有一个满足
-        return Arrays.stream(permissions).anyMatch(permission -> {
-            List<MenuDO> menuList = menuService.getMenuListByPermissionFromCache(permission);
-            // 采用严格模式,如果权限找不到对应的 Menu 的话,认为
-            if (CollUtil.isEmpty(menuList)) {
-                return false;
-            }
-            // 获得是否拥有该权限,任一一个
-            return menuList.stream().anyMatch(menu -> CollUtil.containsAny(roleIds,
-                    menuRoleCache.get(menu.getId())));
-        });
+    /**
+     * 获得用户拥有的角色,并且这些角色是开启状态的
+     *
+     * @param userId 用户编号
+     * @return 用户拥有的角色
+     */
+    @VisibleForTesting
+    List<RoleDO> getEnableUserRoleListByUserIdFromCache(Long userId) {
+        // 获得用户拥有的角色编号
+        Set<Long> roleIds = getSelf().getUserRoleIdListByUserIdFromCache(userId);
+        // 获得角色数组,并移除被禁用的
+        List<RoleDO> roles = roleService.getRoleListFromCache(roleIds);
+        roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()));
+        return roles;
     }
 
-    @Override
-    public boolean hasAnyRoles(Long userId, String... roles) {
-        // 如果为空,说明已经有权限
-        if (ArrayUtil.isEmpty(roles)) {
-            return true;
-        }
+    // ========== 用户-部门的相关方法  ==========
 
-        // 获得当前登录的角色。如果为空,说明没有权限
-        Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
-        if (CollUtil.isEmpty(roleIds)) {
-            return false;
-        }
-        // 判断是否是超管。如果是,当然符合条件
-        if (roleService.hasAnySuperAdmin(roleIds)) {
-            return true;
-        }
-        Set<String> userRoles = convertSet(roleService.getRoleListFromCache(roleIds),
-                RoleDO::getCode);
-        return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
+    @Override
+    public void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds) {
+        roleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds);
     }
 
     @Override
     @DataPermission(enable = false) // 关闭数据权限,不然就会出现递归获取数据权限的问题
-    @TenantIgnore // 忽略多租户的自动过滤。如果不忽略,会导致添加租户时,因为切换租户,导致获取不到 User。即使忽略,本身该方法不存在跨租户的操作,不会存在问题。
     public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
         // 获得用户的角色
-        Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
+        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
+
         // 如果角色为空,则只能查看自己
         DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();
-        if (CollUtil.isEmpty(roleIds)) {
+        if (CollUtil.isEmpty(roles)) {
             result.setSelf(true);
             return result;
         }
-        List<RoleDO> roles = roleService.getRoleListFromCache(roleIds);
 
         // 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询
-        Supplier<Long> userDeptIdCache = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
+        Supplier<Long> userDeptId = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
         // 遍历每个角色,计算
         for (RoleDO role : roles) {
             // 为空时,跳过
@@ -408,20 +297,19 @@ public class PermissionServiceImpl implements PermissionService {
                 CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
                 // 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。
                 // 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉
-                CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
+                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                 continue;
             }
             // 情况三,DEPT_ONLY
             if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
-                CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptIdCache.get());
+                CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get());
                 continue;
             }
             // 情况四,DEPT_DEPT_AND_CHILD
             if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
-                List<DeptDO> depts = deptService.getDeptListByParentIdFromCache(userDeptIdCache.get(), true);
-                CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, DeptDO::getId));
+                CollUtil.addAll(result.getDeptIds(), deptService.getChildDeptIdListFromCache(userDeptId.get()));
                 // 添加本身部门编号
-                CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
+                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                 continue;
             }
             // 情况五,SELF
@@ -430,9 +318,18 @@ public class PermissionServiceImpl implements PermissionService {
                 continue;
             }
             // 未知情况,error log 即可
-            log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, JsonUtils.toJsonString(result));
+            log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, toJsonString(result));
         }
         return result;
     }
 
+    /**
+     * 获得自身的代理对象,解决 AOP 生效问题
+     *
+     * @return 自己
+     */
+    private PermissionServiceImpl getSelf() {
+        return SpringUtil.getBean(getClass());
+    }
+
 }

+ 25 - 26
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java

@@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleEx
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleUpdateReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
-import org.springframework.lang.Nullable;
 
 import javax.validation.Valid;
 import java.util.Collection;
@@ -20,11 +19,6 @@ import java.util.Set;
  */
 public interface RoleService {
 
-    /**
-     * 初始化角色的本地缓存
-     */
-    void initLocalCache();
-
     /**
      * 创建角色
      *
@@ -65,6 +59,14 @@ public interface RoleService {
      */
     void updateRoleDataScope(Long id, Integer dataScope, Set<Long> dataScopeDeptIds);
 
+    /**
+     * 获得角色
+     *
+     * @param id 角色编号
+     * @return 角色
+     */
+    RoleDO getRole(Long id);
+
     /**
      * 获得角色,从缓存中
      *
@@ -76,10 +78,10 @@ public interface RoleService {
     /**
      * 获得角色列表
      *
-     * @param statuses 筛选的状态。允许空,空时不筛选
+     * @param ids 角色编号数组
      * @return 角色列表
      */
-    List<RoleDO> getRoleListByStatus(@Nullable Collection<Integer> statuses);
+    List<RoleDO> getRoleList(Collection<Long> ids);
 
     /**
      * 获得角色数组,从缓存中
@@ -90,30 +92,19 @@ public interface RoleService {
     List<RoleDO> getRoleListFromCache(Collection<Long> ids);
 
     /**
-     * 判断角色数组中,是否有超级管理员
-     *
-     * @param roleList 角色数组
-     * @return 是否有管理员
-     */
-    boolean hasAnySuperAdmin(Collection<RoleDO> roleList);
-
-    /**
-     * 判断角色编号数组中,是否有管理员
+     * 获得角色列表
      *
-     * @param ids 角色编号数组
-     * @return 是否有管理员
+     * @param statuses 筛选的状态
+     * @return 角色列表
      */
-    default boolean hasAnySuperAdmin(Set<Long> ids) {
-        return hasAnySuperAdmin(getRoleListFromCache(ids));
-    }
+    List<RoleDO> getRoleListByStatus(Collection<Integer> statuses);
 
     /**
-     * 获得角色
+     * 获得所有角色列表
      *
-     * @param id 角色编号
-     * @return 角色
+     * @return 角色列表
      */
-    RoleDO getRole(Long id);
+    List<RoleDO> getRoleList();
 
     /**
      * 获得角色分页
@@ -131,6 +122,14 @@ public interface RoleService {
      */
     List<RoleDO> getRoleList(RoleExportReqVO reqVO);
 
+    /**
+     * 判断角色编号数组中,是否有管理员
+     *
+     * @param ids 角色编号数组
+     * @return 是否有管理员
+     */
+    boolean hasAnySuperAdmin(Collection<Long> ids);
+
     /**
      * 校验角色们是否有效。如下情况,视为无效:
      * 1. 角色编号不存在

+ 83 - 105
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java

@@ -3,9 +3,9 @@ package cn.iocoder.yudao.module.system.service.permission;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
@@ -13,26 +13,23 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleUp
 import cn.iocoder.yudao.module.system.convert.permission.RoleConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper;
+import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
 import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
-import cn.iocoder.yudao.module.system.mq.producer.permission.RoleProducer;
 import com.google.common.annotations.VisibleForTesting;
-import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.lang.Nullable;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.transaction.support.TransactionSynchronization;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
 import org.springframework.util.StringUtils;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.util.*;
-import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 
@@ -45,43 +42,14 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 @Slf4j
 public class RoleServiceImpl implements RoleService {
 
-    /**
-     * 角色缓存
-     * key:角色编号 {@link RoleDO#getId()}
-     *
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    @Getter
-    private volatile Map<Long, RoleDO> roleCache;
-
     @Resource
     private PermissionService permissionService;
 
     @Resource
     private RoleMapper roleMapper;
 
-    @Resource
-    private RoleProducer roleProducer;
-
-    /**
-     * 初始化 {@link #roleCache} 缓存
-     */
-    @Override
-    @PostConstruct
-    public void initLocalCache() {
-        // 注意:忽略自动多租户,因为要全局初始化缓存
-        TenantUtils.executeIgnore(() -> {
-            // 第一步:查询数据
-            List<RoleDO> roleList = roleMapper.selectList();
-            log.info("[initLocalCache][缓存角色,数量为:{}]", roleList.size());
-
-            // 第二步:构建缓存
-            roleCache = convertMap(roleList, RoleDO::getId);
-        });
-    }
-
     @Override
-    @Transactional
+    @Transactional(rollbackFor = Exception.class)
     public Long createRole(RoleCreateReqVO reqVO, Integer type) {
         // 校验角色
         validateRoleDuplicate(reqVO.getName(), reqVO.getCode(), null);
@@ -91,18 +59,12 @@ public class RoleServiceImpl implements RoleService {
         role.setStatus(CommonStatusEnum.ENABLE.getStatus());
         role.setDataScope(DataScopeEnum.ALL.getScope()); // 默认可查看所有数据。原因是,可能一些项目不需要项目权限
         roleMapper.insert(role);
-        // 发送刷新消息
-        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
-            @Override
-            public void afterCommit() {
-                roleProducer.sendRoleRefreshMessage();
-            }
-        });
         // 返回
         return role.getId();
     }
 
     @Override
+    @CacheEvict(value = RedisKeyConstants.ROLE, key = "#reqVO.id")
     public void updateRole(RoleUpdateReqVO reqVO) {
         // 校验是否可以更新
         validateRoleForUpdate(reqVO.getId());
@@ -112,11 +74,10 @@ public class RoleServiceImpl implements RoleService {
         // 更新到数据库
         RoleDO updateObj = RoleConvert.INSTANCE.convert(reqVO);
         roleMapper.updateById(updateObj);
-        // 发送刷新消息
-        roleProducer.sendRoleRefreshMessage();
     }
 
     @Override
+    @CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")
     public void updateRoleStatus(Long id, Integer status) {
         // 校验是否可以更新
         validateRoleForUpdate(id);
@@ -124,11 +85,10 @@ public class RoleServiceImpl implements RoleService {
         // 更新状态
         RoleDO updateObj = new RoleDO().setId(id).setStatus(status);
         roleMapper.updateById(updateObj);
-        // 发送刷新消息
-        roleProducer.sendRoleRefreshMessage();
     }
 
     @Override
+    @CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")
     public void updateRoleDataScope(Long id, Integer dataScope, Set<Long> dataScopeDeptIds) {
         // 校验是否可以更新
         validateRoleForUpdate(id);
@@ -139,12 +99,11 @@ public class RoleServiceImpl implements RoleService {
         updateObject.setDataScope(dataScope);
         updateObject.setDataScopeDeptIds(dataScopeDeptIds);
         roleMapper.updateById(updateObject);
-        // 发送刷新消息
-        roleProducer.sendRoleRefreshMessage();
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    @CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")
     public void deleteRole(Long id) {
         // 校验是否可以更新
         validateRoleForUpdate(id);
@@ -152,60 +111,6 @@ public class RoleServiceImpl implements RoleService {
         roleMapper.deleteById(id);
         // 删除相关数据
         permissionService.processRoleDeleted(id);
-        // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
-        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
-
-            @Override
-            public void afterCommit() {
-                roleProducer.sendRoleRefreshMessage();
-            }
-
-        });
-    }
-
-    @Override
-    public RoleDO getRoleFromCache(Long id) {
-        return roleCache.get(id);
-    }
-
-    @Override
-    public List<RoleDO> getRoleListByStatus(@Nullable Collection<Integer> statuses) {
-        if (CollUtil.isEmpty(statuses)) {
-    		return roleMapper.selectList();
-		}
-        return roleMapper.selectListByStatus(statuses);
-    }
-
-    @Override
-    public List<RoleDO> getRoleListFromCache(Collection<Long> ids) {
-        if (CollectionUtil.isEmpty(ids)) {
-            return Collections.emptyList();
-        }
-        return roleCache.values().stream().filter(roleDO -> ids.contains(roleDO.getId()))
-                .collect(Collectors.toList());
-    }
-
-    @Override
-    public boolean hasAnySuperAdmin(Collection<RoleDO> roleList) {
-        if (CollectionUtil.isEmpty(roleList)) {
-            return false;
-        }
-        return roleList.stream().anyMatch(role -> RoleCodeEnum.isSuperAdmin(role.getCode()));
-    }
-
-    @Override
-    public RoleDO getRole(Long id) {
-        return roleMapper.selectById(id);
-    }
-
-    @Override
-    public PageResult<RoleDO> getRolePage(RolePageReqVO reqVO) {
-        return roleMapper.selectPage(reqVO);
-    }
-
-    @Override
-    public List<RoleDO> getRoleList(RoleExportReqVO reqVO) {
-        return roleMapper.selectList(reqVO);
     }
 
     /**
@@ -257,6 +162,69 @@ public class RoleServiceImpl implements RoleService {
         }
     }
 
+    @Override
+    public RoleDO getRole(Long id) {
+        return roleMapper.selectById(id);
+    }
+
+    @Override
+    @Cacheable(value = RedisKeyConstants.ROLE, key = "#id",
+            unless = "#result == null")
+    public RoleDO getRoleFromCache(Long id) {
+        return roleMapper.selectById(id);
+    }
+
+
+    @Override
+    public List<RoleDO> getRoleListByStatus(Collection<Integer> statuses) {
+        return roleMapper.selectListByStatus(statuses);
+    }
+
+    @Override
+    public List<RoleDO> getRoleList() {
+        return roleMapper.selectList();
+    }
+
+    @Override
+    public List<RoleDO> getRoleList(Collection<Long> ids) {
+        if (CollectionUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        return roleMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public List<RoleDO> getRoleListFromCache(Collection<Long> ids) {
+        if (CollectionUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        // 这里采用 for 循环从缓存中获取,主要考虑 Spring CacheManager 无法批量操作的问题
+        RoleServiceImpl self = getSelf();
+        return convertList(ids, self::getRoleFromCache);
+    }
+
+    @Override
+    public PageResult<RoleDO> getRolePage(RolePageReqVO reqVO) {
+        return roleMapper.selectPage(reqVO);
+    }
+
+    @Override
+    public List<RoleDO> getRoleList(RoleExportReqVO reqVO) {
+        return roleMapper.selectList(reqVO);
+    }
+
+    @Override
+    public boolean hasAnySuperAdmin(Collection<Long> ids) {
+        if (CollectionUtil.isEmpty(ids)) {
+            return false;
+        }
+        RoleServiceImpl self = getSelf();
+        return ids.stream().anyMatch(id -> {
+            RoleDO role = self.getRoleFromCache(id);
+            return role != null && RoleCodeEnum.isSuperAdmin(role.getCode());
+        });
+    }
+
     @Override
     public void validateRoleList(Collection<Long> ids) {
         if (CollUtil.isEmpty(ids)) {
@@ -276,4 +244,14 @@ public class RoleServiceImpl implements RoleService {
             }
         });
     }
+
+    /**
+     * 获得自身的代理对象,解决 AOP 生效问题
+     *
+     * @return 自己
+     */
+    private RoleServiceImpl getSelf() {
+        return SpringUtil.getBean(getClass());
+    }
+
 }

+ 15 - 36
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateService.java

@@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 
 import javax.validation.Valid;
-import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -16,40 +15,10 @@ import java.util.Map;
  * 短信模板 Service 接口
  *
  * @author zzf
- * @date 2021/1/25 9:24
+ * @since 2021/1/25 9:24
  */
 public interface SmsTemplateService {
 
-    /**
-     * 初始化短信模板的本地缓存
-     */
-    void initLocalCache();
-
-    /**
-     * 获得短信模板,从缓存中
-     *
-     * @param code 模板编码
-     * @return 短信模板
-     */
-    SmsTemplateDO getSmsTemplateByCodeFromCache(String code);
-
-    /**
-     * 格式化短信内容
-     *
-     * @param content 短信模板的内容
-     * @param params 内容的参数
-     * @return 格式化后的内容
-     */
-    String formatSmsTemplateContent(String content, Map<String, Object> params);
-
-    /**
-     * 获得短信模板
-     *
-     * @param code 模板编码
-     * @return 短信模板
-     */
-    SmsTemplateDO getSmsTemplateByCode(String code);
-
     /**
      * 创建短信模板
      *
@@ -81,12 +50,12 @@ public interface SmsTemplateService {
     SmsTemplateDO getSmsTemplate(Long id);
 
     /**
-     * 获得短信模板列表
+     * 获得短信模板,从缓存中
      *
-     * @param ids 编号
-     * @return 短信模板列表
+     * @param code 模板编码
+     * @return 短信模板
      */
-    List<SmsTemplateDO> getSmsTemplateList(Collection<Long> ids);
+    SmsTemplateDO getSmsTemplateByCodeFromCache(String code);
 
     /**
      * 获得短信模板分页
@@ -112,4 +81,14 @@ public interface SmsTemplateService {
      */
     Long countByChannelId(Long channelId);
 
+
+    /**
+     * 格式化短信内容
+     *
+     * @param content 短信模板的内容
+     * @param params 内容的参数
+     * @return 格式化后的内容
+     */
+    String formatSmsTemplateContent(String content, Map<String, Object> params);
+
 }

+ 22 - 57
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImpl.java

@@ -4,7 +4,6 @@ import cn.hutool.core.util.ReUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
 import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
 import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
@@ -17,16 +16,15 @@ import cn.iocoder.yudao.module.system.convert.sms.SmsTemplateConvert;
 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
 import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsTemplateMapper;
-import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
+import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
 import com.google.common.annotations.VisibleForTesting;
-import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 import org.springframework.util.Assert;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
-import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -59,49 +57,6 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
     @Resource
     private SmsClientFactory smsClientFactory;
 
-    @Resource
-    private SmsProducer smsProducer;
-
-    /**
-     * 短信模板缓存
-     * key:短信模板编码 {@link SmsTemplateDO#getCode()}
-     *
-     * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
-     */
-    @Getter // 为了方便测试,这里提供 getter 方法
-    private volatile Map<String, SmsTemplateDO> smsTemplateCache;
-
-    @Override
-    @PostConstruct
-    public void initLocalCache() {
-        // 第一步:查询数据
-        List<SmsTemplateDO> smsTemplateList = smsTemplateMapper.selectList();
-        log.info("[initLocalCache][缓存短信模版,数量为:{}]", smsTemplateList.size());
-
-        // 第二步:构建缓存
-        smsTemplateCache = CollectionUtils.convertMap(smsTemplateList, SmsTemplateDO::getCode);
-    }
-
-    @Override
-    public SmsTemplateDO getSmsTemplateByCodeFromCache(String code) {
-        return smsTemplateCache.get(code);
-    }
-
-    @Override
-    public String formatSmsTemplateContent(String content, Map<String, Object> params) {
-        return StrUtil.format(content, params);
-    }
-
-    @Override
-    public SmsTemplateDO getSmsTemplateByCode(String code) {
-        return smsTemplateMapper.selectByCode(code);
-    }
-
-    @VisibleForTesting
-    public List<String> parseTemplateContentParams(String content) {
-        return ReUtil.findAllGroup1(PATTERN_PARAMS, content);
-    }
-
     @Override
     public Long createSmsTemplate(SmsTemplateCreateReqVO createReqVO) {
         // 校验短信渠道
@@ -116,13 +71,13 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
         template.setParams(parseTemplateContentParams(template.getContent()));
         template.setChannelCode(channelDO.getCode());
         smsTemplateMapper.insert(template);
-        // 发送刷新消息
-        smsProducer.sendSmsTemplateRefreshMessage();
         // 返回
         return template.getId();
     }
 
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.SMS_TEMPLATE,
+            allEntries = true) // allEntries 清空所有缓存,因为可能修改到 code 字段,不好清理
     public void updateSmsTemplate(SmsTemplateUpdateReqVO updateReqVO) {
         // 校验存在
         validateSmsTemplateExists(updateReqVO.getId());
@@ -138,18 +93,16 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
         updateObj.setParams(parseTemplateContentParams(updateObj.getContent()));
         updateObj.setChannelCode(channelDO.getCode());
         smsTemplateMapper.updateById(updateObj);
-        // 发送刷新消息
-        smsProducer.sendSmsTemplateRefreshMessage();
     }
 
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.SMS_TEMPLATE,
+            allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 code,不好清理
     public void deleteSmsTemplate(Long id) {
         // 校验存在
         validateSmsTemplateExists(id);
         // 更新
         smsTemplateMapper.deleteById(id);
-        // 发送刷新消息
-        smsProducer.sendSmsTemplateRefreshMessage();
     }
 
     private void validateSmsTemplateExists(Long id) {
@@ -164,8 +117,10 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
     }
 
     @Override
-    public List<SmsTemplateDO> getSmsTemplateList(Collection<Long> ids) {
-        return smsTemplateMapper.selectBatchIds(ids);
+    @Cacheable(cacheNames = RedisKeyConstants.SMS_TEMPLATE, key = "#code",
+            unless = "#result == null")
+    public SmsTemplateDO getSmsTemplateByCodeFromCache(String code) {
+        return smsTemplateMapper.selectByCode(code);
     }
 
     @Override
@@ -217,7 +172,7 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
      * @param apiTemplateId API 模板编号
      */
     @VisibleForTesting
-    public void validateApiTemplate(Long channelId, String apiTemplateId) {
+    void validateApiTemplate(Long channelId, String apiTemplateId) {
         // 获得短信模板
         SmsClient smsClient = smsClientFactory.getSmsClient(channelId);
         Assert.notNull(smsClient, String.format("短信客户端(%d) 不存在", channelId));
@@ -226,4 +181,14 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
         templateResult.checkError();
     }
 
+    @Override
+    public String formatSmsTemplateContent(String content, Map<String, Object> params) {
+        return StrUtil.format(content, params);
+    }
+
+    @VisibleForTesting
+    List<String> parseTemplateContentParams(String content) {
+        return ReUtil.findAllGroup1(PATTERN_PARAMS, content);
+    }
+
 }

+ 2 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java

@@ -10,9 +10,9 @@ import cn.iocoder.yudao.module.system.convert.tenant.TenantPackageConvert;
 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 com.baomidou.dynamic.datasource.annotation.DSTransactional;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
@@ -47,7 +47,7 @@ public class TenantPackageServiceImpl implements TenantPackageService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
     public void updateTenantPackage(TenantPackageUpdateReqVO updateReqVO) {
         // 校验存在
         TenantPackageDO tenantPackage = validateTenantPackageExists(updateReqVO.getId());

+ 5 - 4
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java

@@ -29,6 +29,7 @@ import cn.iocoder.yudao.module.system.service.permission.RoleService;
 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantInfoHandler;
 import cn.iocoder.yudao.module.system.service.tenant.handler.TenantMenuHandler;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
+import com.baomidou.dynamic.datasource.annotation.DSTransactional;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Lazy;
@@ -95,7 +96,7 @@ public class TenantServiceImpl implements TenantService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
     public Long createTenant(TenantCreateReqVO createReqVO) {
         // 校验租户名称是否重复
         validTenantNameDuplicate(createReqVO.getName(), null);
@@ -137,7 +138,7 @@ public class TenantServiceImpl implements TenantService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @DSTransactional
     public void updateTenant(TenantUpdateReqVO updateReqVO) {
         // 校验存在
         TenantDO tenant = validateUpdateTenant(updateReqVO.getId());
@@ -170,7 +171,7 @@ public class TenantServiceImpl implements TenantService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @DSTransactional
     public void updateTenantRoleMenu(Long tenantId, Set<Long> menuIds) {
         TenantUtils.execute(tenantId, () -> {
             // 获得所有角色
@@ -186,7 +187,7 @@ public class TenantServiceImpl implements TenantService {
                     return;
                 }
                 // 如果是其他角色,则去掉超过套餐的权限
-                Set<Long> roleMenuIds = permissionService.getRoleMenuIds(role.getId());
+                Set<Long> roleMenuIds = permissionService.getRoleMenuListByRoleId(role.getId());
                 roleMenuIds = CollUtil.intersectionDistinct(roleMenuIds, menuIds);
                 permissionService.assignRoleMenu(role.getId(), roleMenuIds);
                 log.info("[updateTenantRoleMenu][角色({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), roleMenuIds);

+ 1 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java

@@ -290,8 +290,7 @@ public class AdminUserServiceImpl implements AdminUserService {
         if (deptId == null) {
             return Collections.emptySet();
         }
-        Set<Long> deptIds = convertSet(deptService.getDeptListByParentIdFromCache(
-                deptId, true), DeptDO::getId);
+        Set<Long> deptIds = convertSet(deptService.getChildDeptList(deptId), DeptDO::getId);
         deptIds.add(deptId); // 包括自身
         return deptIds;
     }

+ 113 - 167
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImplTest.java

@@ -1,9 +1,7 @@
 package cn.iocoder.yudao.module.system.service.dept;
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
-import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
@@ -11,27 +9,20 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateRe
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
 import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
 import cn.iocoder.yudao.module.system.enums.dept.DeptIdEnum;
-import cn.iocoder.yudao.module.system.mq.producer.dept.DeptProducer;
-import com.google.common.collect.Multimap;
-import org.junit.jupiter.api.BeforeEach;
 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.Arrays;
 import java.util.List;
-import java.util.Map;
-import java.util.function.Consumer;
+import java.util.Set;
 
-import static cn.hutool.core.util.RandomUtil.randomEle;
 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.*;
 import static java.util.Collections.singletonList;
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.verify;
 
 /**
  * {@link DeptServiceImpl} 的单元测试类
@@ -45,63 +36,9 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
     private DeptServiceImpl deptService;
     @Resource
     private DeptMapper deptMapper;
-    @MockBean
-    private DeptProducer deptProducer;
-
-    @BeforeEach
-    public void setUp() {
-        // 清理租户上下文
-        TenantContextHolder.clear();
-    }
-
-    @Test
-    public void testInitLocalCache() {
-        // mock 数据
-        DeptDO deptDO1 = randomDeptDO();
-        deptMapper.insert(deptDO1);
-        DeptDO deptDO2 = randomDeptDO();
-        deptMapper.insert(deptDO2);
-
-        // 调用
-        deptService.initLocalCache();
-        // 断言 deptCache 缓存
-        Map<Long, DeptDO> deptCache = deptService.getDeptCache();
-        assertEquals(2, deptCache.size());
-        assertPojoEquals(deptDO1, deptCache.get(deptDO1.getId()));
-        assertPojoEquals(deptDO2, deptCache.get(deptDO2.getId()));
-        // 断言 parentDeptCache 缓存
-        Multimap<Long, DeptDO> parentDeptCache = deptService.getParentDeptCache();
-        assertEquals(2, parentDeptCache.size());
-        assertPojoEquals(deptDO1, parentDeptCache.get(deptDO1.getParentId()));
-        assertPojoEquals(deptDO2, parentDeptCache.get(deptDO2.getParentId()));
-    }
-
-    @Test
-    public void testListDepts() {
-        // mock 数据
-        DeptDO dept = randomPojo(DeptDO.class, o -> { // 等会查询到
-            o.setName("开发部");
-            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-        });
-        deptMapper.insert(dept);
-        // 测试 name 不匹配
-        deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setName("发")));
-        // 测试 status 不匹配
-        deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
-        // 准备参数
-        DeptListReqVO reqVO = new DeptListReqVO();
-        reqVO.setName("开");
-        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
-
-        // 调用
-        List<DeptDO> sysDeptDOS = deptService.getDeptList(reqVO);
-        // 断言
-        assertEquals(1, sysDeptDOS.size());
-        assertPojoEquals(dept, sysDeptDOS.get(0));
-    }
 
     @Test
-    public void testCreateDept_success() {
+    public void testCreateDept() {
         // 准备参数
         DeptCreateReqVO reqVO = randomPojo(DeptCreateReqVO.class, o -> {
             o.setParentId(DeptIdEnum.ROOT.getId());
@@ -115,12 +52,10 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
         // 校验记录的属性是否正确
         DeptDO deptDO = deptMapper.selectById(deptId);
         assertPojoEquals(reqVO, deptDO);
-        // 校验调用
-        verify(deptProducer).sendDeptRefreshMessage();
     }
 
     @Test
-    public void testUpdateDept_success() {
+    public void testUpdateDept() {
         // mock 数据
         DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
         deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据
@@ -137,14 +72,12 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
         // 校验是否更新正确
         DeptDO deptDO = deptMapper.selectById(reqVO.getId()); // 获取最新的
         assertPojoEquals(reqVO, deptDO);
-        // 校验调用
-        verify(deptProducer).sendDeptRefreshMessage();
     }
 
     @Test
     public void testDeleteDept_success() {
         // mock 数据
-        DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
+        DeptDO dbDeptDO = randomPojo(DeptDO.class);
         deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据
         // 准备参数
         Long id = dbDeptDO.getId();
@@ -153,134 +86,99 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
         deptService.deleteDept(id);
         // 校验数据不存在了
         assertNull(deptMapper.selectById(id));
-        // 校验调用
-        verify(deptProducer).sendDeptRefreshMessage();
     }
 
     @Test
-    public void testValidateDept_nameDuplicateForUpdate() {
+    public void testDeleteDept_exitsChildren() {
         // mock 数据
-        DeptDO deptDO = randomDeptDO();
-        // 设置根节点部门
-        deptDO.setParentId(DeptIdEnum.ROOT.getId());
-        deptMapper.insert(deptDO);
-        // mock 数据 稍后模拟重复它的 name
-        DeptDO nameDeptDO = randomDeptDO();
-        // 设置根节点部门
-        nameDeptDO.setParentId(DeptIdEnum.ROOT.getId());
-        deptMapper.insert(nameDeptDO);
+        DeptDO parentDept = randomPojo(DeptDO.class);
+        deptMapper.insert(parentDept);// @Sql: 先插入出一条存在的数据
         // 准备参数
-        DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
-            // 设置根节点部门
-            o.setParentId(DeptIdEnum.ROOT.getId());
-            // 设置更新的 ID
-            o.setId(deptDO.getId());
-            // 模拟 name 重复
-            o.setName(nameDeptDO.getName());
+        DeptDO childrenDeptDO = randomPojo(DeptDO.class, o -> {
+            o.setParentId(parentDept.getId());
+            o.setStatus(randomCommonStatus());
         });
+        // 插入子部门
+        deptMapper.insert(childrenDeptDO);
 
         // 调用, 并断言异常
-        assertServiceException(() -> deptService.updateDept(reqVO), DEPT_NAME_DUPLICATE);
+        assertServiceException(() -> deptService.deleteDept(parentDept.getId()), DEPT_EXITS_CHILDREN);
     }
 
     @Test
-    public void testValidateDept_parentNotExitsForCreate() {
+    public void testValidateDeptExists_notFound() {
         // 准备参数
-        DeptCreateReqVO reqVO = randomPojo(DeptCreateReqVO.class,
-            o -> o.setStatus(randomCommonStatus()));
+        Long id = randomLongId();
 
-        // 调用,并断言异常
-        assertServiceException(() -> deptService.createDept(reqVO), DEPT_PARENT_NOT_EXITS);
+        // 调用, 并断言异常
+        assertServiceException(() -> deptService.validateDeptExists(id), DEPT_NOT_FOUND);
     }
 
     @Test
-    public void testValidateDept_notFoundForDelete() {
+    public void testValidateParentDept_parentError() {
         // 准备参数
         Long id = randomLongId();
 
         // 调用, 并断言异常
-        assertServiceException(() -> deptService.deleteDept(id), DEPT_NOT_FOUND);
+        assertServiceException(() -> deptService.validateParentDept(id, id),
+                DEPT_PARENT_ERROR);
     }
 
     @Test
-   public void testValidateDept_exitsChildrenForDelete() {
-        // mock 数据
-        DeptDO parentDept = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
-        deptMapper.insert(parentDept);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        DeptDO childrenDeptDO = randomPojo(DeptDO.class, o -> {
+    public void testValidateParentDept_parentIsChild() {
+        // mock 数据(父节点)
+        DeptDO parentDept = randomPojo(DeptDO.class);
+        deptMapper.insert(parentDept);
+        // mock 数据(子节点)
+        DeptDO childDept = randomPojo(DeptDO.class, o -> {
             o.setParentId(parentDept.getId());
-            o.setStatus(randomCommonStatus());
         });
-        // 插入子部门
-        deptMapper.insert(childrenDeptDO);
-        // 调用, 并断言异常
-        assertServiceException(() -> deptService.deleteDept(parentDept.getId()), DEPT_EXITS_CHILDREN);
-    }
+        deptMapper.insert(childDept);
 
-    @Test
-    public void testValidateDept_parentErrorForUpdate() {
-        // mock 数据
-        DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
-        deptMapper.insert(dbDeptDO);
         // 准备参数
-        DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
-            // 设置自己为父部门
-            o.setParentId(dbDeptDO.getId());
-            // 设置更新的 ID
-            o.setId(dbDeptDO.getId());
-        });
+        Long id = parentDept.getId();
+        Long parentId = childDept.getId();
 
         // 调用, 并断言异常
-        assertServiceException(() -> deptService.updateDept(reqVO), DEPT_PARENT_ERROR);
+        assertServiceException(() -> deptService.validateParentDept(id, parentId), DEPT_PARENT_IS_CHILD);
     }
 
     @Test
-    public void testValidateDept_notEnableForCreate() {
+    public void testValidateNameUnique_duplicate() {
         // mock 数据
-        DeptDO deptDO = randomPojo(DeptDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
+        DeptDO deptDO = randomPojo(DeptDO.class);
         deptMapper.insert(deptDO);
+
         // 准备参数
-        DeptCreateReqVO reqVO = randomPojo(DeptCreateReqVO.class, o -> {
-            // 设置未启用的部门为父部门
-            o.setParentId(deptDO.getId());
-        });
+        Long id = randomLongId();
+        Long parentId = deptDO.getParentId();
+        String name = deptDO.getName();
 
         // 调用, 并断言异常
-        assertServiceException(() -> deptService.createDept(reqVO), DEPT_NOT_ENABLE);
+        assertServiceException(() -> deptService.validateDeptNameUnique(id, parentId, name),
+                DEPT_NAME_DUPLICATE);
     }
 
     @Test
-    public void testCheckDept_parentIsChildForUpdate() {
+    public void testGetDept() {
         // mock 数据
-        DeptDO parentDept = randomPojo(DeptDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        deptMapper.insert(parentDept);
-        DeptDO childDept = randomPojo(DeptDO.class, o -> {
-            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-            o.setParentId(parentDept.getId());
-        });
-        deptMapper.insert(childDept);
-        // 初始化本地缓存
-        deptService.initLocalCache();
-
+        DeptDO deptDO = randomPojo(DeptDO.class);
+        deptMapper.insert(deptDO);
         // 准备参数
-        DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
-            // 设置自己的子部门为父部门
-            o.setParentId(childDept.getId());
-            // 设置更新的 ID
-            o.setId(parentDept.getId());
-        });
+        Long id = deptDO.getId();
 
-        // 调用, 并断言异常
-        assertServiceException(() -> deptService.updateDept(reqVO), DEPT_PARENT_IS_CHILD);
+        // 调用
+        DeptDO dbDept = deptService.getDept(id);
+        // 断言
+        assertEquals(deptDO, dbDept);
     }
 
     @Test
-    public void testGetDeptList() {
+    public void testGetDeptList_ids() {
         // mock 数据
-        DeptDO deptDO01 = randomDeptDO();
+        DeptDO deptDO01 = randomPojo(DeptDO.class);
         deptMapper.insert(deptDO01);
-        DeptDO deptDO02 = randomDeptDO();
+        DeptDO deptDO02 = randomPojo(DeptDO.class);
         deptMapper.insert(deptDO02);
         // 准备参数
         List<Long> ids = Arrays.asList(deptDO01.getId(), deptDO02.getId());
@@ -294,23 +192,79 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
     }
 
     @Test
-    public void testGetDept() {
+    public void testGetDeptList_reqVO() {
         // mock 数据
-        DeptDO deptDO = randomDeptDO();
-        deptMapper.insert(deptDO);
+        DeptDO dept = randomPojo(DeptDO.class, o -> { // 等会查询到
+            o.setName("开发部");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        deptMapper.insert(dept);
+        // 测试 name 不匹配
+        deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setName("发")));
+        // 测试 status 不匹配
+        deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
         // 准备参数
-        Long id = deptDO.getId();
+        DeptListReqVO reqVO = new DeptListReqVO();
+        reqVO.setName("开");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
 
         // 调用
-        DeptDO dbDept = deptService.getDept(id);
+        List<DeptDO> sysDeptDOS = deptService.getDeptList(reqVO);
         // 断言
-        assertEquals(deptDO, dbDept);
+        assertEquals(1, sysDeptDOS.size());
+        assertPojoEquals(dept, sysDeptDOS.get(0));
+    }
+
+    @Test
+    public void testGetChildDeptList() {
+        // mock 数据(1 级别子节点)
+        DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName("1"));
+        deptMapper.insert(dept1);
+        DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName("2"));
+        deptMapper.insert(dept2);
+        // mock 数据(2 级子节点)
+        DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName("1-a").setParentId(dept1.getId()));
+        deptMapper.insert(dept1a);
+        DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName("2-a").setParentId(dept2.getId()));
+        deptMapper.insert(dept2a);
+        // 准备参数
+        Long id = dept1.getParentId();
+
+        // 调用
+        List<DeptDO> result = deptService.getChildDeptList(id);
+        // 断言
+        assertEquals(result.size(), 2);
+        assertPojoEquals(dept1, result.get(0));
+        assertPojoEquals(dept1a, result.get(1));
+    }
+
+    @Test
+    public void testGetChildDeptListFromCache() {
+        // mock 数据(1 级别子节点)
+        DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName("1"));
+        deptMapper.insert(dept1);
+        DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName("2"));
+        deptMapper.insert(dept2);
+        // mock 数据(2 级子节点)
+        DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName("1-a").setParentId(dept1.getId()));
+        deptMapper.insert(dept1a);
+        DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName("2-a").setParentId(dept2.getId()));
+        deptMapper.insert(dept2a);
+        // 准备参数
+        Long id = dept1.getParentId();
+
+        // 调用
+        Set<Long> result = deptService.getChildDeptIdListFromCache(id);
+        // 断言
+        assertEquals(result.size(), 2);
+        assertTrue(result.contains(dept1.getId()));
+        assertTrue(result.contains(dept1a.getId()));
     }
 
     @Test
     public void testValidateDeptList_success() {
         // mock 数据
-        DeptDO deptDO = randomDeptDO().setStatus(CommonStatusEnum.ENABLE.getStatus());
+        DeptDO deptDO = randomPojo(DeptDO.class).setStatus(CommonStatusEnum.ENABLE.getStatus());
         deptMapper.insert(deptDO);
         // 准备参数
         List<Long> ids = singletonList(deptDO.getId());
@@ -331,7 +285,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testValidateDeptList_notEnable() {
         // mock 数据
-        DeptDO deptDO = randomDeptDO().setStatus(CommonStatusEnum.DISABLE.getStatus());
+        DeptDO deptDO = randomPojo(DeptDO.class).setStatus(CommonStatusEnum.DISABLE.getStatus());
         deptMapper.insert(deptDO);
         // 准备参数
         List<Long> ids = singletonList(deptDO.getId());
@@ -340,12 +294,4 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
         assertServiceException(() -> deptService.validateDeptList(ids), DEPT_NOT_ENABLE, deptDO.getName());
     }
 
-    @SafeVarargs
-    private static DeptDO randomDeptDO(Consumer<DeptDO>... consumers) {
-        Consumer<DeptDO> consumer = (o) -> {
-            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
-        };
-        return randomPojo(DeptDO.class, ArrayUtils.append(consumer, consumers));
-    }
-
 }

+ 27 - 51
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImplTest.java

@@ -7,14 +7,12 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccou
 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountUpdateReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
 import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
-import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
 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 java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
@@ -23,14 +21,13 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS;
 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 MailAccountServiceImpl} 的单元测试类
-*
-* @author 芋道源码
-*/
+ * {@link MailAccountServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
 @Import(MailAccountServiceImpl.class)
 public class MailAccountServiceImplTest extends BaseDbUnitTest {
 
@@ -42,23 +39,6 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
 
     @MockBean
     private MailTemplateService mailTemplateService;
-    @MockBean
-    private MailProducer mailProducer;
-
-    @Test
-    public void testInitLocalCache() {
-        MailAccountDO accountDO1 = randomPojo(MailAccountDO.class);
-        mailAccountMapper.insert(accountDO1);
-        MailAccountDO accountDO02 = randomPojo(MailAccountDO.class);
-        mailAccountMapper.insert(accountDO02);
-
-        // 调用
-        mailAccountService.initLocalCache();
-        // 断言 mailAccountCache 缓存
-        Map<Long, MailAccountDO> mailAccountCache = mailAccountService.getMailAccountCache();
-        assertPojoEquals(accountDO1, mailAccountCache.get(accountDO1.getId()));
-        assertPojoEquals(accountDO02, mailAccountCache.get(accountDO02.getId()));
-    }
 
     @Test
     public void testCreateMailAccount_success() {
@@ -72,7 +52,6 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
         // 校验记录的属性是否正确
         MailAccountDO mailAccount = mailAccountMapper.selectById(mailAccountId);
         assertPojoEquals(reqVO, mailAccount);
-        verify(mailProducer).sendMailAccountRefreshMessage();
     }
 
     @Test
@@ -91,7 +70,6 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
         // 校验是否更新正确
         MailAccountDO mailAccount = mailAccountMapper.selectById(reqVO.getId()); // 获取最新的
         assertPojoEquals(reqVO, mailAccount);
-        verify(mailProducer).sendMailAccountRefreshMessage();
     }
 
     @Test
@@ -115,9 +93,8 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
 
         // 调用
         mailAccountService.deleteMailAccount(id);
-       // 校验数据不存在了
-       assertNull(mailAccountMapper.selectById(id));
-        verify(mailProducer).sendMailAccountRefreshMessage();
+        // 校验数据不存在了
+        assertNull(mailAccountMapper.selectById(id));
     }
 
     @Test
@@ -125,7 +102,6 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
         // mock 数据
         MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class);
         mailAccountMapper.insert(dbMailAccount);// @Sql: 先插入出一条存在的数据
-        mailAccountService.initLocalCache();
         // 准备参数
         Long id = dbMailAccount.getId();
 
@@ -146,27 +122,27 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
 
     @Test
     public void testGetMailAccountPage() {
-       // mock 数据
-       MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class, o -> { // 等会查询到
-           o.setMail("768@qq.com");
-           o.setUsername("yunai");
-       });
-       mailAccountMapper.insert(dbMailAccount);
-       // 测试 mail 不匹配
-       mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setMail("788@qq.com")));
-       // 测试 username 不匹配
-       mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setUsername("tudou")));
-       // 准备参数
-       MailAccountPageReqVO reqVO = new MailAccountPageReqVO();
-       reqVO.setMail("768");
-       reqVO.setUsername("yu");
-
-       // 调用
-       PageResult<MailAccountDO> pageResult = mailAccountService.getMailAccountPage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbMailAccount, pageResult.getList().get(0));
+        // mock 数据
+        MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class, o -> { // 等会查询到
+            o.setMail("768@qq.com");
+            o.setUsername("yunai");
+        });
+        mailAccountMapper.insert(dbMailAccount);
+        // 测试 mail 不匹配
+        mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setMail("788@qq.com")));
+        // 测试 username 不匹配
+        mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setUsername("tudou")));
+        // 准备参数
+        MailAccountPageReqVO reqVO = new MailAccountPageReqVO();
+        reqVO.setMail("768");
+        reqVO.setUsername("yu");
+
+        // 调用
+        PageResult<MailAccountDO> pageResult = mailAccountService.getMailAccountPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbMailAccount, pageResult.getList().get(0));
     }
 
     @Test

+ 37 - 62
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImplTest.java

@@ -8,9 +8,7 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemp
 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateUpdateReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
 import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
-import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
 import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
@@ -27,13 +25,12 @@ 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.MAIL_TEMPLATE_NOT_EXISTS;
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.verify;
 
 /**
-* {@link MailTemplateServiceImpl} 的单元测试类
-*
-* @author 芋道源码
-*/
+ * {@link MailTemplateServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
 @Import(MailTemplateServiceImpl.class)
 public class MailTemplateServiceImplTest extends BaseDbUnitTest {
 
@@ -43,24 +40,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
     @Resource
     private MailTemplateMapper mailTemplateMapper;
 
-    @MockBean
-    private MailProducer mailProducer;
-
-    @Test
-    public void testInitLocalCache() {
-        MailTemplateDO templateDO01 = randomPojo(MailTemplateDO.class);
-        mailTemplateMapper.insert(templateDO01);
-        MailTemplateDO templateDO02 = randomPojo(MailTemplateDO.class);
-        mailTemplateMapper.insert(templateDO02);
-
-        // 调用
-        mailTemplateService.initLocalCache();
-        // 断言 mailTemplateCache 缓存
-        Map<String, MailTemplateDO> mailTemplateCache = mailTemplateService.getMailTemplateCache();
-        assertPojoEquals(templateDO01, mailTemplateCache.get(templateDO01.getCode()));
-        assertPojoEquals(templateDO02, mailTemplateCache.get(templateDO02.getCode()));
-    }
-
     @Test
     public void testCreateMailTemplate_success() {
         // 准备参数
@@ -73,7 +52,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
         // 校验记录的属性是否正确
         MailTemplateDO mailTemplate = mailTemplateMapper.selectById(mailTemplateId);
         assertPojoEquals(reqVO, mailTemplate);
-        verify(mailProducer).sendMailTemplateRefreshMessage();
     }
 
     @Test
@@ -91,7 +69,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
         // 校验是否更新正确
         MailTemplateDO mailTemplate = mailTemplateMapper.selectById(reqVO.getId()); // 获取最新的
         assertPojoEquals(reqVO, mailTemplate);
-        verify(mailProducer).sendMailTemplateRefreshMessage();
     }
 
     @Test
@@ -115,7 +92,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
         mailTemplateService.deleteMailTemplate(id);
         // 校验数据不存在了
         assertNull(mailTemplateMapper.selectById(id));
-        verify(mailProducer).sendMailTemplateRefreshMessage();
     }
 
     @Test
@@ -129,39 +105,39 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
 
     @Test
     public void testGetMailTemplatePage() {
-       // mock 数据
-       MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class, o -> { // 等会查询到
-           o.setName("源码");
-           o.setCode("test_01");
-           o.setAccountId(1L);
-           o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-           o.setCreateTime(buildTime(2023, 2, 3));
-       });
-       mailTemplateMapper.insert(dbMailTemplate);
-       // 测试 name 不匹配
-       mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setName("芋道")));
-       // 测试 code 不匹配
-       mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCode("test_02")));
-       // 测试 accountId 不匹配
-       mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setAccountId(2L)));
-       // 测试 status 不匹配
-       mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
-       // 测试 createTime 不匹配
-       mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCreateTime(buildTime(2023, 1, 5))));
-       // 准备参数
-       MailTemplatePageReqVO reqVO = new MailTemplatePageReqVO();
-       reqVO.setName("源");
-       reqVO.setCode("est_01");
-       reqVO.setAccountId(1L);
-       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
-       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 5));
-
-       // 调用
-       PageResult<MailTemplateDO> pageResult = mailTemplateService.getMailTemplatePage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbMailTemplate, pageResult.getList().get(0));
+        // mock 数据
+        MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class, o -> { // 等会查询到
+            o.setName("源码");
+            o.setCode("test_01");
+            o.setAccountId(1L);
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setCreateTime(buildTime(2023, 2, 3));
+        });
+        mailTemplateMapper.insert(dbMailTemplate);
+        // 测试 name 不匹配
+        mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setName("芋道")));
+        // 测试 code 不匹配
+        mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCode("test_02")));
+        // 测试 accountId 不匹配
+        mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setAccountId(2L)));
+        // 测试 status 不匹配
+        mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 测试 createTime 不匹配
+        mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCreateTime(buildTime(2023, 1, 5))));
+        // 准备参数
+        MailTemplatePageReqVO reqVO = new MailTemplatePageReqVO();
+        reqVO.setName("源");
+        reqVO.setCode("est_01");
+        reqVO.setAccountId(1L);
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 5));
+
+        // 调用
+        PageResult<MailTemplateDO> pageResult = mailTemplateService.getMailTemplatePage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbMailTemplate, pageResult.getList().get(0));
     }
 
     @Test
@@ -199,7 +175,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
         // mock 数据
         MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class);
         mailTemplateMapper.insert(dbMailTemplate);
-        mailTemplateService.initLocalCache();
         // 准备参数
         String code = dbMailTemplate.getCode();
 

+ 36 - 46
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/notify/NotifyTemplateServiceImplTest.java

@@ -8,9 +8,7 @@ import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.Notify
 import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateUpdateReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
 import cn.iocoder.yudao.module.system.dal.mysql.notify.NotifyTemplateMapper;
-import cn.iocoder.yudao.module.system.mq.producer.notify.NotifyProducer;
 import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
@@ -25,13 +23,12 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_NOT_EXISTS;
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.verify;
 
 /**
-* {@link NotifyTemplateServiceImpl} 的单元测试类
-*
-* @author 芋道源码
-*/
+ * {@link NotifyTemplateServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
 @Import(NotifyTemplateServiceImpl.class)
 public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
 
@@ -41,9 +38,6 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
     @Resource
     private NotifyTemplateMapper notifyTemplateMapper;
 
-    @MockBean
-    private NotifyProducer notifyProducer;
-
     @Test
     public void testCreateNotifyTemplate_success() {
         // 准备参数
@@ -57,7 +51,6 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
         // 校验记录的属性是否正确
         NotifyTemplateDO notifyTemplate = notifyTemplateMapper.selectById(notifyTemplateId);
         assertPojoEquals(reqVO, notifyTemplate);
-        verify(notifyProducer).sendNotifyTemplateRefreshMessage();
     }
 
     @Test
@@ -76,7 +69,6 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
         // 校验是否更新正确
         NotifyTemplateDO notifyTemplate = notifyTemplateMapper.selectById(reqVO.getId()); // 获取最新的
         assertPojoEquals(reqVO, notifyTemplate);
-        verify(notifyProducer).sendNotifyTemplateRefreshMessage();
     }
 
     @Test
@@ -98,9 +90,8 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
 
         // 调用
         notifyTemplateService.deleteNotifyTemplate(id);
-       // 校验数据不存在了
-       assertNull(notifyTemplateMapper.selectById(id));
-       verify(notifyProducer).sendNotifyTemplateRefreshMessage();
+        // 校验数据不存在了
+        assertNull(notifyTemplateMapper.selectById(id));
     }
 
     @Test
@@ -114,35 +105,35 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
 
     @Test
     public void testGetNotifyTemplatePage() {
-       // mock 数据
-       NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class, o -> { // 等会查询到
-           o.setName("芋头");
-           o.setCode("test_01");
-           o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-           o.setCreateTime(buildTime(2022, 2, 3));
-       });
-       notifyTemplateMapper.insert(dbNotifyTemplate);
-       // 测试 name 不匹配
-       notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setName("投")));
-       // 测试 code 不匹配
-       notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCode("test_02")));
-       // 测试 status 不匹配
-       notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
-       // 测试 createTime 不匹配
-       notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCreateTime(buildTime(2022, 1, 5))));
-       // 准备参数
-       NotifyTemplatePageReqVO reqVO = new NotifyTemplatePageReqVO();
-       reqVO.setName("芋");
-       reqVO.setCode("est_01");
-       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
-       reqVO.setCreateTime(buildBetweenTime(2022, 2, 1, 2022, 2, 5));
-
-       // 调用
-       PageResult<NotifyTemplateDO> pageResult = notifyTemplateService.getNotifyTemplatePage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbNotifyTemplate, pageResult.getList().get(0));
+        // mock 数据
+        NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class, o -> { // 等会查询到
+            o.setName("芋头");
+            o.setCode("test_01");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setCreateTime(buildTime(2022, 2, 3));
+        });
+        notifyTemplateMapper.insert(dbNotifyTemplate);
+        // 测试 name 不匹配
+        notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setName("投")));
+        // 测试 code 不匹配
+        notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCode("test_02")));
+        // 测试 status 不匹配
+        notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 测试 createTime 不匹配
+        notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCreateTime(buildTime(2022, 1, 5))));
+        // 准备参数
+        NotifyTemplatePageReqVO reqVO = new NotifyTemplatePageReqVO();
+        reqVO.setName("芋");
+        reqVO.setCode("est_01");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setCreateTime(buildBetweenTime(2022, 2, 1, 2022, 2, 5));
+
+        // 调用
+        PageResult<NotifyTemplateDO> pageResult = notifyTemplateService.getNotifyTemplatePage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbNotifyTemplate, pageResult.getList().get(0));
     }
 
     @Test
@@ -164,7 +155,6 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
         // mock 数据
         NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class);
         notifyTemplateMapper.insert(dbNotifyTemplate);
-        notifyTemplateService.initLocalCache();
         // 准备参数
         String code = dbNotifyTemplate.getCode();
 
@@ -173,7 +163,7 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
         // 断言
         assertPojoEquals(dbNotifyTemplate, notifyTemplate);
     }
-    
+
     @Test
     public void testFormatNotifyTemplateContent() {
         // 准备参数

+ 75 - 83
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImplTest.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.system.service.oauth2;
 
-import cn.hutool.core.map.MapUtil;
+import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
@@ -9,14 +9,12 @@ import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2Cl
 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientUpdateReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
 import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2ClientMapper;
-import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer;
 import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.mock.mockito.MockBean;
+import org.mockito.MockedStatic;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
 import java.util.Collections;
-import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
@@ -24,13 +22,14 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.verify;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mockStatic;
 
 /**
-* {@link OAuth2ClientServiceImpl} 的单元测试类
-*
-* @author 芋道源码
-*/
+ * {@link OAuth2ClientServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
 @Import(OAuth2ClientServiceImpl.class)
 public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
 
@@ -40,26 +39,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
     @Resource
     private OAuth2ClientMapper oauth2ClientMapper;
 
-    @MockBean
-    private OAuth2ClientProducer oauth2ClientProducer;
-
-    @Test
-    public void testInitLocalCache() {
-        // mock 数据
-        OAuth2ClientDO clientDO1 = randomPojo(OAuth2ClientDO.class);
-        oauth2ClientMapper.insert(clientDO1);
-        OAuth2ClientDO clientDO2 = randomPojo(OAuth2ClientDO.class);
-        oauth2ClientMapper.insert(clientDO2);
-
-        // 调用
-        oauth2ClientService.initLocalCache();
-        // 断言 clientCache 缓存
-        Map<String, OAuth2ClientDO> clientCache = oauth2ClientService.getClientCache();
-        assertEquals(2, clientCache.size());
-        assertPojoEquals(clientDO1, clientCache.get(clientDO1.getClientId()));
-        assertPojoEquals(clientDO2, clientCache.get(clientDO2.getClientId()));
-    }
-
     @Test
     public void testCreateOAuth2Client_success() {
         // 准备参数
@@ -73,7 +52,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
         // 校验记录的属性是否正确
         OAuth2ClientDO oAuth2Client = oauth2ClientMapper.selectById(oauth2ClientId);
         assertPojoEquals(reqVO, oAuth2Client);
-        verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
     }
 
     @Test
@@ -92,7 +70,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
         // 校验是否更新正确
         OAuth2ClientDO oAuth2Client = oauth2ClientMapper.selectById(reqVO.getId()); // 获取最新的
         assertPojoEquals(reqVO, oAuth2Client);
-        verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
     }
 
     @Test
@@ -116,7 +93,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
         oauth2ClientService.deleteOAuth2Client(id);
         // 校验数据不存在了
         assertNull(oauth2ClientMapper.selectById(id));
-        verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
     }
 
     @Test
@@ -166,63 +142,79 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
         assertPojoEquals(clientDO, dbClientDO);
     }
 
+    @Test
+    public void testGetOAuth2ClientFromCache() {
+        // mock 数据
+        OAuth2ClientDO clientDO = randomPojo(OAuth2ClientDO.class);
+        oauth2ClientMapper.insert(clientDO);
+        // 准备参数
+        String clientId = clientDO.getClientId();
+
+        // 调用,并断言
+        OAuth2ClientDO dbClientDO = oauth2ClientService.getOAuth2ClientFromCache(clientId);
+        assertPojoEquals(clientDO, dbClientDO);
+    }
+
     @Test
     public void testGetOAuth2ClientPage() {
-       // mock 数据
-       OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class, o -> { // 等会查询到
-           o.setName("潜龙");
-           o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-       });
-       oauth2ClientMapper.insert(dbOAuth2Client);
-       // 测试 name 不匹配
-       oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setName("凤凰")));
-       // 测试 status 不匹配
-       oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
-       // 准备参数
-       OAuth2ClientPageReqVO reqVO = new OAuth2ClientPageReqVO();
-       reqVO.setName("龙");
-       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
-
-       // 调用
-       PageResult<OAuth2ClientDO> pageResult = oauth2ClientService.getOAuth2ClientPage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbOAuth2Client, pageResult.getList().get(0));
+        // mock 数据
+        OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class, o -> { // 等会查询到
+            o.setName("潜龙");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        oauth2ClientMapper.insert(dbOAuth2Client);
+        // 测试 name 不匹配
+        oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setName("凤凰")));
+        // 测试 status 不匹配
+        oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 准备参数
+        OAuth2ClientPageReqVO reqVO = new OAuth2ClientPageReqVO();
+        reqVO.setName("龙");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+
+        // 调用
+        PageResult<OAuth2ClientDO> pageResult = oauth2ClientService.getOAuth2ClientPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbOAuth2Client, pageResult.getList().get(0));
     }
 
     @Test
     public void testValidOAuthClientFromCache() {
-        // mock 方法
-        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId("default")
-                .setStatus(CommonStatusEnum.ENABLE.getStatus());
-        OAuth2ClientDO client02 = randomPojo(OAuth2ClientDO.class).setClientId("disable")
-                .setStatus(CommonStatusEnum.DISABLE.getStatus());
-        Map<String, OAuth2ClientDO> clientCache = MapUtil.<String, OAuth2ClientDO>builder()
-                .put(client.getClientId(), client)
-                .put(client02.getClientId(), client02).build();
-        oauth2ClientService.setClientCache(clientCache);
-
-        // 调用,并断言
-        assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache(randomString(),
-                null, null, null, null), OAUTH2_CLIENT_NOT_EXISTS);
-        assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("disable",
-                null, null, null, null), OAUTH2_CLIENT_DISABLE);
-        assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
-                randomString(), null, null, null), OAUTH2_CLIENT_CLIENT_SECRET_ERROR);
-        assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
-                null, randomString(), null, null), OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS);
-        assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
-                null, null, Collections.singleton(randomString()), null), OAUTH2_CLIENT_SCOPE_OVER);
-        assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
-                null, null, null, "test"), OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH, "test");
-        // 成功调用(1:参数完整)
-        OAuth2ClientDO result = oauth2ClientService.validOAuthClientFromCache(client.getClientId(), client.getSecret(),
-                client.getAuthorizedGrantTypes().get(0), client.getScopes(), client.getRedirectUris().get(0));
-        assertPojoEquals(client, result);
-        // 成功调用(2:只有 clientId 参数)
-        result = oauth2ClientService.validOAuthClientFromCache(client.getClientId());
-        assertPojoEquals(client, result);
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(OAuth2ClientServiceImpl.class)))
+                    .thenReturn(oauth2ClientService);
+
+            // mock 方法
+            OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId("default")
+                    .setStatus(CommonStatusEnum.ENABLE.getStatus());
+            oauth2ClientMapper.insert(client);
+            OAuth2ClientDO client02 = randomPojo(OAuth2ClientDO.class).setClientId("disable")
+                    .setStatus(CommonStatusEnum.DISABLE.getStatus());
+            oauth2ClientMapper.insert(client02);
+
+            // 调用,并断言
+            assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache(randomString(),
+                    null, null, null, null), OAUTH2_CLIENT_NOT_EXISTS);
+            assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("disable",
+                    null, null, null, null), OAUTH2_CLIENT_DISABLE);
+            assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
+                    randomString(), null, null, null), OAUTH2_CLIENT_CLIENT_SECRET_ERROR);
+            assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
+                    null, randomString(), null, null), OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS);
+            assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
+                    null, null, Collections.singleton(randomString()), null), OAUTH2_CLIENT_SCOPE_OVER);
+            assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
+                    null, null, null, "test"), OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH, "test");
+            // 成功调用(1:参数完整)
+            OAuth2ClientDO result = oauth2ClientService.validOAuthClientFromCache(client.getClientId(), client.getSecret(),
+                    client.getAuthorizedGrantTypes().get(0), client.getScopes(), client.getRedirectUris().get(0));
+            assertPojoEquals(client, result);
+            // 成功调用(2:只有 clientId 参数)
+            result = oauth2ClientService.validOAuthClientFromCache(client.getClientId());
+            assertPojoEquals(client, result);
+        }
     }
 
 }

+ 14 - 126
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImplTest.java

@@ -8,10 +8,7 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuUp
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.MenuMapper;
 import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
-import cn.iocoder.yudao.module.system.mq.producer.permission.MenuProducer;
 import cn.iocoder.yudao.module.system.service.tenant.TenantService;
-import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.Multimap;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
@@ -26,8 +23,6 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
 import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
-import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -46,35 +41,12 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
     @MockBean
     private PermissionService permissionService;
     @MockBean
-    private MenuProducer menuProducer;
-    @MockBean
     private TenantService tenantService;
 
-    @Test
-    public void testInitLocalCache_success() {
-        MenuDO menuDO1 = randomPojo(MenuDO.class);
-        menuMapper.insert(menuDO1);
-        MenuDO menuDO2 = randomPojo(MenuDO.class);
-        menuMapper.insert(menuDO2);
-
-        // 调用
-        menuService.initLocalCache();
-        // 校验 menuCache 缓存
-        Map<Long, MenuDO> menuCache = menuService.getMenuCache();
-        assertEquals(2, menuCache.size());
-        assertPojoEquals(menuDO1, menuCache.get(menuDO1.getId()));
-        assertPojoEquals(menuDO2, menuCache.get(menuDO2.getId()));
-        // 校验 permissionMenuCache 缓存
-        Multimap<String, MenuDO> permissionMenuCache = menuService.getPermissionMenuCache();
-        assertEquals(2, permissionMenuCache.size());
-        assertPojoEquals(menuDO1, permissionMenuCache.get(menuDO1.getPermission()));
-        assertPojoEquals(menuDO2, permissionMenuCache.get(menuDO2.getPermission()));
-    }
-
     @Test
     public void testCreateMenu_success() {
         // mock 数据(构造父菜单)
-        MenuDO menuDO = createMenuDO(MenuTypeEnum.MENU,
+        MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU,
                 "parent", 0L);
         menuMapper.insert(menuDO);
         Long parentId = menuDO.getId();
@@ -89,14 +61,12 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
         // 校验记录的属性是否正确
         MenuDO dbMenu = menuMapper.selectById(menuId);
         assertPojoEquals(reqVO, dbMenu);
-        // 校验调用
-        verify(menuProducer).sendMenuRefreshMessage();
     }
 
     @Test
     public void testUpdateMenu_success() {
         // mock 数据(构造父子菜单)
-        MenuDO sonMenuDO = initParentAndSonMenu();
+        MenuDO sonMenuDO = createParentAndSonMenu();
         Long sonId = sonMenuDO.getId();
         // 准备参数
         MenuUpdateReqVO reqVO = randomPojo(MenuUpdateReqVO.class, o -> {
@@ -111,8 +81,6 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
         // 校验记录的属性是否正确
         MenuDO dbMenu = menuMapper.selectById(sonId);
         assertPojoEquals(reqVO, dbMenu);
-        // 校验调用
-        verify(menuProducer).sendMenuRefreshMessage();
     }
 
     @Test
@@ -137,7 +105,6 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
         MenuDO dbMenuDO = menuMapper.selectById(id);
         assertNull(dbMenuDO);
         verify(permissionService).processMenuDeleted(id);
-        verify(menuProducer).sendMenuRefreshMessage();
     }
 
     @Test
@@ -149,7 +116,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testDeleteMenu_existChildren() {
         // mock 数据(构造父子菜单)
-        MenuDO sonMenu = initParentAndSonMenu();
+        MenuDO sonMenu = createParentAndSonMenu();
         // 准备参数
         Long parentId = sonMenu.getParentId();
 
@@ -218,85 +185,6 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
         assertPojoEquals(menu100, result.get(0));
     }
 
-    @Test
-    public void testListMenusFromCache_withoutId() {
-        // mock 缓存
-        Map<Long, MenuDO> menuCache = new HashMap<>();
-        // 可被匹配
-        MenuDO menuDO = randomPojo(MenuDO.class, o -> o.setId(1L)
-                .setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        menuCache.put(menuDO.getId(), menuDO);
-        // 测试 type 不匹配
-        menuCache.put(3L, randomPojo(MenuDO.class, o -> o.setId(3L)
-                .setType(MenuTypeEnum.BUTTON.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus())));
-        // 测试 status 不匹配
-        menuCache.put(4L, randomPojo(MenuDO.class, o -> o.setId(4L)
-                .setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.DISABLE.getStatus())));
-        menuService.setMenuCache(menuCache);
-        // 准备参数
-        Collection<Integer> menuTypes = singletonList(MenuTypeEnum.MENU.getType());
-        Collection<Integer> menusStatuses = singletonList(CommonStatusEnum.ENABLE.getStatus());
-
-        // 调用
-        List<MenuDO> list = menuService.getMenuListFromCache(menuTypes, menusStatuses);
-        // 断言
-        assertEquals(1, list.size());
-        assertPojoEquals(menuDO, list.get(0));
-    }
-
-    @Test
-    public void testListMenusFromCache_withId() {
-        // mock 缓存
-        Map<Long, MenuDO> menuCache = new HashMap<>();
-        // 可被匹配
-        MenuDO menuDO = randomPojo(MenuDO.class, o -> o.setId(1L)
-                .setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        menuCache.put(menuDO.getId(), menuDO);
-        // 测试 id 不匹配
-        menuCache.put(2L, randomPojo(MenuDO.class, o -> o.setId(2L)
-                .setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus())));
-        // 测试 type 不匹配
-        menuCache.put(3L, randomPojo(MenuDO.class, o -> o.setId(3L)
-                .setType(MenuTypeEnum.BUTTON.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus())));
-        // 测试 status 不匹配
-        menuCache.put(4L, randomPojo(MenuDO.class, o -> o.setId(4L)
-                .setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.DISABLE.getStatus())));
-        menuService.setMenuCache(menuCache);
-        // 准备参数
-        Collection<Long> menuIds = asList(1L, 3L, 4L);
-        Collection<Integer> menuTypes = singletonList(MenuTypeEnum.MENU.getType());
-        Collection<Integer> menusStatuses = singletonList(CommonStatusEnum.ENABLE.getStatus());
-
-        // 调用
-        List<MenuDO> list = menuService.getMenuListFromCache(menuIds, menuTypes, menusStatuses);
-        // 断言
-        assertEquals(1, list.size());
-        assertPojoEquals(menuDO, list.get(0));
-    }
-
-    @Test
-    public void testGetMenuListByPermissionFromCache() {
-        // mock 缓存
-        Multimap<String, MenuDO> permissionMenuCache = LinkedListMultimap.create();
-        // 可被匹配
-        MenuDO menuDO01 = randomPojo(MenuDO.class, o -> o.setId(1L).setPermission("123"));
-        permissionMenuCache.put(menuDO01.getPermission(), menuDO01);
-        MenuDO menuDO02 = randomPojo(MenuDO.class, o -> o.setId(2L).setPermission("123"));
-        permissionMenuCache.put(menuDO02.getPermission(), menuDO02);
-        // 不可匹配
-        permissionMenuCache.put("456", randomPojo(MenuDO.class, o -> o.setId(3L).setPermission("456")));
-        menuService.setPermissionMenuCache(permissionMenuCache);
-        // 准备参数
-        String permission = "123";
-
-        // 调用
-        List<MenuDO> list = menuService.getMenuListByPermissionFromCache(permission);
-        // 断言
-        assertEquals(2, list.size());
-        assertPojoEquals(menuDO01, list.get(0));
-        assertPojoEquals(menuDO02, list.get(1));
-    }
-
     @Test
     public void testGetMenu() {
         // mock 数据
@@ -314,7 +202,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testValidateParentMenu_success() {
         // mock 数据
-        MenuDO menuDO = createMenuDO(MenuTypeEnum.MENU, "parent", 0L);
+        MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU, "parent", 0L);
         menuMapper.insert(menuDO);
         // 准备参数
         Long parentId = menuDO.getId();
@@ -340,7 +228,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testValidateParentMenu_parentTypeError() {
         // mock 数据
-        MenuDO menuDO = createMenuDO(MenuTypeEnum.BUTTON, "parent", 0L);
+        MenuDO menuDO = buildMenuDO(MenuTypeEnum.BUTTON, "parent", 0L);
         menuMapper.insert(menuDO);
         // 准备参数
         Long parentId = menuDO.getId();
@@ -353,7 +241,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testValidateMenu_success() {
         // mock 父子菜单
-        MenuDO sonMenu = initParentAndSonMenu();
+        MenuDO sonMenu = createParentAndSonMenu();
         // 准备参数
         Long parentId = sonMenu.getParentId();
         Long otherSonMenuId = randomLongId();
@@ -366,7 +254,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testValidateMenu_sonMenuNameDuplicate() {
         // mock 父子菜单
-        MenuDO sonMenu = initParentAndSonMenu();
+        MenuDO sonMenu = createParentAndSonMenu();
         // 准备参数
         Long parentId = sonMenu.getParentId();
         Long otherSonMenuId = randomLongId();
@@ -380,26 +268,26 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
     // ====================== 初始化方法 ======================
 
     /**
-     * 构造父子菜单,返回子菜单
+     * 插入父子菜单,返回子菜单
      *
      * @return 子菜单
      */
-    private MenuDO initParentAndSonMenu() {
+    private MenuDO createParentAndSonMenu() {
         // 构造父子菜单
-        MenuDO parentMenuDO = createMenuDO(MenuTypeEnum.MENU, "parent", ID_ROOT);
+        MenuDO parentMenuDO = buildMenuDO(MenuTypeEnum.MENU, "parent", ID_ROOT);
         menuMapper.insert(parentMenuDO);
         // 构建子菜单
-        MenuDO sonMenuDO = createMenuDO(MenuTypeEnum.MENU, "testSonName",
+        MenuDO sonMenuDO = buildMenuDO(MenuTypeEnum.MENU, "testSonName",
                 parentMenuDO.getParentId());
         menuMapper.insert(sonMenuDO);
         return sonMenuDO;
     }
 
-    private MenuDO createMenuDO(MenuTypeEnum type, String name, Long parentId) {
-        return createMenuDO(type, name, parentId, randomCommonStatus());
+    private MenuDO buildMenuDO(MenuTypeEnum type, String name, Long parentId) {
+        return buildMenuDO(type, name, parentId, randomCommonStatus());
     }
 
-    private MenuDO createMenuDO(MenuTypeEnum type, String name, Long parentId, Integer status) {
+    private MenuDO buildMenuDO(MenuTypeEnum type, String name, Long parentId, Integer status) {
         return randomPojo(MenuDO.class, o -> o.setId(null).setName(name).setParentId(parentId)
                 .setType(type.getType()).setStatus(status));
     }

+ 312 - 330
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.system.service.permission;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.map.MapUtil;
+import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
@@ -14,32 +14,28 @@ import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
 import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
-import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
 import cn.iocoder.yudao.module.system.service.dept.DeptService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.Multimap;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
 import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
+import static cn.hutool.core.collection.ListUtil.toList;
 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
-import static java.util.Arrays.asList;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
 import static java.util.Collections.singleton;
 import static java.util.Collections.singletonList;
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 @Import({PermissionServiceImpl.class})
 public class PermissionServiceTest extends BaseDbUnitTest {
@@ -61,99 +57,140 @@ public class PermissionServiceTest extends BaseDbUnitTest {
     @MockBean
     private AdminUserService userService;
 
-    @MockBean
-    private PermissionProducer permissionProducer;
-
     @Test
-    public void testInitLocalCacheForRoleMenu() {
-        // mock 数据
-        RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(10L));
-        roleMenuMapper.insert(roleMenuDO01);
-        RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(20L));
-        roleMenuMapper.insert(roleMenuDO02);
-
-        // 调用
-        permissionService.initLocalCacheForRoleMenu();
-        // 断言 roleMenuCache 缓存
-        assertEquals(1, permissionService.getRoleMenuCache().keySet().size());
-        assertEquals(asList(10L, 20L), permissionService.getRoleMenuCache().get(1L));
-        // 断言 menuRoleCache 缓存
-        assertEquals(2, permissionService.getMenuRoleCache().size());
-        assertEquals(singletonList(1L), permissionService.getMenuRoleCache().get(10L));
-        assertEquals(singletonList(1L), permissionService.getMenuRoleCache().get(20L));
+    public void testHasAnyPermissions_superAdmin() {
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
+                    .thenReturn(permissionService);
+
+            // 准备参数
+            Long userId = 1L;
+            String[] roles = new String[]{"system:user:query", "system:user:create"};
+            // mock 用户登录的角色
+            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
+            RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
+                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+            when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));
+            // mock 其它方法
+            when(roleService.hasAnySuperAdmin(eq(asSet(100L)))).thenReturn(true);
+
+            // 调用,并断言
+            assertTrue(permissionService.hasAnyPermissions(userId, roles));
+        }
     }
 
     @Test
-    public void testInitLocalCacheForUserRole() {
-        // mock 数据
-        UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
-        userRoleMapper.insert(userRoleDO01);
-        UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
-        userRoleMapper.insert(roleMenuDO02);
+    public void testHasAnyPermissions_normal() {
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
+                    .thenReturn(permissionService);
+
+            // 准备参数
+            Long userId = 1L;
+            String[] roles = new String[]{"system:user:query", "system:user:create"};
+            // mock 用户登录的角色
+            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
+            RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
+                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+            when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));
+            // mock 菜单
+            Long menuId = 1000L;
+            when(menuService.getMenuIdListByPermissionFromCache(
+                    eq("system:user:create"))).thenReturn(singletonList(menuId));
+            roleMenuMapper.insert(randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1000L));
+
+            // 调用,并断言
+            assertTrue(permissionService.hasAnyPermissions(userId, roles));
+        }
+    }
 
-        // 调用
-        permissionService.initLocalCacheForUserRole();
-        // 断言 roleMenuCache 缓存
-        assertEquals(1, permissionService.getUserRoleCache().size());
-        assertEquals(asSet(10L, 20L), permissionService.getUserRoleCache().get(1L));
+    @Test
+    public void testHasAnyRoles() {
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
+                    .thenReturn(permissionService);
+
+            // 准备参数
+            Long userId = 1L;
+            String[] roles = new String[]{"yunai", "tudou"};
+            // mock 用户与角色的缓存
+            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
+            RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L).setCode("tudou")
+                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+            when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));
+
+            // 调用,并断言
+            assertTrue(permissionService.hasAnyRoles(userId, roles));
+        }
     }
 
+    // ========== 角色-菜单的相关方法  ==========
+
     @Test
-    public void testGetRoleMenuListFromCache_superAdmin() {
+    public void testAssignRoleMenu() {
         // 准备参数
-        Collection<Long> roleIds = singletonList(100L);
-        Collection<Integer> menuTypes = asList(2, 3);
-        Collection<Integer> menusStatuses = asList(0, 1);
-        // mock 方法
-        List<RoleDO> roleList = singletonList(randomPojo(RoleDO.class, o -> o.setId(100L)));
-        when(roleService.getRoleListFromCache(eq(roleIds))).thenReturn(roleList);
-        when(roleService.hasAnySuperAdmin(same(roleList))).thenReturn(true);
-        List<MenuDO> menuList = randomPojoList(MenuDO.class);
-        when(menuService.getMenuListFromCache(eq(menuTypes), eq(menusStatuses))).thenReturn(menuList);
+        Long roleId = 1L;
+        Set<Long> menuIds = asSet(200L, 300L);
+        // mock 数据
+        RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(100L);
+        roleMenuMapper.insert(roleMenu01);
+        RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(200L);
+        roleMenuMapper.insert(roleMenu02);
 
         // 调用
-        List<MenuDO> result = permissionService.getRoleMenuListFromCache(roleIds, menuTypes, menusStatuses);
+        permissionService.assignRoleMenu(roleId, menuIds);
         // 断言
-        assertSame(menuList, result);
+        List<RoleMenuDO> roleMenuList = roleMenuMapper.selectList();
+        assertEquals(2, roleMenuList.size());
+        assertEquals(1L, roleMenuList.get(0).getRoleId());
+        assertEquals(200L, roleMenuList.get(0).getMenuId());
+        assertEquals(1L, roleMenuList.get(1).getRoleId());
+        assertEquals(300L, roleMenuList.get(1).getMenuId());
     }
 
     @Test
-    public void testGetRoleMenuListFromCache_normal() {
+    public void testProcessRoleDeleted() {
         // 准备参数
-        Collection<Long> roleIds = asSet(100L, 200L);
-        Collection<Integer> menuTypes = asList(2, 3);
-        Collection<Integer> menusStatuses = asList(0, 1);
-        // mock 方法
-        Multimap<Long, Long> roleMenuCache = ImmutableMultimap.<Long, Long>builder().put(100L, 1000L)
-                .put(200L, 2000L).put(200L, 2001L).build();
-        permissionService.setRoleMenuCache(roleMenuCache);
-        List<MenuDO> menuList = randomPojoList(MenuDO.class);
-        when(menuService.getMenuListFromCache(eq(asList(1000L, 2000L, 2001L)), eq(menuTypes), eq(menusStatuses))).thenReturn(menuList);
+        Long roleId = randomLongId();
+        // mock 数据 UserRole
+        UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setRoleId(roleId)); // 被删除
+        userRoleMapper.insert(userRoleDO01);
+        UserRoleDO userRoleDO02 = randomPojo(UserRoleDO.class); // 不被删除
+        userRoleMapper.insert(userRoleDO02);
+        // mock 数据 RoleMenu
+        RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(roleId)); // 被删除
+        roleMenuMapper.insert(roleMenuDO01);
+        RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class); // 不被删除
+        roleMenuMapper.insert(roleMenuDO02);
 
         // 调用
-        List<MenuDO> result = permissionService.getRoleMenuListFromCache(roleIds, menuTypes, menusStatuses);
-        // 断言
-        assertSame(menuList, result);
+        permissionService.processRoleDeleted(roleId);
+        // 断言数据 RoleMenuDO
+        List<RoleMenuDO> dbRoleMenus = roleMenuMapper.selectList();
+        assertEquals(1, dbRoleMenus.size());
+        assertPojoEquals(dbRoleMenus.get(0), roleMenuDO02);
+        // 断言数据 UserRoleDO
+        List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();
+        assertEquals(1, dbUserRoles.size());
+        assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
     }
 
     @Test
-    public void testGetUserRoleIdsFromCache() {
+    public void testProcessMenuDeleted() {
         // 准备参数
-        Long userId = 1L;
-        Collection<Integer> roleStatuses = singleton(CommonStatusEnum.ENABLE.getStatus());
-        // mock 方法
-        Map<Long, Set<Long>> userRoleCache = MapUtil.<Long, Set<Long>>builder()
-                .put(1L, asSet(10L, 20L)).build();
-        permissionService.setUserRoleCache(userRoleCache);
-        RoleDO roleDO01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        when(roleService.getRoleFromCache(eq(10L))).thenReturn(roleDO01);
-        RoleDO roleDO02 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
-        when(roleService.getRoleFromCache(eq(20L))).thenReturn(roleDO02);
+        Long menuId = randomLongId();
+        // mock 数据
+        RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setMenuId(menuId)); // 被删除
+        roleMenuMapper.insert(roleMenuDO01);
+        RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class); // 不被删除
+        roleMenuMapper.insert(roleMenuDO02);
 
         // 调用
-        Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(userId, roleStatuses);
-        // 断言
-        assertEquals(asSet(10L), roleIds);
+        permissionService.processMenuDeleted(menuId);
+        // 断言数据
+        List<RoleMenuDO> dbRoleMenus = roleMenuMapper.selectList();
+        assertEquals(1, dbRoleMenus.size());
+        assertPojoEquals(dbRoleMenus.get(0), roleMenuDO02);
     }
 
     @Test
@@ -166,7 +203,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
         when(menuService.getMenuList()).thenReturn(menuList);
 
         // 调用
-        Set<Long> menuIds = permissionService.getRoleMenuIds(roleId);
+        Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(roleId);
         // 断言
         assertEquals(singleton(1L), menuIds);
     }
@@ -182,34 +219,29 @@ public class PermissionServiceTest extends BaseDbUnitTest {
         roleMenuMapper.insert(roleMenu02);
 
         // 调用
-        Set<Long> menuIds = permissionService.getRoleMenuIds(roleId);
+        Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(roleId);
         // 断言
         assertEquals(asSet(1L, 2L), menuIds);
     }
 
     @Test
-    public void testAssignRoleMenu() {
+    public void testGetMenuRoleIdListByMenuIdFromCache() {
         // 准备参数
-        Long roleId = 1L;
-        Set<Long> menuIds = asSet(200L, 300L);
+        Long menuId = 1L;
         // mock 数据
-        RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(100L);
+        RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1L);
         roleMenuMapper.insert(roleMenu01);
-        RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(200L);
+        RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(200L).setMenuId(1L);
         roleMenuMapper.insert(roleMenu02);
 
         // 调用
-        permissionService.assignRoleMenu(roleId, menuIds);
+        Set<Long> roleIds = permissionService.getMenuRoleIdListByMenuIdFromCache(menuId);
         // 断言
-        List<RoleMenuDO> roleMenuList = roleMenuMapper.selectList();
-        assertEquals(2, roleMenuList.size());
-        assertEquals(1L, roleMenuList.get(0).getRoleId());
-        assertEquals(200L, roleMenuList.get(0).getMenuId());
-        assertEquals(1L, roleMenuList.get(1).getRoleId());
-        assertEquals(300L, roleMenuList.get(1).getMenuId());
-        verify(permissionProducer).sendRoleMenuRefreshMessage();
+        assertEquals(asSet(100L, 200L), roleIds);
     }
 
+    // ========== 用户-角色的相关方法  ==========
+
     @Test
     public void testAssignUserRole() {
         // 准备参数
@@ -230,7 +262,24 @@ public class PermissionServiceTest extends BaseDbUnitTest {
         assertEquals(200L, userRoleDOList.get(0).getRoleId());
         assertEquals(1L, userRoleDOList.get(1).getUserId());
         assertEquals(300L, userRoleDOList.get(1).getRoleId());
-        verify(permissionProducer).sendUserRoleRefreshMessage();
+    }
+
+    @Test
+    public void testProcessUserDeleted() {
+        // 准备参数
+        Long userId = randomLongId();
+        // mock 数据
+        UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(userId)); // 被删除
+        userRoleMapper.insert(userRoleDO01);
+        UserRoleDO userRoleDO02 = randomPojo(UserRoleDO.class); // 不被删除
+        userRoleMapper.insert(userRoleDO02);
+
+        // 调用
+        permissionService.processUserDeleted(userId);
+        // 断言数据
+        List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();
+        assertEquals(1, dbUserRoles.size());
+        assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
     }
 
     @Test
@@ -250,296 +299,229 @@ public class PermissionServiceTest extends BaseDbUnitTest {
     }
 
     @Test
-    public void testGetUserRoleIdListByRoleIds() {
+    public void testGetUserRoleIdListByUserIdFromCache() {
         // 准备参数
-        Collection<Long> roleIds = asSet(10L, 20L);
+        Long userId = 1L;
         // mock 数据
         UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
         userRoleMapper.insert(userRoleDO01);
-        UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(2L).setRoleId(20L));
+        UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
         userRoleMapper.insert(roleMenuDO02);
 
         // 调用
-        Set<Long> result = permissionService.getUserRoleIdListByRoleIds(roleIds);
+        Set<Long> result = permissionService.getUserRoleIdListByUserIdFromCache(userId);
         // 断言
-        assertEquals(asSet(1L, 2L), result);
-    }
-
-    @Test
-    public void testAssignRoleDataScope() {
-        // 准备参数
-        Long roleId = 1L;
-        Integer dataScope = 2;
-        Set<Long> dataScopeDeptIds = asSet(10L, 20L);
-
-        // 调用
-        permissionService.assignRoleDataScope(roleId, dataScope, dataScopeDeptIds);
-        // 断言
-        verify(roleService).updateRoleDataScope(eq(roleId), eq(dataScope), eq(dataScopeDeptIds));
-    }
-
-    @Test
-    public void testProcessRoleDeleted() {
-        // 准备参数
-        Long roleId = randomLongId();
-        // mock 数据 UserRole
-        UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setRoleId(roleId)); // 被删除
-        userRoleMapper.insert(userRoleDO01);
-        UserRoleDO userRoleDO02 = randomPojo(UserRoleDO.class); // 不被删除
-        userRoleMapper.insert(userRoleDO02);
-        // mock 数据 RoleMenu
-        RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(roleId)); // 被删除
-        roleMenuMapper.insert(roleMenuDO01);
-        RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class); // 不被删除
-        roleMenuMapper.insert(roleMenuDO02);
-
-        // 调用
-        permissionService.processRoleDeleted(roleId);
-        // 断言数据 RoleMenuDO
-        List<RoleMenuDO> dbRoleMenus = roleMenuMapper.selectList();
-        assertEquals(1, dbRoleMenus.size());
-        assertPojoEquals(dbRoleMenus.get(0), roleMenuDO02);
-        // 断言数据 UserRoleDO
-        List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();
-        assertEquals(1, dbUserRoles.size());
-        assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
-        // 断言调用
-        verify(permissionProducer).sendRoleMenuRefreshMessage();
-        verify(permissionProducer).sendUserRoleRefreshMessage();
+        assertEquals(asSet(10L, 20L), result);
     }
 
     @Test
-    public void testProcessMenuDeleted() {
+    public void testGetUserRoleIdsFromCache() {
         // 准备参数
-        Long menuId = randomLongId();
+        Long userId = 1L;
         // mock 数据
-        RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setMenuId(menuId)); // 被删除
-        roleMenuMapper.insert(roleMenuDO01);
-        RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class); // 不被删除
-        roleMenuMapper.insert(roleMenuDO02);
+        UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
+        userRoleMapper.insert(userRoleDO01);
+        UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
+        userRoleMapper.insert(roleMenuDO02);
 
         // 调用
-        permissionService.processMenuDeleted(menuId);
-        // 断言数据
-        List<RoleMenuDO> dbRoleMenus = roleMenuMapper.selectList();
-        assertEquals(1, dbRoleMenus.size());
-        assertPojoEquals(dbRoleMenus.get(0), roleMenuDO02);
-        // 断言调用
-        verify(permissionProducer).sendRoleMenuRefreshMessage();
+        Set<Long> result = permissionService.getUserRoleIdListByUserIdFromCache(userId);
+        // 断言
+        assertEquals(asSet(10L, 20L), result);
     }
 
     @Test
-    public void testProcessUserDeleted() {
+    public void testGetUserRoleIdListByRoleId() {
         // 准备参数
-        Long userId = randomLongId();
+        Collection<Long> roleIds = asSet(10L, 20L);
         // mock 数据
-        UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(userId)); // 被删除
+        UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
         userRoleMapper.insert(userRoleDO01);
-        UserRoleDO userRoleDO02 = randomPojo(UserRoleDO.class); // 不被删除
-        userRoleMapper.insert(userRoleDO02);
-
-        // 调用
-        permissionService.processUserDeleted(userId);
-        // 断言数据
-        List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();
-        assertEquals(1, dbUserRoles.size());
-        assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
-        // 断言调用
-        verify(permissionProducer).sendUserRoleRefreshMessage();
-    }
-
-    @Test
-    public void testHasAnyPermissions_superAdmin() {
-        // 准备参数
-        Long userId = 1L;
-        String[] roles = new String[]{"system:user:query", "system:user:create"};
-        // mock 用户与角色的缓存
-        permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(100L)).build());
-        RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
-                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        when(roleService.getRoleFromCache(eq(100L))).thenReturn(role);
-        // mock 其它方法
-        when(roleService.hasAnySuperAdmin(eq(asSet(100L)))).thenReturn(true);
+        UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(2L).setRoleId(20L));
+        userRoleMapper.insert(roleMenuDO02);
 
         // 调用
-        boolean has = permissionService.hasAnyPermissions(userId, roles);
+        Set<Long> result = permissionService.getUserRoleIdListByRoleId(roleIds);
         // 断言
-        assertTrue(has);
+        assertEquals(asSet(1L, 2L), result);
     }
 
     @Test
-    public void testHasAnyPermissions_normal() {
-        // 准备参数
-        Long userId = 1L;
-        String[] roles = new String[]{"system:user:query", "system:user:create"};
-        // mock 用户与角色的缓存
-        permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(100L)).build());
-        RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
-                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        when(roleService.getRoleFromCache(eq(100L))).thenReturn(role);
-        // mock 其它方法
-        MenuDO menu = randomPojo(MenuDO.class, o -> o.setId(1000L));
-        when(menuService.getMenuListByPermissionFromCache(eq("system:user:create"))).thenReturn(singletonList(menu));
-        permissionService.setMenuRoleCache(ImmutableMultimap.<Long, Long>builder().put(1000L, 100L).build());
-
-
-        // 调用
-        boolean has = permissionService.hasAnyPermissions(userId, roles);
-        // 断言
-        assertTrue(has);
+    public void testGetEnableUserRoleListByUserIdFromCache() {
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
+                    .thenReturn(permissionService);
+
+            // 准备参数
+            Long userId = 1L;
+            // mock 用户登录的角色
+            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
+            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(200L));
+            RoleDO role01 = randomPojo(RoleDO.class, o -> o.setId(100L)
+                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+            RoleDO role02 = randomPojo(RoleDO.class, o -> o.setId(200L)
+                    .setStatus(CommonStatusEnum.DISABLE.getStatus()));
+            when(roleService.getRoleListFromCache(eq(asSet(100L, 200L))))
+                    .thenReturn(toList(role01, role02));
+
+            // 调用
+            List<RoleDO> result = permissionService.getEnableUserRoleListByUserIdFromCache(userId);
+            // 断言
+            assertEquals(1, result.size());
+            assertPojoEquals(role01, result.get(0));
+        }
     }
 
-    @Test
-    public void testHasAnyRoles_superAdmin() {
-        // 准备参数
-        Long userId = 1L;
-        String[] roles = new String[]{"yunai", "tudou"};
-        // mock 用户与角色的缓存
-        permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(100L)).build());
-        RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
-                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        when(roleService.getRoleFromCache(eq(100L))).thenReturn(role);
-        // mock 其它方法
-        when(roleService.hasAnySuperAdmin(eq(asSet(100L)))).thenReturn(true);
-
-        // 调用
-        boolean has = permissionService.hasAnyRoles(userId, roles);
-        // 断言
-        assertTrue(has);
-    }
+    // ========== 用户-部门的相关方法  ==========
 
     @Test
-    public void testHasAnyRoles_normal() {
+    public void testAssignRoleDataScope() {
         // 准备参数
-        Long userId = 1L;
-        String[] roles = new String[]{"yunai", "tudou"};
-        // mock 用户与角色的缓存
-        permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(100L)).build());
-        RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L).setCode("yunai")
-                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        when(roleService.getRoleFromCache(eq(100L))).thenReturn(role);
-        // mock 其它方法
-        when(roleService.getRoleListFromCache(eq(asSet(100L)))).thenReturn(singletonList(role));
+        Long roleId = 1L;
+        Integer dataScope = 2;
+        Set<Long> dataScopeDeptIds = asSet(10L, 20L);
 
         // 调用
-        boolean has = permissionService.hasAnyRoles(userId, roles);
+        permissionService.assignRoleDataScope(roleId, dataScope, dataScopeDeptIds);
         // 断言
-        assertTrue(has);
+        verify(roleService).updateRoleDataScope(eq(roleId), eq(dataScope), eq(dataScopeDeptIds));
     }
 
     @Test
     public void testGetDeptDataPermission_All() {
-        // 准备参数
-        Long userId = 1L;
-        // mock 用户的角色编号
-        permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
-        // mock 获得用户的角色
-        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope())
-                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
-        when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
-
-        // 调用
-        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
-        // 断言
-        assertTrue(result.getAll());
-        assertFalse(result.getSelf());
-        assertTrue(CollUtil.isEmpty(result.getDeptIds()));
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
+                    .thenReturn(permissionService);
+
+            // 准备参数
+            Long userId = 1L;
+            // mock 用户的角色编号
+            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
+            // mock 获得用户的角色
+            RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope())
+                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+            when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
+
+            // 调用
+            DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
+            // 断言
+            assertTrue(result.getAll());
+            assertFalse(result.getSelf());
+            assertTrue(CollUtil.isEmpty(result.getDeptIds()));
+        }
     }
 
     @Test
     public void testGetDeptDataPermission_DeptCustom() {
-        // 准备参数
-        Long userId = 1L;
-        // mock 用户的角色编号
-        permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
-        // mock 获得用户的角色
-        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())
-                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
-        when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
-        // mock 部门的返回
-        when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
-
-        // 调用
-        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
-        // 断言
-        assertFalse(result.getAll());
-        assertFalse(result.getSelf());
-        assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size());
-        assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds()));
-        assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
+                    .thenReturn(permissionService);
+
+            // 准备参数
+            Long userId = 1L;
+            // mock 用户的角色编号
+            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
+            // mock 获得用户的角色
+            RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())
+                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+            when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
+            // mock 部门的返回
+            when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),
+                    null, null); // 最后返回 null 的目的,看看会不会重复调用
+
+            // 调用
+            DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
+            // 断言
+            assertFalse(result.getAll());
+            assertFalse(result.getSelf());
+            assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size());
+            assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds()));
+            assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
+        }
     }
 
     @Test
     public void testGetDeptDataPermission_DeptOnly() {
-        // 准备参数
-        Long userId = 1L;
-        // mock 用户的角色编号
-        permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
-        // mock 获得用户的角色
-        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope())
-                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
-        when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
-        // mock 部门的返回
-        when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
-
-        // 调用
-        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
-        // 断言
-        assertFalse(result.getAll());
-        assertFalse(result.getSelf());
-        assertEquals(1, result.getDeptIds().size());
-        assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
+                    .thenReturn(permissionService);
+
+            // 准备参数
+            Long userId = 1L;
+            // mock 用户的角色编号
+            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
+            // mock 获得用户的角色
+            RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope())
+                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+            when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
+            // mock 部门的返回
+            when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),
+                    null, null); // 最后返回 null 的目的,看看会不会重复调用
+
+            // 调用
+            DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
+            // 断言
+            assertFalse(result.getAll());
+            assertFalse(result.getSelf());
+            assertEquals(1, result.getDeptIds().size());
+            assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
+        }
     }
 
     @Test
     public void testGetDeptDataPermission_DeptAndChild() {
-        // 准备参数
-        Long userId = 1L;
-        // mock 用户的角色编号
-        permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
-        // mock 获得用户的角色
-        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope())
-                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
-        when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
-        // mock 部门的返回
-        when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
-        // mock 方法(部门)
-        DeptDO deptDO = randomPojo(DeptDO.class);
-        when(deptService.getDeptListByParentIdFromCache(eq(3L), eq(true)))
-                .thenReturn(singletonList(deptDO));
-
-        // 调用
-        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
-        // 断言
-        assertFalse(result.getAll());
-        assertFalse(result.getSelf());
-        assertEquals(2, result.getDeptIds().size());
-        assertTrue(CollUtil.contains(result.getDeptIds(), deptDO.getId()));
-        assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
+                    .thenReturn(permissionService);
+
+            // 准备参数
+            Long userId = 1L;
+            // mock 用户的角色编号
+            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
+            // mock 获得用户的角色
+            RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope())
+                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+            when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
+            // mock 部门的返回
+            when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),
+                    null, null); // 最后返回 null 的目的,看看会不会重复调用
+            // mock 方法(部门)
+            DeptDO deptDO = randomPojo(DeptDO.class);
+            when(deptService.getChildDeptIdListFromCache(eq(3L))).thenReturn(singleton(deptDO.getId()));
+
+            // 调用
+            DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
+            // 断言
+            assertFalse(result.getAll());
+            assertFalse(result.getSelf());
+            assertEquals(2, result.getDeptIds().size());
+            assertTrue(CollUtil.contains(result.getDeptIds(), deptDO.getId()));
+            assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
+        }
     }
 
     @Test
     public void testGetDeptDataPermission_Self() {
-        // 准备参数
-        Long userId = 1L;
-        // mock 用户的角色编号
-        permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
-        // mock 获得用户的角色
-        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope())
-                .setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
-        when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
-
-        // 调用
-        DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
-        // 断言
-        assertFalse(result.getAll());
-        assertTrue(result.getSelf());
-        assertTrue(CollUtil.isEmpty(result.getDeptIds()));
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
+                    .thenReturn(permissionService);
+
+            // 准备参数
+            Long userId = 1L;
+            // mock 用户的角色编号
+            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
+            // mock 获得用户的角色
+            RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope())
+                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));
+            when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
+
+            // 调用
+            DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
+            // 断言
+            assertFalse(result.getAll());
+            assertTrue(result.getSelf());
+            assertTrue(CollUtil.isEmpty(result.getDeptIds()));
+        }
     }
 
 }

+ 119 - 132
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImplTest.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.system.service.permission;
 
+import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
@@ -11,15 +12,14 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper;
 import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
-import cn.iocoder.yudao.module.system.mq.producer.permission.RoleProducer;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
 import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 import static cn.hutool.core.util.RandomUtil.randomEle;
@@ -33,6 +33,8 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 import static java.util.Collections.singleton;
 import static java.util.Collections.singletonList;
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.verify;
 
 @Import(RoleServiceImpl.class)
@@ -46,26 +48,9 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
 
     @MockBean
     private PermissionService permissionService;
-    @MockBean
-    private RoleProducer roleProducer;
 
     @Test
-    public void testInitLocalCache() {
-        RoleDO roleDO1 = randomPojo(RoleDO.class);
-        roleMapper.insert(roleDO1);
-        RoleDO roleDO2 = randomPojo(RoleDO.class);
-        roleMapper.insert(roleDO2);
-
-        // 调用
-        roleService.initLocalCache();
-        // 断言 roleCache 缓存
-        Map<Long, RoleDO> roleCache = roleService.getRoleCache();
-        assertPojoEquals(roleDO1, roleCache.get(roleDO1.getId()));
-        assertPojoEquals(roleDO2, roleCache.get(roleDO2.getId()));
-    }
-
-    @Test
-    public void testCreateRole_success() {
+    public void testCreateRole() {
         // 准备参数
         RoleCreateReqVO reqVO = randomPojo(RoleCreateReqVO.class);
 
@@ -77,12 +62,10 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
         assertEquals(RoleTypeEnum.CUSTOM.getType(), roleDO.getType());
         assertEquals(CommonStatusEnum.ENABLE.getStatus(), roleDO.getStatus());
         assertEquals(DataScopeEnum.ALL.getScope(), roleDO.getDataScope());
-        // verify 发送刷新消息
-        verify(roleProducer).sendRoleRefreshMessage();
     }
 
     @Test
-    public void testUpdateRole_success() {
+    public void testUpdateRole() {
         // mock 数据
         RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));
         roleMapper.insert(roleDO);
@@ -95,12 +78,10 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
         // 断言
         RoleDO newRoleDO = roleMapper.selectById(id);
         assertPojoEquals(reqVO, newRoleDO);
-        // verify 发送刷新消息
-        verify(roleProducer).sendRoleRefreshMessage();
     }
 
     @Test
-    public void testUpdateRoleStatus_success() {
+    public void testUpdateRoleStatus() {
         // mock 数据
         RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
                 .setType(RoleTypeEnum.CUSTOM.getType()));
@@ -114,12 +95,10 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
         // 断言
         RoleDO dbRoleDO = roleMapper.selectById(roleId);
         assertEquals(CommonStatusEnum.DISABLE.getStatus(), dbRoleDO.getStatus());
-        // verify 发送刷新消息
-        verify(roleProducer).sendRoleRefreshMessage();
     }
 
     @Test
-    public void testUpdateRoleDataScope_success() {
+    public void testUpdateRoleDataScope() {
         // mock 数据
         RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));
         roleMapper.insert(roleDO);
@@ -134,12 +113,10 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
         RoleDO dbRoleDO = roleMapper.selectById(id);
         assertEquals(dataScope, dbRoleDO.getDataScope());
         assertEquals(dataScopeRoleIds, dbRoleDO.getDataScopeDeptIds());
-        // verify 发送刷新消息
-        verify(roleProducer).sendRoleRefreshMessage();
     }
 
     @Test
-    public void testDeleteRole_success() {
+    public void testDeleteRole() {
         // mock 数据
         RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));
         roleMapper.insert(roleDO);
@@ -152,23 +129,65 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
         assertNull(roleMapper.selectById(id));
         // verify 删除相关数据
         verify(permissionService).processRoleDeleted(id);
-        // verify 发送刷新消息
-        verify(roleProducer).sendRoleRefreshMessage();
     }
 
     @Test
-    public void testGetRoleFromCache() {
-        // mock 数据(缓存)
+    public void testValidateRoleDuplicate_success() {
+        // 调用,不会抛异常
+        roleService.validateRoleDuplicate(randomString(), randomString(), null);
+    }
+
+    @Test
+    public void testValidateRoleDuplicate_nameDuplicate() {
+        // mock 数据
+        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setName("role_name"));
+        roleMapper.insert(roleDO);
+        // 准备参数
+        String name = "role_name";
+
+        // 调用,并断言异常
+        assertServiceException(() -> roleService.validateRoleDuplicate(name, randomString(), null),
+                ROLE_NAME_DUPLICATE, name);
+    }
+
+    @Test
+    public void testValidateRoleDuplicate_codeDuplicate() {
+        // mock 数据
+        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setCode("code"));
+        roleMapper.insert(roleDO);
+        // 准备参数
+        String code = "code";
+
+        // 调用,并断言异常
+        assertServiceException(() -> roleService.validateRoleDuplicate(randomString(), code, null),
+                ROLE_CODE_DUPLICATE, code);
+    }
+
+    @Test
+    public void testValidateUpdateRole_success() {
         RoleDO roleDO = randomPojo(RoleDO.class);
         roleMapper.insert(roleDO);
-        roleService.initLocalCache();
-        // 参数准备
+        // 准备参数
         Long id = roleDO.getId();
 
-        // 调用
-        RoleDO dbRoleDO = roleService.getRoleFromCache(id);
-        // 断言
-        assertPojoEquals(roleDO, dbRoleDO);
+        // 调用,无异常
+        roleService.validateRoleForUpdate(id);
+    }
+
+    @Test
+    public void testValidateUpdateRole_roleIdNotExist() {
+        assertServiceException(() -> roleService.validateRoleForUpdate(randomLongId()), ROLE_NOT_EXISTS);
+    }
+
+    @Test
+    public void testValidateUpdateRole_systemRoleCanNotBeUpdate() {
+        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.SYSTEM.getType()));
+        roleMapper.insert(roleDO);
+        // 准备参数
+        Long id = roleDO.getId();
+
+        assertServiceException(() -> roleService.validateRoleForUpdate(id),
+                ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE);
     }
 
     @Test
@@ -186,22 +205,21 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
     }
 
     @Test
-    public void testGetRoleListByStatus_statusNotEmpty() {
-        // mock 数据
-        RoleDO dbRole = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        roleMapper.insert(dbRole);
-        // 测试 status 不匹配
-        roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+    public void testGetRoleFromCache() {
+        // mock 数据(缓存)
+        RoleDO roleDO = randomPojo(RoleDO.class);
+        roleMapper.insert(roleDO);
+        // 参数准备
+        Long id = roleDO.getId();
 
         // 调用
-        List<RoleDO> list = roleService.getRoleListByStatus(singleton(CommonStatusEnum.ENABLE.getStatus()));
+        RoleDO dbRoleDO = roleService.getRoleFromCache(id);
         // 断言
-        assertEquals(1, list.size());
-        assertPojoEquals(dbRole, list.get(0));
+        assertPojoEquals(roleDO, dbRoleDO);
     }
 
     @Test
-    public void testGetRoleListByStatus_statusEmpty() {
+    public void testGetRoleListByStatus() {
         // mock 数据
         RoleDO dbRole01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
         roleMapper.insert(dbRole01);
@@ -209,29 +227,33 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
         roleMapper.insert(dbRole02);
 
         // 调用
-        List<RoleDO> list = roleService.getRoleListByStatus(null);
+        List<RoleDO> list = roleService.getRoleListByStatus(
+                singleton(CommonStatusEnum.ENABLE.getStatus()));
         // 断言
-        assertEquals(2, list.size());
+        assertEquals(1, list.size());
         assertPojoEquals(dbRole01, list.get(0));
-        assertPojoEquals(dbRole02, list.get(1));
     }
 
     @Test
     public void testGetRoleListFromCache() {
-        // mock 数据
-        RoleDO dbRole = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        roleMapper.insert(dbRole);
-        // 测试 id 不匹配
-        roleMapper.insert(cloneIgnoreId(dbRole, o -> {}));
-        roleService.initLocalCache();
-        // 准备参数
-        Collection<Long> ids = singleton(dbRole.getId());
-
-        // 调用
-        List<RoleDO> list = roleService.getRoleListFromCache(ids);
-        // 断言
-        assertEquals(1, list.size());
-        assertPojoEquals(dbRole, list.get(0));
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
+                    .thenReturn(roleService);
+
+            // mock 数据
+            RoleDO dbRole = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
+            roleMapper.insert(dbRole);
+            // 测试 id 不匹配
+            roleMapper.insert(cloneIgnoreId(dbRole, o -> {}));
+            // 准备参数
+            Collection<Long> ids = singleton(dbRole.getId());
+
+            // 调用
+            List<RoleDO> list = roleService.getRoleListFromCache(ids);
+            // 断言
+            assertEquals(1, list.size());
+            assertPojoEquals(dbRole, list.get(0));
+        }
     }
 
     @Test
@@ -296,72 +318,37 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
     }
 
     @Test
-    public void testHasAnySuperAdmin() {
-        // 是超级
-        assertTrue(roleService.hasAnySuperAdmin(singletonList(randomPojo(RoleDO.class,
-                o -> o.setCode("super_admin")))));
-        // 非超级
-        assertFalse(roleService.hasAnySuperAdmin(singletonList(randomPojo(RoleDO.class,
-                o -> o.setCode("tenant_admin")))));
-    }
-
-    @Test
-    public void testValidateRoleDuplicate_success() {
-        // 调用,不会抛异常
-        roleService.validateRoleDuplicate(randomString(), randomString(), null);
-    }
-
-    @Test
-    public void testValidateRoleDuplicate_nameDuplicate() {
-        // mock 数据
-        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setName("role_name"));
-        roleMapper.insert(roleDO);
-        // 准备参数
-        String name = "role_name";
-
-        // 调用,并断言异常
-        assertServiceException(() -> roleService.validateRoleDuplicate(name, randomString(), null),
-                ROLE_NAME_DUPLICATE, name);
+    public void testHasAnySuperAdmin_true() {
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
+                    .thenReturn(roleService);
+
+            // mock 数据
+            RoleDO dbRole = randomPojo(RoleDO.class).setCode("super_admin");
+            roleMapper.insert(dbRole);
+            // 准备参数
+            Long id = dbRole.getId();
+
+            // 调用,并调用
+            assertTrue(roleService.hasAnySuperAdmin(singletonList(id)));
+        }
     }
 
     @Test
-    public void testValidateRoleDuplicate_codeDuplicate() {
-        // mock 数据
-        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setCode("code"));
-        roleMapper.insert(roleDO);
-        // 准备参数
-        String code = "code";
-
-        // 调用,并断言异常
-        assertServiceException(() -> roleService.validateRoleDuplicate(randomString(), code, null),
-                ROLE_CODE_DUPLICATE, code);
-    }
-
-    @Test
-    public void testValidateUpdateRole_success() {
-        RoleDO roleDO = randomPojo(RoleDO.class);
-        roleMapper.insert(roleDO);
-        // 准备参数
-        Long id = roleDO.getId();
-
-        // 调用,无异常
-        roleService.validateRoleForUpdate(id);
-    }
-
-    @Test
-    public void testValidateUpdateRole_roleIdNotExist() {
-        assertServiceException(() -> roleService.validateRoleForUpdate(randomLongId()), ROLE_NOT_EXISTS);
-    }
-
-    @Test
-    public void testValidateUpdateRole_systemRoleCanNotBeUpdate() {
-        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.SYSTEM.getType()));
-        roleMapper.insert(roleDO);
-        // 准备参数
-        Long id = roleDO.getId();
-
-        assertServiceException(() -> roleService.validateRoleForUpdate(id),
-                ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE);
+    public void testHasAnySuperAdmin_false() {
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
+                    .thenReturn(roleService);
+
+            // mock 数据
+            RoleDO dbRole = randomPojo(RoleDO.class).setCode("tenant_admin");
+            roleMapper.insert(dbRole);
+            // 准备参数
+            Long id = dbRole.getId();
+
+            // 调用,并调用
+            assertFalse(roleService.hasAnySuperAdmin(singletonList(id)));
+        }
     }
 
     @Test

+ 48 - 75
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImplTest.java

@@ -18,7 +18,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
 import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsTemplateMapper;
 import cn.iocoder.yudao.module.system.enums.sms.SmsTemplateTypeEnum;
-import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
 import com.google.common.collect.Lists;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
@@ -26,7 +25,6 @@ import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
 import java.util.List;
-import java.util.Map;
 import java.util.function.Consumer;
 
 import static cn.hutool.core.util.RandomUtil.randomEle;
@@ -55,25 +53,6 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
     private SmsClientFactory smsClientFactory;
     @MockBean
     private SmsClient smsClient;
-    @MockBean
-    private SmsProducer smsProducer;
-
-    @Test
-    void testInitLocalCache() {
-        // mock 数据
-        SmsTemplateDO smsTemplate01 = randomSmsTemplateDO();
-        smsTemplateMapper.insert(smsTemplate01);
-        SmsTemplateDO smsTemplate02 = randomSmsTemplateDO();
-        smsTemplateMapper.insert(smsTemplate02);
-
-        // 调用
-        smsTemplateService.initLocalCache();
-        // 断言 deptCache 缓存
-        Map<String, SmsTemplateDO> smsTemplateCache = smsTemplateService.getSmsTemplateCache();
-        assertEquals(2, smsTemplateCache.size());
-        assertPojoEquals(smsTemplate01, smsTemplateCache.get(smsTemplate01.getCode()));
-        assertPojoEquals(smsTemplate02, smsTemplateCache.get(smsTemplate02.getCode()));
-    }
 
     @Test
     public void testParseTemplateContentParams() {
@@ -116,8 +95,6 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
         assertPojoEquals(reqVO, smsTemplate);
         assertEquals(Lists.newArrayList("operation", "code"), smsTemplate.getParams());
         assertEquals(channelDO.getCode(), smsTemplate.getChannelCode());
-        // 校验调用
-        verify(smsProducer, times(1)).sendSmsTemplateRefreshMessage();
     }
 
     @Test
@@ -151,8 +128,6 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
         assertPojoEquals(reqVO, smsTemplate);
         assertEquals(Lists.newArrayList("operation", "code"), smsTemplate.getParams());
         assertEquals(channelDO.getCode(), smsTemplate.getChannelCode());
-        // 校验调用
-        verify(smsProducer, times(1)).sendSmsTemplateRefreshMessage();
     }
 
     @Test
@@ -174,10 +149,8 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
 
         // 调用
         smsTemplateService.deleteSmsTemplate(id);
-       // 校验数据不存在了
-       assertNull(smsTemplateMapper.selectById(id));
-        // 校验调用
-        verify(smsProducer, times(1)).sendSmsTemplateRefreshMessage();
+        // 校验数据不存在了
+        assertNull(smsTemplateMapper.selectById(id));
     }
 
     @Test
@@ -191,47 +164,47 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
 
     @Test
     public void testGetSmsTemplatePage() {
-       // mock 数据
-       SmsTemplateDO dbSmsTemplate = randomPojo(SmsTemplateDO.class, o -> { // 等会查询到
-           o.setType(SmsTemplateTypeEnum.PROMOTION.getType());
-           o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-           o.setCode("tudou");
-           o.setContent("芋道源码");
-           o.setApiTemplateId("yunai");
-           o.setChannelId(1L);
-           o.setCreateTime(buildTime(2021, 11, 11));
-       });
-       smsTemplateMapper.insert(dbSmsTemplate);
-       // 测试 type 不匹配
-       smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setType(SmsTemplateTypeEnum.VERIFICATION_CODE.getType())));
-       // 测试 status 不匹配
-       smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
-       // 测试 code 不匹配
-       smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCode("yuanma")));
-       // 测试 content 不匹配
-       smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setContent("源码")));
-       // 测试 apiTemplateId 不匹配
-       smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setApiTemplateId("nai")));
-       // 测试 channelId 不匹配
-       smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setChannelId(2L)));
-       // 测试 createTime 不匹配
-       smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCreateTime(buildTime(2021, 12, 12))));
-       // 准备参数
-       SmsTemplatePageReqVO reqVO = new SmsTemplatePageReqVO();
-       reqVO.setType(SmsTemplateTypeEnum.PROMOTION.getType());
-       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
-       reqVO.setCode("tu");
-       reqVO.setContent("芋道");
-       reqVO.setApiTemplateId("yu");
-       reqVO.setChannelId(1L);
-       reqVO.setCreateTime(buildBetweenTime(2021, 11, 1, 2021, 12, 1));
-
-       // 调用
-       PageResult<SmsTemplateDO> pageResult = smsTemplateService.getSmsTemplatePage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbSmsTemplate, pageResult.getList().get(0));
+        // mock 数据
+        SmsTemplateDO dbSmsTemplate = randomPojo(SmsTemplateDO.class, o -> { // 等会查询到
+            o.setType(SmsTemplateTypeEnum.PROMOTION.getType());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setCode("tudou");
+            o.setContent("芋道源码");
+            o.setApiTemplateId("yunai");
+            o.setChannelId(1L);
+            o.setCreateTime(buildTime(2021, 11, 11));
+        });
+        smsTemplateMapper.insert(dbSmsTemplate);
+        // 测试 type 不匹配
+        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setType(SmsTemplateTypeEnum.VERIFICATION_CODE.getType())));
+        // 测试 status 不匹配
+        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 测试 code 不匹配
+        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCode("yuanma")));
+        // 测试 content 不匹配
+        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setContent("源码")));
+        // 测试 apiTemplateId 不匹配
+        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setApiTemplateId("nai")));
+        // 测试 channelId 不匹配
+        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setChannelId(2L)));
+        // 测试 createTime 不匹配
+        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCreateTime(buildTime(2021, 12, 12))));
+        // 准备参数
+        SmsTemplatePageReqVO reqVO = new SmsTemplatePageReqVO();
+        reqVO.setType(SmsTemplateTypeEnum.PROMOTION.getType());
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setCode("tu");
+        reqVO.setContent("芋道");
+        reqVO.setApiTemplateId("yu");
+        reqVO.setChannelId(1L);
+        reqVO.setCreateTime(buildBetweenTime(2021, 11, 1, 2021, 12, 1));
+
+        // 调用
+        PageResult<SmsTemplateDO> pageResult = smsTemplateService.getSmsTemplatePage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbSmsTemplate, pageResult.getList().get(0));
     }
 
     @Test
@@ -271,11 +244,11 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
         reqVO.setChannelId(1L);
         reqVO.setCreateTime(buildBetweenTime(2021, 11, 1, 2021, 12, 1));
 
-       // 调用
-       List<SmsTemplateDO> list = smsTemplateService.getSmsTemplateList(reqVO);
-       // 断言
-       assertEquals(1, list.size());
-       assertPojoEquals(dbSmsTemplate, list.get(0));
+        // 调用
+        List<SmsTemplateDO> list = smsTemplateService.getSmsTemplateList(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbSmsTemplate, list.get(0));
     }
 
     @Test

+ 2 - 2
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java

@@ -199,7 +199,7 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
         role101.setTenantId(dbTenant.getId());
         when(roleService.getRoleListByStatus(isNull())).thenReturn(asList(role100, role101));
         // mock 每个角色的权限
-        when(permissionService.getRoleMenuIds(eq(101L))).thenReturn(asSet(201L, 202L));
+        when(permissionService.getRoleMenuListByRoleId(eq(101L))).thenReturn(asSet(201L, 202L));
 
         // 调用
         tenantService.updateTenant(reqVO);
@@ -454,7 +454,7 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
         TenantContextHolder.setTenantId(dbTenant.getId());
         // mock 菜单
         when(menuService.getMenuList()).thenReturn(Arrays.asList(randomPojo(MenuDO.class, o -> o.setId(100L)),
-                        randomPojo(MenuDO.class, o -> o.setId(101L))));
+                randomPojo(MenuDO.class, o -> o.setId(101L))));
 
         // 调用
         tenantService.handleTenantMenu(handler);

+ 2 - 2
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java

@@ -345,7 +345,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
         reqVO.setDeptId(1L); // 其中,1L 是 2L 的父部门
         // mock 方法
         List<DeptDO> deptList = newArrayList(randomPojo(DeptDO.class, o -> o.setId(2L)));
-        when(deptService.getDeptListByParentIdFromCache(eq(reqVO.getDeptId()), eq(true))).thenReturn(deptList);
+        when(deptService.getChildDeptList(eq(reqVO.getDeptId()))).thenReturn(deptList);
 
         // 调用
         PageResult<AdminUserDO> pageResult = userService.getUserPage(reqVO);
@@ -368,7 +368,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
         reqVO.setDeptId(1L); // 其中,1L 是 2L 的父部门
         // mock 方法
         List<DeptDO> deptList = newArrayList(randomPojo(DeptDO.class, o -> o.setId(2L)));
-        when(deptService.getDeptListByParentIdFromCache(eq(reqVO.getDeptId()), eq(true))).thenReturn(deptList);
+        when(deptService.getChildDeptList(eq(reqVO.getDeptId()))).thenReturn(deptList);
 
         // 调用
         List<AdminUserDO> list = userService.getUserList(reqVO);

+ 0 - 9
yudao-ui-admin/src/api/menu.js

@@ -1,9 +0,0 @@
-import request from '@/utils/request'
-
-// 获取路由
-export const getRouters = () => {
-  return request({
-    url: '/system/auth/list-menus',
-    method: 'get'
-  })
-}

+ 1 - 1
yudao-ui-admin/src/api/system/permission.js

@@ -3,7 +3,7 @@ import request from '@/utils/request'
 // 查询角色拥有的菜单数组
 export function listRoleMenus(roleId) {
   return request({
-    url: '/system/permission/list-role-resources?roleId=' + roleId,
+    url: '/system/permission/list-role-menus?roleId=' + roleId,
     method: 'get'
   })
 }

+ 3 - 2
yudao-ui-admin/src/permission.js

@@ -25,9 +25,10 @@ router.beforeEach((to, from, next) => {
         // 获取字典数据 add by 芋艿
         store.dispatch('dict/loadDictDatas')
         // 判断当前用户是否已拉取完 user_info 信息
-        store.dispatch('GetInfo').then(() => {
+        store.dispatch('GetInfo').then(userInfo => {
           isRelogin.show = false
-          store.dispatch('GenerateRoutes').then(accessRoutes => {
+          // 触发 GenerateRoutes 事件时,将 menus 菜单树传递进去
+          store.dispatch('GenerateRoutes', userInfo.menus).then(accessRoutes => {
             // 根据 roles 权限生成可访问的路由表
             router.addRoutes(accessRoutes) // 动态添加可访问路由表
             next({ ...to, replace: true }) // hack方法 确保addRoutes已完成

+ 18 - 16
yudao-ui-admin/src/store/modules/permission.js

@@ -1,5 +1,4 @@
 import {constantRoutes} from '@/router'
-import {getRouters} from '@/api/menu'
 import Layout from '@/layout/index'
 import ParentView from '@/components/ParentView';
 import {toCamelCase} from "@/utils";
@@ -27,22 +26,25 @@ const permission = {
     },
   },
   actions: {
-    // 生成路由
-    GenerateRoutes({commit}) {
+    /**
+     * 生成路由
+     *
+     * @param commit commit 函数
+     * @param menus  路由参数
+     */
+    GenerateRoutes({commit}, menus) {
       return new Promise(resolve => {
-        // 向后端请求路由数据(菜单)
-        getRouters().then(res => {
-          const sdata = JSON.parse(JSON.stringify(res.data)) // 【重要】用于菜单中的数据
-          const rdata = JSON.parse(JSON.stringify(res.data)) // 用于最后添加到 Router 中的数据
-          const sidebarRoutes = filterAsyncRouter(sdata)
-          const rewriteRoutes = filterAsyncRouter(rdata, false, true)
-          rewriteRoutes.push({path: '*', redirect: '/404', hidden: true})
-          commit('SET_ROUTES', rewriteRoutes)
-          commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
-          commit('SET_DEFAULT_ROUTES', sidebarRoutes)
-          commit('SET_TOPBAR_ROUTES', sidebarRoutes)
-          resolve(rewriteRoutes)
-        })
+        // 将 menus 菜单,转换为 route 路由数组
+        const sdata = JSON.parse(JSON.stringify(menus)) // 【重要】用于菜单中的数据
+        const rdata = JSON.parse(JSON.stringify(menus)) // 用于最后添加到 Router 中的数据
+        const sidebarRoutes = filterAsyncRouter(sdata)
+        const rewriteRoutes = filterAsyncRouter(rdata, false, true)
+        rewriteRoutes.push({path: '*', redirect: '/404', hidden: true})
+        commit('SET_ROUTES', rewriteRoutes)
+        commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
+        commit('SET_DEFAULT_ROUTES', sidebarRoutes)
+        commit('SET_TOPBAR_ROUTES', sidebarRoutes)
+        resolve(rewriteRoutes)
       })
     }
   }