缓存改造:Role 使用 Redis 作为缓存
This commit is contained in:
parent
45a5b7d1d4
commit
2976675331
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
// 继续基于父方法
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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()),
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
/**
|
||||
* 获得角色分页
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)); // 兜底校验
|
||||
// 重新分配每个角色的权限
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue