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

This commit is contained in:
YunaiV 2023-02-25 18:00:05 +08:00
parent 9a1a80e4b8
commit 56c44acd11
7 changed files with 48 additions and 153 deletions

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.dal.mysql.permission;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.tenant.core.db.dynamic.TenantDS;
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;
@ -9,9 +10,10 @@ import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@Mapper
// TODO 芋艿@TenantDS
@TenantDS
public interface RoleMenuMapper extends BaseMapperX<RoleMenuDO> {
default List<RoleMenuDO> selectListByRoleId(Long roleId) {
@ -22,6 +24,10 @@ public interface RoleMenuMapper extends BaseMapperX<RoleMenuDO> {
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)

View File

@ -42,4 +42,12 @@ public interface RedisKeyConstants {
*/
String USER_ROLE_ID = "user_role_id";
/**
* 拥有指定菜单的角色编号的缓存
*
* KEY 格式user_role_ids::{menuId}
* 数据类型String 角色编号集合
*/
String MENU_ROLE_ID = "menu_role_id";
}

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

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 RoleMenuRefreshMessage extends AbstractChannelMessage {
@Override
public String getChannel() {
return "system.role-menu.refresh";
}
}

View File

@ -1,26 +0,0 @@
package cn.iocoder.yudao.module.system.mq.producer.permission;
import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
import cn.iocoder.yudao.module.system.mq.message.permission.RoleMenuRefreshMessage;
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);
}
}

View File

@ -16,10 +16,7 @@ import static java.util.Collections.singleton;
*/
public interface PermissionService {
/**
* 初始化权限的本地缓存
*/
void initLocalCache();
// ========== 角色-菜单的相关方法 ==========
/**
* 获得角色拥有的菜单编号集合
@ -39,6 +36,14 @@ public interface PermissionService {
*/
Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds);
/**
* 获得拥有指定菜单的角色编号数组从缓存中获取
*
* @param menuId 菜单编号
* @return 角色编号数组
*/
Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId);
/**
* 获得拥有多个角色的用户编号集合
*
@ -134,4 +139,8 @@ public interface PermissionService {
*/
DeptDataPermissionRespDTO getDeptDataPermission(Long userId);
// ========== 用户-角色的相关方法 ==========
// ========== 用户-部门的相关方法 ==========
}

View File

@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
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;
@ -19,26 +18,20 @@ 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.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.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 javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@ -53,17 +46,6 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
@Slf4j
public class PermissionServiceImpl implements PermissionService {
/**
* 菜单编号与角色编号的缓存映射
* key菜单编号
* value角色编号的数组
*
* 这里声明 volatile 修饰的原因是每次刷新时直接修改指向
*/
@Getter
@Setter // 单元测试需要
private volatile Multimap<Long, Long> menuRoleCache;
@Resource
private RoleMenuMapper roleMenuMapper;
@Resource
@ -78,33 +60,6 @@ public class PermissionServiceImpl implements PermissionService {
@Resource
private AdminUserService userService;
@Resource
private PermissionProducer permissionProducer;
@Override
@PostConstruct
public void initLocalCache() {
initLocalCacheForRoleMenu();
}
/**
* 刷新 RoleMenu 本地缓存
*/
@VisibleForTesting
void initLocalCacheForRoleMenu() {
// 注意忽略自动多租户因为要全局初始化缓存
TenantUtils.executeIgnore(() -> {
// 第一步查询数据
List<RoleMenuDO> roleMenus = roleMenuMapper.selectList();
log.info("[initLocalCacheForRoleMenu][缓存角色与菜单,数量为:{}]", roleMenus.size());
// 第二步构建缓存
ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
roleMenus.forEach(roleMenuDO -> menuRoleCacheBuilder.put(roleMenuDO.getMenuId(), roleMenuDO.getRoleId()));
menuRoleCache = menuRoleCacheBuilder.build();
});
}
@Override
public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) {
// 如果是管理员的情况下获取全部菜单编号
@ -115,6 +70,12 @@ public class PermissionServiceImpl implements PermissionService {
return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
}
@Override
@Cacheable(value = RedisKeyConstants.MENU_ROLE_ID, key = "#menuId")
public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) {
return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
@ -135,15 +96,6 @@ public class PermissionServiceImpl implements PermissionService {
if (!CollectionUtil.isEmpty(deleteMenuIds)) {
roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
}
// 发送刷新消息. 注意需要事务提交后在进行发送刷新消息不然 db 还未提交结果缓存先刷新了
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
permissionProducer.sendRoleMenuRefreshMessage();
}
});
}
@Override
@ -215,22 +167,11 @@ public class PermissionServiceImpl implements PermissionService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void processMenuDeleted(Long menuId) {
roleMenuMapper.deleteListByMenuId(menuId);
// 发送刷新消息. 注意需要事务提交后在进行发送刷新消息不然 db 还未提交结果缓存先刷新了
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
permissionProducer.sendRoleMenuRefreshMessage();
}
});
}
@Override
@Transactional(rollbackFor = Exception.class)
public void processUserDeleted(Long userId) {
userRoleMapper.deleteListByUserId(userId);
}
@ -274,10 +215,17 @@ public class PermissionServiceImpl implements PermissionService {
return false;
}
// 获得是否拥有该权限任一一个
// 判断是否有权限
Set<Long> roleIds = convertSet(roles, RoleDO::getId);
return menuList.stream().anyMatch(menu -> CollUtil.containsAny(roleIds,
menuRoleCache.get(menu.getId())));
for (MenuDO menu : menuList) {
// 拥有该角色的菜单编号数组
Set<Long> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menu.getId());
// 如果有交集说明有权限
if (CollUtil.containsAny(menuRoleIds, roleIds)) {
return true;
}
}
return false;
}
@Override