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

This commit is contained in:
YunaiV 2023-02-25 11:50:05 +08:00
parent 45a5b7d1d4
commit 2976675331
21 changed files with 178 additions and 244 deletions

View File

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

View File

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

View File

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

View File

@ -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();
}
// 继续基于父方法

View File

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

View File

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

View File

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

View File

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

View File

@ -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()),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,12 +92,19 @@ 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);
/**
* 获得所有角色列表
*
* @return 角色列表
*/
List<RoleDO> getRoleList();
/**
* 判断角色编号数组中是否有管理员
@ -103,17 +112,7 @@ public interface RoleService {
* @param ids 角色编号数组
* @return 是否有管理员
*/
default boolean hasAnySuperAdmin(Set<Long> ids) {
return hasAnySuperAdmin(getRoleListFromCache(ids));
}
/**
* 获得角色
*
* @param id 角色编号
* @return 角色
*/
RoleDO getRole(Long id);
boolean hasAnySuperAdmin(Collection<Long> ids);
/**
* 获得角色分页

View File

@ -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,50 +112,54 @@ 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> 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 roleMapper.selectById(id);
}
@Override
public List<RoleDO> getRoleList(Collection<Long> ids) {
return roleMapper.selectBatchIds(ids);
}
@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());
// 这里采用 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());
}
}

View File

@ -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)); // 兜底校验
// 重新分配每个角色的权限

View File

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

View File

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