Pārlūkot izejas kodu

缓存改造:Role 使用 Redis 作为缓存

YunaiV 2 gadi atpakaļ
vecāks
revīzija
2976675331
21 mainītis faili ar 173 papildinājumiem un 239 dzēšanām
  1. 0 46
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringAopUtils.java
  2. 7 0
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/TenantProperties.java
  3. 3 4
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java
  4. 11 5
      yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisCacheManager.java
  5. 5 0
      yudao-framework/yudao-spring-boot-starter-redis/pom.xml
  6. 4 1
      yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java
  7. 13 2
      yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoRedisAutoConfiguration.java
  8. 31 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/tenant/dto/TenantDataSourceConfigRespDTO.java
  9. 3 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
  10. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserProfileController.java
  11. 5 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java
  12. 8 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java
  13. 0 29
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/permission/RoleRefreshConsumer.java
  14. 0 21
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/permission/RoleRefreshMessage.java
  15. 0 28
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/permission/RoleProducer.java
  16. 1 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java
  17. 21 22
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java
  18. 46 72
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java
  19. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java
  20. 11 1
      yudao-server/src/main/resources/application-local.yaml
  21. 2 0
      yudao-server/src/main/resources/application.yaml

+ 0 - 46
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringAopUtils.java

@@ -1,46 +0,0 @@
-package cn.iocoder.yudao.framework.common.util.spring;
-
-import cn.hutool.core.bean.BeanUtil;
-import org.springframework.aop.framework.AdvisedSupport;
-import org.springframework.aop.framework.AopProxy;
-import org.springframework.aop.support.AopUtils;
-
-/**
- * Spring AOP 工具类
- *
- * 参考波克尔 http://www.bubuko.com/infodetail-3471885.html 实现
- */
-public class SpringAopUtils {
-
-    /**
-     * 获取代理的目标对象
-     *
-     * @param proxy 代理对象
-     * @return 目标对象
-     */
-    public static Object getTarget(Object proxy) throws Exception {
-        // 不是代理对象
-        if (!AopUtils.isAopProxy(proxy)) {
-            return proxy;
-        }
-        // Jdk 代理
-        if (AopUtils.isJdkDynamicProxy(proxy)) {
-            return getJdkDynamicProxyTargetObject(proxy);
-        }
-        // Cglib 代理
-        return getCglibProxyTargetObject(proxy);
-    }
-
-    private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
-        Object dynamicAdvisedInterceptor = BeanUtil.getFieldValue(proxy, "CGLIB$CALLBACK_0");
-        AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(dynamicAdvisedInterceptor, "advised");
-        return advisedSupport.getTargetSource().getTarget();
-    }
-
-    private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
-        AopProxy aopProxy = (AopProxy) BeanUtil.getFieldValue(proxy, "h");
-        AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(aopProxy, "advised");
-        return advisedSupport.getTargetSource().getTarget();
-    }
-
-}

+ 7 - 0
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/TenantProperties.java

@@ -39,4 +39,11 @@ public class TenantProperties {
      */
     private Set<String> ignoreTables = Collections.emptySet();
 
+    /**
+     * 需要忽略多租户的 {@link org.springframework.cache.Cache}
+     *
+     * 即默认所有 Cache 都开启多租户的功能
+     */
+    private Set<String> ignoreCaches = Collections.emptySet();
+
 }

+ 3 - 4
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java

@@ -18,9 +18,7 @@ import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
 import cn.iocoder.yudao.framework.web.config.WebProperties;
 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
 import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
-import com.baomidou.dynamic.datasource.processor.DsHeaderProcessor;
 import com.baomidou.dynamic.datasource.processor.DsProcessor;
-import com.baomidou.dynamic.datasource.processor.DsSessionProcessor;
 import com.baomidou.dynamic.datasource.processor.DsSpelExpressionProcessor;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
@@ -136,12 +134,13 @@ public class YudaoTenantAutoConfiguration {
     @Bean
     @Primary // 引入租户时,tenantRedisCacheManager 为主 Bean
     public RedisCacheManager tenantRedisCacheManager(RedisTemplate<String, Object> redisTemplate,
-                                                     RedisCacheConfiguration redisCacheConfiguration) {
+                                                     RedisCacheConfiguration redisCacheConfiguration,
+                                                     TenantProperties tenantProperties) {
         // 创建 RedisCacheWriter 对象
         RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
         RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
         // 创建 TenantRedisCacheManager 对象
-        return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration);
+        return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration, tenantProperties);
     }
 
 }

