增加 UserRole 的缓存,完善权限相关的单元测试

This commit is contained in:
YunaiV 2022-05-12 22:57:58 +08:00
parent 97db4586a8
commit 49b4eedfc0
8 changed files with 451 additions and 64 deletions

View File

@ -93,7 +93,7 @@ public class AuthController {
return null; return null;
} }
// 获得角色列表 // 获得角色列表
Set<Long> roleIds = permissionService.getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus())); Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
List<RoleDO> roleList = roleService.getRolesFromCache(roleIds); List<RoleDO> roleList = roleService.getRolesFromCache(roleIds);
// 获得菜单列表 // 获得菜单列表
List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds, List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
@ -107,7 +107,7 @@ public class AuthController {
@ApiOperation("获得登录用户的菜单列表") @ApiOperation("获得登录用户的菜单列表")
public CommonResult<List<AuthMenuRespVO>> getMenus() { public CommonResult<List<AuthMenuRespVO>> getMenus() {
// 获得角色列表 // 获得角色列表
Set<Long> roleIds = permissionService.getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus())); Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
// 获得用户拥有的菜单列表 // 获得用户拥有的菜单列表
List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds, List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型

View File

@ -4,8 +4,10 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import java.util.List; import java.util.List;
@Mapper @Mapper
@ -36,4 +38,8 @@ public interface UserRoleMapper extends BaseMapperX<UserRoleDO> {
default List<UserRoleDO> selectListByRoleIds(Collection<Long> roleIds) { default List<UserRoleDO> selectListByRoleIds(Collection<Long> roleIds) {
return selectList(UserRoleDO::getRoleId, roleIds); return selectList(UserRoleDO::getRoleId, roleIds);
} }
@Select("SELECT COUNT(*) FROM system_user_role WHERE update_time > #{maxUpdateTime}")
Long selectCountByUpdateTimeGt(Date maxUpdateTime);
} }

View File

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

View File

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

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.mq.producer.permission;
import cn.iocoder.yudao.module.system.mq.message.permission.RoleMenuRefreshMessage; import cn.iocoder.yudao.module.system.mq.message.permission.RoleMenuRefreshMessage;
import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate; import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
import cn.iocoder.yudao.module.system.mq.message.permission.UserRoleRefreshMessage;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -23,4 +24,12 @@ public class PermissionProducer {
redisMQTemplate.send(message); redisMQTemplate.send(message);
} }
/**
* 发送 {@link UserRoleRefreshMessage} 消息
*/
public void sendUserRoleRefreshMessage() {
UserRoleRefreshMessage message = new UserRoleRefreshMessage();
redisMQTemplate.send(message);
}
} }

View File

@ -37,13 +37,13 @@ public interface PermissionService extends SecurityPermissionFrameworkService, D
Collection<Integer> menusStatuses); Collection<Integer> menusStatuses);
/** /**
* 获得用户拥有的角色编号集合 * 获得用户拥有的角色编号集合从缓存中获取
* *
* @param userId 用户编号 * @param userId 用户编号
* @param roleStatuses 角色状态集合. 允许为空为空时不过滤 * @param roleStatuses 角色状态集合. 允许为空为空时不过滤
* @return 角色编号集合 * @return 角色编号集合
*/ */
Set<Long> getUserRoleIds(Long userId, @Nullable Collection<Integer> roleStatuses); Set<Long> getUserRoleIdsFromCache(Long userId, @Nullable Collection<Integer> roleStatuses);
/** /**
* 获得角色拥有的菜单编号集合 * 获得角色拥有的菜单编号集合
@ -53,6 +53,14 @@ public interface PermissionService extends SecurityPermissionFrameworkService, D
*/ */
Set<Long> getRoleMenuIds(Long roleId); Set<Long> getRoleMenuIds(Long roleId);
/**
* 获得拥有多个角色的用户编号集合
*
* @param roleIds 角色编号集合
* @return 用户编号集合
*/
Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds);
/** /**
* 设置角色菜单 * 设置角色菜单
* *
@ -69,14 +77,6 @@ public interface PermissionService extends SecurityPermissionFrameworkService, D
*/ */
Set<Long> getUserRoleIdListByUserId(Long userId); Set<Long> getUserRoleIdListByUserId(Long userId);
/**
* 获得拥有某个角色的用户编号集合
*
* @param roleId 角色编号
* @return 用户编号集合
*/
Set<Long> getUserRoleIdListByRoleId(Long roleId);
/** /**
* 设置用户角色 * 设置用户角色
* *
@ -115,12 +115,4 @@ public interface PermissionService extends SecurityPermissionFrameworkService, D
*/ */
void processUserDeleted(Long userId); void processUserDeleted(Long userId);
/**
* 获得拥有多个角色的用户编号集合
*
* @param roleIds 角色编号集合
* @return 用户编号集合
*/
Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds);
} }

View File

@ -23,10 +23,13 @@ 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.mq.producer.permission.PermissionProducer;
import cn.iocoder.yudao.module.system.service.dept.DeptService; import cn.iocoder.yudao.module.system.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService; 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.base.Suppliers;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
@ -40,6 +43,8 @@ import javax.annotation.Resource;
import java.util.*; import java.util.*;
import java.util.function.Supplier; import java.util.function.Supplier;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static java.util.Collections.singleton; import static java.util.Collections.singleton;
@ -65,6 +70,8 @@ public class PermissionServiceImpl implements PermissionService {
* *
* 这里声明 volatile 修饰的原因是每次刷新时直接修改指向 * 这里声明 volatile 修饰的原因是每次刷新时直接修改指向
*/ */
@Getter
@Setter // 单元测试
private volatile Multimap<Long, Long> roleMenuCache; private volatile Multimap<Long, Long> roleMenuCache;
/** /**
* 菜单编号与角色编号的缓存映射 * 菜单编号与角色编号的缓存映射
@ -73,11 +80,29 @@ public class PermissionServiceImpl implements PermissionService {
* *
* 这里声明 volatile 修饰的原因是每次刷新时直接修改指向 * 这里声明 volatile 修饰的原因是每次刷新时直接修改指向
*/ */
@Getter
private volatile Multimap<Long, Long> menuRoleCache; private volatile Multimap<Long, Long> menuRoleCache;
/** /**
* 缓存菜单的最大更新时间用于后续的增量轮询判断是否有更新 * 缓存 RoleMenu 的最大更新时间用于后续的增量轮询判断是否有更新
*/ */
private volatile Date maxUpdateTime; @Getter
private volatile Date roleMenuMaxUpdateTime;
/**
* 用户编号与角色编号的缓存映射
* key用户编号
* value角色编号的数组
*
* 这里声明 volatile 修饰的原因是每次刷新时直接修改指向
*/
@Getter
@Setter // 单元测试需要
private volatile Map<Long, Set<Long>> userRoleCache;
/**
* 缓存 UserRole 的最大更新时间用于后续的增量轮询判断是否有更新
*/
@Getter
private volatile Date userRoleMaxUpdateTime;
@Resource @Resource
private RoleMenuMapper roleMenuMapper; private RoleMenuMapper roleMenuMapper;
@ -104,15 +129,21 @@ public class PermissionServiceImpl implements PermissionService {
@Lazy // 注入自己所以延迟加载 @Lazy // 注入自己所以延迟加载
private PermissionService self; private PermissionService self;
/**
* 初始化 {@link #roleMenuCache} {@link #menuRoleCache} 缓存
*/
@Override @Override
@PostConstruct @PostConstruct
@TenantIgnore // 初始化缓存无需租户过滤 @TenantIgnore // 初始化缓存无需租户过滤
public void initLocalCache() { public void initLocalCache() {
initUserRoleLocalCache();
initRoleMenuLocalCache();
}
/**
* 初始化 {@link #roleMenuCache} {@link #menuRoleCache} 缓存
*/
@VisibleForTesting
void initRoleMenuLocalCache() {
// 获取角色与菜单的关联列表如果有更新 // 获取角色与菜单的关联列表如果有更新
List<RoleMenuDO> roleMenuList = loadRoleMenuIfUpdate(maxUpdateTime); List<RoleMenuDO> roleMenuList = loadRoleMenuIfUpdate(roleMenuMaxUpdateTime);
if (CollUtil.isEmpty(roleMenuList)) { if (CollUtil.isEmpty(roleMenuList)) {
return; return;
} }
@ -126,8 +157,27 @@ public class PermissionServiceImpl implements PermissionService {
}); });
roleMenuCache = roleMenuCacheBuilder.build(); roleMenuCache = roleMenuCacheBuilder.build();
menuRoleCache = menuRoleCacheBuilder.build(); menuRoleCache = menuRoleCacheBuilder.build();
maxUpdateTime = CollectionUtils.getMaxValue(roleMenuList, RoleMenuDO::getUpdateTime); roleMenuMaxUpdateTime = getMaxValue(roleMenuList, RoleMenuDO::getUpdateTime);
log.info("[initLocalCache][初始化角色与菜单的关联数量为 {}]", roleMenuList.size()); log.info("[initRoleMenuLocalCache][初始化角色与菜单的关联数量为 {}]", roleMenuList.size());
}
/**
* 初始化 {@link #userRoleCache} 缓存
*/
@VisibleForTesting
void initUserRoleLocalCache() {
// 获取用户与角色的关联列表如果有更新
List<UserRoleDO> userRoleList = loadUserRoleIfUpdate(userRoleMaxUpdateTime);
if (CollUtil.isEmpty(userRoleList)) {
return;
}
// 初始化 userRoleCache 缓存
ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
userRoleList.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
userRoleCache = CollectionUtils.convertMultiMap2(userRoleList, UserRoleDO::getUserId, UserRoleDO::getRoleId);
userRoleMaxUpdateTime = getMaxValue(userRoleList, UserRoleDO::getUpdateTime);
log.info("[initUserRoleLocalCache][初始化用户与角色的关联数量为 {}]", userRoleList.size());
} }
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
@ -156,6 +206,27 @@ public class PermissionServiceImpl implements PermissionService {
return roleMenuMapper.selectList(); return roleMenuMapper.selectList();
} }
/**
* 如果用户与角色的关联发生变化从数据库中获取最新的全量用户与角色的关联
* 如果未发生变化则返回空
*
* @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
* @return 角色与菜单的关联列表
*/
protected List<UserRoleDO> loadUserRoleIfUpdate(Date maxUpdateTime) {
// 第一步判断是否要更新
if (maxUpdateTime == null) { // 如果更新时间为空说明 DB 一定有新数据
log.info("[loadUserRoleIfUpdate][首次加载全量用户与角色的关联]");
} else { // 判断数据库中是否有更新的用户与角色的关联
if (userRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
return null;
}
log.info("[loadUserRoleIfUpdate][增量加载全量用户与角色的关联]");
}
// 第二步如果有更新则从数据库加载所有用户与角色的关联
return userRoleMapper.selectList();
}
@Override @Override
public List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes, public List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
Collection<Integer> menusStatuses) { Collection<Integer> menusStatuses) {
@ -176,35 +247,33 @@ public class PermissionServiceImpl implements PermissionService {
} }
@Override @Override
public Set<Long> getUserRoleIds(Long userId, Collection<Integer> roleStatuses) { public Set<Long> getUserRoleIdsFromCache(Long userId, Collection<Integer> roleStatuses) {
List<UserRoleDO> userRoleList = userRoleMapper.selectListByUserId(userId); Set<Long> roleIds = new HashSet<>(userRoleCache.get(userId));
// 过滤角色状态 // 过滤角色状态
if (CollectionUtil.isNotEmpty(roleStatuses)) { if (CollectionUtil.isNotEmpty(roleStatuses)) {
userRoleList.removeIf(userRoleDO -> { roleIds.removeIf(roleId -> {
RoleDO role = roleService.getRoleFromCache(userRoleDO.getRoleId()); RoleDO role = roleService.getRoleFromCache(roleId);
return role == null || !roleStatuses.contains(role.getStatus()); return role == null || !roleStatuses.contains(role.getStatus());
}); });
} }
return CollectionUtils.convertSet(userRoleList, UserRoleDO::getRoleId); return roleIds;
} }
@Override @Override
public Set<Long> getRoleMenuIds(Long roleId) { public Set<Long> getRoleMenuIds(Long roleId) {
// 如果是管理员的情况下获取全部菜单编号 // 如果是管理员的情况下获取全部菜单编号
RoleDO role = roleService.getRole(roleId); if (roleService.hasAnySuperAdmin(Collections.singleton(roleId))) {
if (roleService.hasAnySuperAdmin(Collections.singletonList(role))) { return convertSet(menuService.getMenus(), MenuDO::getId);
return CollectionUtils.convertSet(menuService.getMenus(), MenuDO::getId);
} }
// 如果是非管理员的情况下获得拥有的菜单编号 // 如果是非管理员的情况下获得拥有的菜单编号
return CollectionUtils.convertSet(roleMenuMapper.selectListByRoleId(roleId), return convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
RoleMenuDO::getMenuId);
} }
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void assignRoleMenu(Long roleId, Set<Long> menuIds) { public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
// 获得角色拥有菜单编号 // 获得角色拥有菜单编号
Set<Long> dbMenuIds = CollectionUtils.convertSet(roleMenuMapper.selectListByRoleId(roleId), Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId),
RoleMenuDO::getMenuId); RoleMenuDO::getMenuId);
// 计算新增和删除的菜单编号 // 计算新增和删除的菜单编号
Collection<Long> createMenuIds = CollUtil.subtract(menuIds, dbMenuIds); Collection<Long> createMenuIds = CollUtil.subtract(menuIds, dbMenuIds);
@ -234,20 +303,21 @@ public class PermissionServiceImpl implements PermissionService {
@Override @Override
public Set<Long> getUserRoleIdListByUserId(Long userId) { public Set<Long> getUserRoleIdListByUserId(Long userId) {
return CollectionUtils.convertSet(userRoleMapper.selectListByUserId(userId), return convertSet(userRoleMapper.selectListByUserId(userId),
UserRoleDO::getRoleId); UserRoleDO::getRoleId);
} }
@Override @Override
public Set<Long> getUserRoleIdListByRoleId(Long roleId) { public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
return CollectionUtils.convertSet(userRoleMapper.selectListByRoleId(roleId), return convertSet(userRoleMapper.selectListByRoleIds(roleIds),
UserRoleDO::getRoleId); UserRoleDO::getUserId);
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void assignUserRole(Long userId, Set<Long> roleIds) { public void assignUserRole(Long userId, Set<Long> roleIds) {
// 获得角色拥有角色编号 // 获得角色拥有角色编号
Set<Long> dbRoleIds = CollectionUtils.convertSet(userRoleMapper.selectListByUserId(userId), Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId),
UserRoleDO::getRoleId); UserRoleDO::getRoleId);
// 计算新增和删除的角色编号 // 计算新增和删除的角色编号
Collection<Long> createRoleIds = CollUtil.subtract(roleIds, dbRoleIds); Collection<Long> createRoleIds = CollUtil.subtract(roleIds, dbRoleIds);
@ -264,6 +334,15 @@ public class PermissionServiceImpl implements PermissionService {
if (!CollectionUtil.isEmpty(deleteMenuIds)) { if (!CollectionUtil.isEmpty(deleteMenuIds)) {
userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds); userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);
} }
// 发送刷新消息. 注意需要事务提交后在进行发送刷新消息不然 db 还未提交结果缓存先刷新了
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
permissionProducer.sendUserRoleRefreshMessage();
}
});
} }
@Override @Override
@ -284,6 +363,7 @@ public class PermissionServiceImpl implements PermissionService {
@Override @Override
public void afterCommit() { public void afterCommit() {
permissionProducer.sendRoleMenuRefreshMessage(); permissionProducer.sendRoleMenuRefreshMessage();
permissionProducer.sendUserRoleRefreshMessage();
} }
}); });
@ -305,8 +385,17 @@ public class PermissionServiceImpl implements PermissionService {
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void processUserDeleted(Long userId) { public void processUserDeleted(Long userId) {
userRoleMapper.deleteListByUserId(userId); userRoleMapper.deleteListByUserId(userId);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
permissionProducer.sendUserRoleRefreshMessage();
}
});
} }
@Override @Override
@ -322,7 +411,7 @@ public class PermissionServiceImpl implements PermissionService {
} }
// 获得当前登录的角色如果为空说明没有权限 // 获得当前登录的角色如果为空说明没有权限
Set<Long> roleIds = getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus())); Set<Long> roleIds = getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
if (CollUtil.isEmpty(roleIds)) { if (CollUtil.isEmpty(roleIds)) {
return false; return false;
} }
@ -357,7 +446,7 @@ public class PermissionServiceImpl implements PermissionService {
} }
// 获得当前登录的角色如果为空说明没有权限 // 获得当前登录的角色如果为空说明没有权限
Set<Long> roleIds = getUserRoleIds(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus())); Set<Long> roleIds = getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
if (CollUtil.isEmpty(roleIds)) { if (CollUtil.isEmpty(roleIds)) {
return false; return false;
} }
@ -365,7 +454,7 @@ public class PermissionServiceImpl implements PermissionService {
if (roleService.hasAnySuperAdmin(roleIds)) { if (roleService.hasAnySuperAdmin(roleIds)) {
return true; return true;
} }
Set<String> userRoles = CollectionUtils.convertSet(roleService.getRolesFromCache(roleIds), Set<String> userRoles = convertSet(roleService.getRolesFromCache(roleIds),
RoleDO::getCode); RoleDO::getCode);
return CollUtil.containsAny(userRoles, Sets.newHashSet(roles)); return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
} }
@ -375,7 +464,7 @@ public class PermissionServiceImpl implements PermissionService {
public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) { public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO(); DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();
// 获得用户的角色 // 获得用户的角色
Set<Long> roleIds = getUserRoleIds(userId, singleton(CommonStatusEnum.ENABLE.getStatus())); Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
if (CollUtil.isEmpty(roleIds)) { if (CollUtil.isEmpty(roleIds)) {
return result; return result;
} }
@ -425,10 +514,4 @@ public class PermissionServiceImpl implements PermissionService {
return result; return result;
} }
@Override
public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
return CollectionUtils.convertSet(userRoleMapper.selectListByRoleIds(roleIds),
UserRoleDO::getUserId);
}
} }