+ 11 - 5
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/redis/TenantRedisCacheManager.java

@@ -1,6 +1,9 @@
 package cn.iocoder.yudao.framework.tenant.core.redis;
 
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import jodd.io.StreamUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.cache.Cache;
 import org.springframework.data.redis.cache.RedisCacheConfiguration;
@@ -17,17 +20,20 @@ import org.springframework.data.redis.cache.RedisCacheWriter;
 @Slf4j
 public class TenantRedisCacheManager extends RedisCacheManager {
 
+    private final TenantProperties tenantProperties;
+
     public TenantRedisCacheManager(RedisCacheWriter cacheWriter,
-                                   RedisCacheConfiguration defaultCacheConfiguration) {
+                                   RedisCacheConfiguration defaultCacheConfiguration,
+                                   TenantProperties tenantProperties) {
         super(cacheWriter, defaultCacheConfiguration);
+        this.tenantProperties = tenantProperties;
     }
 
     @Override
     public Cache getCache(String name) {
-        // 如果开启多租户,则 name 拼接租户后缀
-        if (!TenantContextHolder.isIgnore()
-            && TenantContextHolder.getTenantId() != null) {
-            name = name + ":" + TenantContextHolder.getTenantId();
+        // 如果不忽略多租户的 Cache,则自动拼接租户后缀
+        if (!tenantProperties.getIgnoreCaches().contains(name)) {
+            name = name + StrUtil.COLON + TenantContextHolder.getRequiredTenantId();
         }
 
         // 继续基于父方法

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

@@ -32,11 +32,16 @@
             <artifactId>spring-boot-starter-cache</artifactId> <!-- 实现对 Caches 的自动化配置 -->
         </dependency>
 
+        <!-- 工具相关 -->
         <dependency>
             <groupId>io.netty</groupId>
             <artifactId>netty-all</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

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

@@ -10,6 +10,8 @@ import org.springframework.data.redis.cache.RedisCacheConfiguration;
 import org.springframework.data.redis.serializer.RedisSerializationContext;
 import org.springframework.data.redis.serializer.RedisSerializer;
 
+import static cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration.buildRedisSerializer;
+
 /**
  * Cache 配置类,基于 Redis 实现
  */
@@ -28,7 +30,8 @@ public class YudaoCacheAutoConfiguration {
     public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
         // 设置使用 JSON 序列化方式
         RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
-        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
+        config = config.serializeValuesWith(
+                RedisSerializationContext.SerializationPair.fromSerializer(buildRedisSerializer()));
 
         // 设置 CacheProperties.Redis 的属性
         CacheProperties.Redis redisProperties = cacheProperties.getRedis();

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

+ 31 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/tenant/dto/TenantDataSourceConfigRespDTO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.system.api.tenant.dto;
+
+import lombok.Data;
+
+/**
+ * 多租户的数据源配置 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class TenantDataSourceConfigRespDTO {
+
+    /**
+     * 连接名
+     */
+    private String name;
+
+    /**
+     * 数据源连接
+     */
+    private String url;
+    /**
+     * 用户名
+     */
+    private String username;
+    /**
+     * 密码
+     */
+    private String password;
+
+}

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

@@ -96,8 +96,9 @@ public class AuthController {
             return null;
         }
         // 获得角色列表
-        Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
-        List<RoleDO> roleList = roleService.getRoleListFromCache(roleIds);
+        Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(),
+                singleton(CommonStatusEnum.ENABLE.getStatus()));
+        List<RoleDO> roleList = roleService.getRoleList(roleIds);
         // 获得菜单列表
         List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
                 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()),

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserProfileController.java

@@ -63,7 +63,7 @@ public class UserProfileController {
         AdminUserDO user = userService.getUser(getLoginUserId());
         UserProfileRespVO resp = UserConvert.INSTANCE.convert03(user);
         // 获得用户角色
-        List<RoleDO> userRoles = roleService.getRoleListFromCache(permissionService.getUserRoleIdListByUserId(user.getId()));
+        List<RoleDO> userRoles = roleService.getRoleList(permissionService.getUserRoleIdListByUserId(user.getId()));
         resp.setRoles(UserConvert.INSTANCE.convertList(userRoles));
         // 获得部门信息
         if (user.getDeptId() != null) {

+ 5 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java

@@ -4,17 +4,20 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
+import com.baomidou.dynamic.datasource.annotation.Master;
 import org.apache.ibatis.annotations.Mapper;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.lang.Nullable;
 
 import java.util.Collection;
 import java.util.List;
 
 @Mapper
-// TODO 芋艿:@TenantDS
+@TenantDS
 public interface RoleMapper extends BaseMapperX<RoleDO> {
 
     default PageResult<RoleDO> selectPage(RolePageReqVO reqVO) {
@@ -42,7 +45,7 @@ public interface RoleMapper extends BaseMapperX<RoleDO> {
         return selectOne(RoleDO::getCode, code);
     }
 
-    default List<RoleDO> selectListByStatus(@Nullable Collection<Integer> statuses) {
+    default List<RoleDO> selectListByStatus(Collection<Integer> statuses) {
         return selectList(RoleDO::getStatus, statuses);
     }
 

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

@@ -26,4 +26,12 @@ public interface RedisKeyConstants {
             "social_auth_state:%s", // 参数为 state
             STRING, String.class, Duration.ofHours(24)); // 值为 state
 
+    /**
+     * 角色的缓存
+     *
+     * KEY 格式:role::{id}
+     * 数据格式:String
+     */
+    String ROLE = "role";
+
 }

+ 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 - 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 - 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);
-    }
-
-}

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

@@ -158,8 +158,7 @@ public class PermissionServiceImpl implements PermissionService {
         }
 
         // 判断角色是否包含超级管理员。如果是超级管理员,获取到全部
-        List<RoleDO> roleList = roleService.getRoleListFromCache(roleIds);
-        if (roleService.hasAnySuperAdmin(roleList)) {
+        if (roleService.hasAnySuperAdmin(roleIds)) {
             return menuService.getMenuListFromCache(menuTypes, menusStatuses);
         }
 

+ 21 - 22
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,27 @@ public interface RoleService {
     List<RoleDO> getRoleListFromCache(Collection<Long> ids);
 
     /**
-     * 判断角色数组中,是否有超级管理员
+     * 获得角色列表
      *
-     * @param roleList 角色数组
-     * @return 是否有管理员
+     * @param statuses 筛选的状态
+     * @return 角色列表
      */
-    boolean hasAnySuperAdmin(Collection<RoleDO> roleList);
+    List<RoleDO> getRoleListByStatus(Collection<Integer> statuses);
 
     /**
-     * 判断角色编号数组中,是否有管理员
+     * 获得所有角色列表
      *
-     * @param ids 角色编号数组
-     * @return 是否有管理员
+     * @return 角色列表
      */
-    default boolean hasAnySuperAdmin(Set<Long> ids) {
-        return hasAnySuperAdmin(getRoleListFromCache(ids));
-    }
+    List<RoleDO> getRoleList();
 
     /**
-     * 获得角色
+     * 判断角色编号数组中,是否有管理员
      *
-     * @param id 角色编号
-     * @return 角色
+     * @param ids 角色编号数组
+     * @return 是否有管理员
      */
-    RoleDO getRole(Long id);
+    boolean hasAnySuperAdmin(Collection<Long> ids);
 
     /**
      * 获得角色分页

+ 46 - 72
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,24 @@ 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.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.lang.Nullable;
 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,41 +43,12 @@ 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
     public Long createRole(RoleCreateReqVO reqVO, Integer type) {
@@ -91,18 +60,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 +75,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 +86,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 +100,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,28 +112,32 @@ public class RoleServiceImpl implements RoleService {
         roleMapper.deleteById(id);
         // 删除相关数据
         permissionService.processRoleDeleted(id);
-        // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
-        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+    }
 
-            @Override
-            public void afterCommit() {
-                roleProducer.sendRoleRefreshMessage();
-            }
+    @Override
+    public List<RoleDO> getRoleListByStatus(@Nullable Collection<Integer> statuses) {
+        return roleMapper.selectListByStatus(statuses);
+    }
 
-        });
+    @Override
+    public List<RoleDO> getRoleList() {
+        return roleMapper.selectList();
     }
 
     @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 roleCache.get(id);
+        return roleMapper.selectById(id);
     }
 
     @Override
-    public List<RoleDO> getRoleListByStatus(@Nullable Collection<Integer> statuses) {
-        if (CollUtil.isEmpty(statuses)) {
-    		return roleMapper.selectList();
-		}
-        return roleMapper.selectListByStatus(statuses);
+    public List<RoleDO> getRoleList(Collection<Long> ids) {
+        return roleMapper.selectBatchIds(ids);
     }
 
     @Override
@@ -181,21 +145,21 @@ public class RoleServiceImpl implements RoleService {
         if (CollectionUtil.isEmpty(ids)) {
             return Collections.emptyList();
         }
-        return roleCache.values().stream().filter(roleDO -> ids.contains(roleDO.getId()))
-                .collect(Collectors.toList());
+        // 这里采用 for 循环从缓存中获取,主要考虑 Spring CacheManager 无法批量操作的问题
+        RoleServiceImpl self = getSelf();
+        return convertList(ids, self::getRoleFromCache);
     }
 
+
     @Override
-    public boolean hasAnySuperAdmin(Collection<RoleDO> roleList) {
-        if (CollectionUtil.isEmpty(roleList)) {
+    public boolean hasAnySuperAdmin(Collection<Long> ids) {
+        if (CollectionUtil.isEmpty(ids)) {
             return false;
         }
-        return roleList.stream().anyMatch(role -> RoleCodeEnum.isSuperAdmin(role.getCode()));
-    }
-
-    @Override
-    public RoleDO getRole(Long id) {
-        return roleMapper.selectById(id);
+        return ids.stream().anyMatch(id -> {
+            RoleDO role = getRoleFromCache(id);
+            return role != null && RoleCodeEnum.isSuperAdmin(role.getCode());
+        });
     }
 
     @Override
@@ -276,4 +240,14 @@ public class RoleServiceImpl implements RoleService {
             }
         });
     }
+
+    /**
+     * 获得自身的代理对象,解决 AOP 生效问题
+     *
+     * @return 自己
+     */
+    private RoleServiceImpl getSelf() {
+        return SpringUtil.getBean(getClass());
+    }
+
 }

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

@@ -156,7 +156,7 @@ public class TenantServiceImpl implements TenantService {
     public void updateTenantRoleMenu(Long tenantId, Set<Long> menuIds) {
         TenantUtils.execute(tenantId, () -> {
             // 获得所有角色
-            List<RoleDO> roles = roleService.getRoleListByStatus(null);
+            List<RoleDO> roles = roleService.getRoleList();
             roles.forEach(role -> Assert.isTrue(tenantId.equals(role.getTenantId()), "角色({}/{}) 租户不匹配",
                     role.getId(), role.getTenantId(), tenantId)); // 兜底校验
             // 重新分配每个角色的权限

+ 11 - 1
yudao-server/src/main/resources/application-local.yaml

@@ -44,7 +44,7 @@ spring:
       primary: master
       datasource:
         master:
-          name: ruoyi-vue-pro
+          name: ruoyi-vue-pro-master
           url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true # MySQL Connector/J 8.X 连接的示例
           #          url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
           #          url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
@@ -65,6 +65,16 @@ spring:
           password: 123456
   #          username: sa
   #          password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
+        tenant_1_ds:
+          name: tenant_1
+          url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro-tenant-a?useSSL=false&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
+          username: root
+          password: 123456
+        tenant_2_ds:
+          name: tenant_2
+          url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro-tenant-b?useSSL=false&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
+          username: root
+          password: 123456
 
   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
   redis:

+ 2 - 0
yudao-server/src/main/resources/application.yaml

@@ -188,6 +188,8 @@ yudao:
       - rep_demo_jianpiao
       - tmp_report_data_1
       - tmp_report_data_income
+    ignore-caches:
+      - demo
   sms-code: # 短信验证码相关的配置项
     expire-times: 10m
     send-frequency: 1m