View File

@ -1,10 +1,13 @@
package cn.iocoder.yudao.module.system.service.permission; package cn.iocoder.yudao.module.system.service.permission;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO; import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
@ -17,20 +20,24 @@ 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.mq.producer.permission.PermissionProducer;
import cn.iocoder.yudao.module.system.service.dept.DeptService; import cn.iocoder.yudao.module.system.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.*;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static java.util.Arrays.asList;
import static java.util.Collections.singleton; import static java.util.Collections.singleton;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -62,6 +69,226 @@ public class PermissionServiceTest extends BaseDbUnitTest {
@MockBean @MockBean
private PermissionProducer permissionProducer; private PermissionProducer permissionProducer;
@Test
public void testInitRoleMenuLocalCache() {
// mock 数据
RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(10L));
roleMenuMapper.insert(roleMenuDO01);
RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(20L));
roleMenuMapper.insert(roleMenuDO02);
// 调用
permissionService.initRoleMenuLocalCache();
// 断言 roleMenuCache 缓存
assertEquals(1, permissionService.getRoleMenuCache().keySet().size());
assertEquals(asList(10L, 20L), permissionService.getRoleMenuCache().get(1L));
// 断言 menuRoleCache 缓存
assertEquals(2, permissionService.getMenuRoleCache().size());
assertEquals(singletonList(1L), permissionService.getMenuRoleCache().get(10L));
assertEquals(singletonList(1L), permissionService.getMenuRoleCache().get(20L));
// 断言 maxUpdateTime 缓存
Date maxUpdateTime = permissionService.getRoleMenuMaxUpdateTime();
assertEquals(ObjectUtils.max(roleMenuDO01.getUpdateTime(), roleMenuDO02.getUpdateTime()), maxUpdateTime);
}
@Test
public void testInitUserRoleLocalCache() {
// mock 数据
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
userRoleMapper.insert(userRoleDO01);
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
userRoleMapper.insert(roleMenuDO02);
// 调用
permissionService.initUserRoleLocalCache();
// 断言 roleMenuCache 缓存
assertEquals(1, permissionService.getUserRoleCache().size());
assertEquals(asSet(10L, 20L), permissionService.getUserRoleCache().get(1L));
// 断言 maxUpdateTime 缓存
Date maxUpdateTime = permissionService.getUserRoleMaxUpdateTime();
assertEquals(ObjectUtils.max(userRoleDO01.getUpdateTime(), roleMenuDO02.getUpdateTime()), maxUpdateTime);
}
@Test
public void testGetRoleMenuListFromCache_superAdmin() {
// 准备参数
Collection<Long> roleIds = singletonList(100L);
Collection<Integer> menuTypes = asList(2, 3);
Collection<Integer> menusStatuses = asList(0, 1);
// mock 方法
List<RoleDO> roleList = singletonList(randomPojo(RoleDO.class, o -> o.setId(100L)));
when(roleService.getRolesFromCache(eq(roleIds))).thenReturn(roleList);
when(roleService.hasAnySuperAdmin(same(roleList))).thenReturn(true);
List<MenuDO> menuList = randomPojoList(MenuDO.class);
when(menuService.getMenuListFromCache(eq(menuTypes), eq(menusStatuses))).thenReturn(menuList);
// 调用
List<MenuDO> result = permissionService.getRoleMenuListFromCache(roleIds, menuTypes, menusStatuses);
// 断言
assertSame(menuList, result);
}
@Test
public void testGetRoleMenuListFromCache_normal() {
// 准备参数
Collection<Long> roleIds = asSet(100L, 200L);
Collection<Integer> menuTypes = asList(2, 3);
Collection<Integer> menusStatuses = asList(0, 1);
// mock 方法
Multimap<Long, Long> roleMenuCache = ImmutableMultimap.<Long, Long>builder().put(100L, 1000L)
.put(200L, 2000L).put(200L, 2001L).build();
permissionService.setRoleMenuCache(roleMenuCache);
List<MenuDO> menuList = randomPojoList(MenuDO.class);
when(menuService.getMenuListFromCache(eq(asList(1000L, 2000L, 2001L)), eq(menuTypes), eq(menusStatuses))).thenReturn(menuList);
// 调用
List<MenuDO> result = permissionService.getRoleMenuListFromCache(roleIds, menuTypes, menusStatuses);
// 断言
assertSame(menuList, result);
}
@Test
public void testGetUserRoleIdsFromCache() {
// 准备参数
Long userId = 1L;
Collection<Integer> roleStatuses = singleton(CommonStatusEnum.ENABLE.getStatus());
// mock 方法
Map<Long, Set<Long>> userRoleCache = MapUtil.<Long, Set<Long>>builder()
.put(1L, asSet(10L, 20L)).build();
permissionService.setUserRoleCache(userRoleCache);
RoleDO roleDO01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
when(roleService.getRoleFromCache(eq(10L))).thenReturn(roleDO01);
RoleDO roleDO02 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
when(roleService.getRoleFromCache(eq(20L))).thenReturn(roleDO02);
// 调用
Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(userId, roleStatuses);
// 断言
assertEquals(asSet(10L), roleIds);
}
@Test
public void testGetRoleMenuIds_superAdmin() {
// 准备参数
Long roleId = 100L;
// mock 方法
when(roleService.hasAnySuperAdmin(eq(singleton(100L)))).thenReturn(true);
List<MenuDO> menuList = singletonList(randomPojo(MenuDO.class).setId(1L));
when(menuService.getMenus()).thenReturn(menuList);
// 调用
Set<Long> menuIds = permissionService.getRoleMenuIds(roleId);
// 断言
assertEquals(singleton(1L), menuIds);
}
@Test
public void testGetRoleMenuIds_normal() {
// 准备参数
Long roleId = 100L;
// mock 数据
RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1L);
roleMenuMapper.insert(roleMenu01);
RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(2L);
roleMenuMapper.insert(roleMenu02);
// 调用
Set<Long> menuIds = permissionService.getRoleMenuIds(roleId);
// 断言
assertEquals(asSet(1L, 2L), menuIds);
}
@Test
public void testAssignRoleMenu() {
// 准备参数
Long roleId = 1L;
Set<Long> menuIds = asSet(200L, 300L);
// mock 数据
RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(100L);
roleMenuMapper.insert(roleMenu01);
RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(200L);
roleMenuMapper.insert(roleMenu02);
// 调用
permissionService.assignRoleMenu(roleId, menuIds);
// 断言
List<RoleMenuDO> roleMenuList = roleMenuMapper.selectList();
assertEquals(2, roleMenuList.size());
assertEquals(1L, roleMenuList.get(0).getRoleId());
assertEquals(200L, roleMenuList.get(0).getMenuId());
assertEquals(1L, roleMenuList.get(1).getRoleId());
assertEquals(300L, roleMenuList.get(1).getMenuId());
verify(permissionProducer).sendRoleMenuRefreshMessage();
}
@Test
public void testAssignUserRole() {
// 准备参数
Long userId = 1L;
Set<Long> roleIds = asSet(200L, 300L);
// mock 数据
UserRoleDO userRole01 = randomPojo(UserRoleDO.class).setUserId(1L).setRoleId(100L);
userRoleMapper.insert(userRole01);
UserRoleDO userRole02 = randomPojo(UserRoleDO.class).setUserId(1L).setRoleId(200L);
userRoleMapper.insert(userRole02);
// 调用
permissionService.assignUserRole(userId, roleIds);
// 断言
List<UserRoleDO> userRoleDOList = userRoleMapper.selectList();
assertEquals(2, userRoleDOList.size());
assertEquals(1L, userRoleDOList.get(0).getUserId());
assertEquals(200L, userRoleDOList.get(0).getRoleId());
assertEquals(1L, userRoleDOList.get(1).getUserId());
assertEquals(300L, userRoleDOList.get(1).getRoleId());
verify(permissionProducer).sendUserRoleRefreshMessage();
}
@Test
public void testGetUserRoleIdListByUserId() {
// 准备参数
Long userId = 1L;
// mock 数据
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
userRoleMapper.insert(userRoleDO01);
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
userRoleMapper.insert(roleMenuDO02);
// 调用
Set<Long> result = permissionService.getUserRoleIdListByUserId(userId);
// 断言
assertEquals(asSet(10L, 20L), result);
}
@Test
public void testGetUserRoleIdListByRoleIds() {
// 准备参数
Collection<Long> roleIds = asSet(10L, 20L);
// mock 数据
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
userRoleMapper.insert(userRoleDO01);
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(2L).setRoleId(20L));
userRoleMapper.insert(roleMenuDO02);
// 调用
Set<Long> result = permissionService.getUserRoleIdListByRoleIds(roleIds);
// 断言
assertEquals(asSet(1L, 2L), result);
}
@Test
public void testAssignRoleDataScope() {
// 准备参数
Long roleId = 1L;
Integer dataScope = 2;
Set<Long> dataScopeDeptIds = asSet(10L, 20L);
// 调用
permissionService.assignRoleDataScope(roleId, dataScope, dataScopeDeptIds);
// 断言
verify(roleService).updateRoleDataScope(eq(roleId), eq(dataScope), eq(dataScopeDeptIds));
}
@Test @Test
public void testProcessRoleDeleted() { public void testProcessRoleDeleted() {
// 准备参数 // 准备参数
@ -89,6 +316,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
assertPojoEquals(dbUserRoles.get(0), userRoleDO02); assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
// 断言调用 // 断言调用
verify(permissionProducer).sendRoleMenuRefreshMessage(); verify(permissionProducer).sendRoleMenuRefreshMessage();
verify(permissionProducer).sendUserRoleRefreshMessage();
} }
@Test @Test
@ -127,14 +355,33 @@ public class PermissionServiceTest extends BaseDbUnitTest {
List<UserRoleDO> dbUserRoles = userRoleMapper.selectList(); List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();
assertEquals(1, dbUserRoles.size()); assertEquals(1, dbUserRoles.size());
assertPojoEquals(dbUserRoles.get(0), userRoleDO02); assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
// 断言调用
verify(permissionProducer).sendUserRoleRefreshMessage();
} }
// @Test
// public void testHasAnyRoles_superAdmin() {
// // 准备参数
// String[] roles = new String[]{"yunai", "tudou"};
// // mock 方法
// List<RoleDO> roleList = singletonList(randomPojo(RoleDO.class, o -> o.setId(100L)));
// when(roleService.getRolesFromCache(eq(roleIds))).thenReturn(roleList);
// when(roleService.hasAnySuperAdmin(same(roleList))).thenReturn(true);
// List<MenuDO> menuList = randomPojoList(MenuDO.class);
// when(menuService.getMenuListFromCache(eq(menuTypes), eq(menusStatuses))).thenReturn(menuList);
//
// // 调用
// List<MenuDO> result = permissionService.getRoleMenuListFromCache(roleIds, menuTypes, menusStatuses);
// // 断言
// assertSame(menuList, result);
// }
@Test @Test
public void testGetDeptDataPermission_All() { public void testGetDeptDataPermission_All() {
// 准备参数 // 准备参数
Long userId = 1L; Long userId = 1L;
// mock 用户的角色编号 // mock 用户的角色编号
userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L)); permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
// mock 获得用户的角色 // mock 获得用户的角色
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope()) RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope())
.setStatus(CommonStatusEnum.ENABLE.getStatus())); .setStatus(CommonStatusEnum.ENABLE.getStatus()));
@ -154,7 +401,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
// 准备参数 // 准备参数
Long userId = 1L; Long userId = 1L;
// mock 用户的角色编号 // mock 用户的角色编号
userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L)); permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
// mock 获得用户的角色 // mock 获得用户的角色
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope()) RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())
.setStatus(CommonStatusEnum.ENABLE.getStatus())); .setStatus(CommonStatusEnum.ENABLE.getStatus()));
@ -164,7 +411,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的看看会不会重复调用 when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的看看会不会重复调用
// 调用 // 调用
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(1L); DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
// 断言 // 断言
assertFalse(result.getAll()); assertFalse(result.getAll());
assertFalse(result.getSelf()); assertFalse(result.getSelf());
@ -178,7 +425,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
// 准备参数 // 准备参数
Long userId = 1L; Long userId = 1L;
// mock 用户的角色编号 // mock 用户的角色编号
userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L)); permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
// mock 获得用户的角色 // mock 获得用户的角色
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope()) RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope())
.setStatus(CommonStatusEnum.ENABLE.getStatus())); .setStatus(CommonStatusEnum.ENABLE.getStatus()));
@ -188,7 +435,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的看看会不会重复调用 when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的看看会不会重复调用
// 调用 // 调用
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(1L); DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
// 断言 // 断言
assertFalse(result.getAll()); assertFalse(result.getAll());
assertFalse(result.getSelf()); assertFalse(result.getSelf());
@ -201,7 +448,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
// 准备参数 // 准备参数
Long userId = 1L; Long userId = 1L;
// mock 用户的角色编号 // mock 用户的角色编号
userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L)); permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
// mock 获得用户的角色 // mock 获得用户的角色
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope()) RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope())
.setStatus(CommonStatusEnum.ENABLE.getStatus())); .setStatus(CommonStatusEnum.ENABLE.getStatus()));
@ -229,7 +476,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
// 准备参数 // 准备参数
Long userId = 1L; Long userId = 1L;
// mock 用户的角色编号 // mock 用户的角色编号
userRoleMapper.insert(new UserRoleDO().setUserId(userId).setRoleId(2L)); permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
// mock 获得用户的角色 // mock 获得用户的角色
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope()) RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope())
.setStatus(CommonStatusEnum.ENABLE.getStatus())); .setStatus(CommonStatusEnum.ENABLE.getStatus()));