|
@@ -3,45 +3,38 @@ 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.ArrayUtil;
|
|
|
+import cn.hutool.extra.spring.SpringUtil;
|
|
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
|
|
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
|
|
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
|
|
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
|
|
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;
|
|
|
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.UserRoleDO;
|
|
|
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.baomidou.dynamic.datasource.annotation.DSTransactional;
|
|
|
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.CacheEvict;
|
|
|
+import org.springframework.cache.annotation.Cacheable;
|
|
|
+import org.springframework.cache.annotation.Caching;
|
|
|
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.function.Supplier;
|
|
|
|
|
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
|
|
-import static java.util.Collections.singleton;
|
|
|
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
|
|
|
|
|
/**
|
|
|
* 权限 Service 实现类
|
|
@@ -52,38 +45,6 @@ import static java.util.Collections.singleton;
|
|
|
@Slf4j
|
|
|
public class PermissionServiceImpl implements PermissionService {
|
|
|
|
|
|
- /**
|
|
|
- * 角色编号与菜单编号的缓存映射
|
|
|
- * key:角色编号
|
|
|
- * value:菜单编号的数组
|
|
|
- *
|
|
|
- * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
|
|
- */
|
|
|
- @Getter
|
|
|
- @Setter // 单元测试需要
|
|
|
- private volatile Multimap<Long, Long> roleMenuCache;
|
|
|
- /**
|
|
|
- * 菜单编号与角色编号的缓存映射
|
|
|
- * key:菜单编号
|
|
|
- * value:角色编号的数组
|
|
|
- *
|
|
|
- * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
|
|
- */
|
|
|
- @Getter
|
|
|
- @Setter // 单元测试需要
|
|
|
- private volatile Multimap<Long, Long> menuRoleCache;
|
|
|
-
|
|
|
- /**
|
|
|
- * 用户编号与角色编号的缓存映射
|
|
|
- * key:用户编号
|
|
|
- * value:角色编号的数组
|
|
|
- *
|
|
|
- * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
|
|
- */
|
|
|
- @Getter
|
|
|
- @Setter // 单元测试需要
|
|
|
- private volatile Map<Long, Set<Long>> userRoleCache;
|
|
|
-
|
|
|
@Resource
|
|
|
private RoleMenuMapper roleMenuMapper;
|
|
|
@Resource
|
|
@@ -98,115 +59,89 @@ public class PermissionServiceImpl implements PermissionService {
|
|
|
@Resource
|
|
|
private AdminUserService userService;
|
|
|
|
|
|
- @Resource
|
|
|
- private PermissionProducer permissionProducer;
|
|
|
-
|
|
|
@Override
|
|
|
- @PostConstruct
|
|
|
- public void initLocalCache() {
|
|
|
- initLocalCacheForRoleMenu();
|
|
|
- initLocalCacheForUserRole();
|
|
|
- }
|
|
|
+ public boolean hasAnyPermissions(Long userId, String... permissions) {
|
|
|
+ // 如果为空,说明已经有权限
|
|
|
+ if (ArrayUtil.isEmpty(permissions)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
|
|
|
- /**
|
|
|
- * 刷新 RoleMenu 本地缓存
|
|
|
- */
|
|
|
- @VisibleForTesting
|
|
|
- void initLocalCacheForRoleMenu() {
|
|
|
- // 注意:忽略自动多租户,因为要全局初始化缓存
|
|
|
- TenantUtils.executeIgnore(() -> {
|
|
|
- // 第一步:查询数据
|
|
|
- List<RoleMenuDO> roleMenus = roleMenuMapper.selectList();
|
|
|
- log.info("[initLocalCacheForRoleMenu][缓存角色与菜单,数量为:{}]", roleMenus.size());
|
|
|
-
|
|
|
- // 第二步:构建缓存
|
|
|
- ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder();
|
|
|
- ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
|
|
|
- roleMenus.forEach(roleMenuDO -> {
|
|
|
- roleMenuCacheBuilder.put(roleMenuDO.getRoleId(), roleMenuDO.getMenuId());
|
|
|
- menuRoleCacheBuilder.put(roleMenuDO.getMenuId(), roleMenuDO.getRoleId());
|
|
|
- });
|
|
|
- roleMenuCache = roleMenuCacheBuilder.build();
|
|
|
- menuRoleCache = menuRoleCacheBuilder.build();
|
|
|
- });
|
|
|
+ // 获得当前登录的角色。如果为空,说明没有权限
|
|
|
+ List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
|
|
|
+ if (CollUtil.isEmpty(roles)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 情况一:遍历判断每个权限,如果有一满足,说明有权限
|
|
|
+ for (String permission : permissions) {
|
|
|
+ if (hasAnyPermission(roles, permission)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 情况二:如果是超管,也说明有权限
|
|
|
+ return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 刷新 UserRole 本地缓存
|
|
|
+ * 判断指定角色,是否拥有该 permission 权限
|
|
|
+ *
|
|
|
+ * @param roles 指定角色数组
|
|
|
+ * @param permission 权限标识
|
|
|
+ * @return 是否拥有
|
|
|
*/
|
|
|
- @VisibleForTesting
|
|
|
- void initLocalCacheForUserRole() {
|
|
|
- // 注意:忽略自动多租户,因为要全局初始化缓存
|
|
|
- TenantUtils.executeIgnore(() -> {
|
|
|
- // 第一步:加载数据
|
|
|
- List<UserRoleDO> userRoles = userRoleMapper.selectList();
|
|
|
- log.info("[initLocalCacheForUserRole][缓存用户与角色,数量为:{}]", userRoles.size());
|
|
|
-
|
|
|
- // 第二步:构建缓存。
|
|
|
- ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
|
|
|
- userRoles.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
|
|
|
- userRoleCache = CollectionUtils.convertMultiMap2(userRoles, UserRoleDO::getUserId, UserRoleDO::getRoleId);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
|
|
|
- Collection<Integer> menusStatuses) {
|
|
|
- // 任一一个参数为空时,不返回任何菜单
|
|
|
- if (CollectionUtils.isAnyEmpty(roleIds, menuTypes, menusStatuses)) {
|
|
|
- return Collections.emptyList();
|
|
|
+ private boolean hasAnyPermission(List<RoleDO> roles, String permission) {
|
|
|
+ List<Long> menuIds = menuService.getMenuIdListByPermissionFromCache(permission);
|
|
|
+ // 采用严格模式,如果权限找不到对应的 Menu 的话,也认为没有权限
|
|
|
+ if (CollUtil.isEmpty(menuIds)) {
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
- // 判断角色是否包含超级管理员。如果是超级管理员,获取到全部
|
|
|
- List<RoleDO> roleList = roleService.getRoleListFromCache(roleIds);
|
|
|
- if (roleService.hasAnySuperAdmin(roleList)) {
|
|
|
- return menuService.getMenuListFromCache(menuTypes, menusStatuses);
|
|
|
+ // 判断是否有权限
|
|
|
+ Set<Long> roleIds = convertSet(roles, RoleDO::getId);
|
|
|
+ for (Long menuId : menuIds) {
|
|
|
+ // 获得拥有该菜单的角色编号集合
|
|
|
+ Set<Long> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menuId);
|
|
|
+ // 如果有交集,说明有权限
|
|
|
+ if (CollUtil.containsAny(menuRoleIds, roleIds)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- // 获得角色拥有的菜单关联
|
|
|
- List<Long> menuIds = MapUtils.getList(roleMenuCache, roleIds);
|
|
|
- return menuService.getMenuListFromCache(menuIds, menuTypes, menusStatuses);
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public Set<Long> getUserRoleIdsFromCache(Long userId, Collection<Integer> roleStatuses) {
|
|
|
- Set<Long> cacheRoleIds = userRoleCache.get(userId);
|
|
|
- // 创建用户的时候没有分配角色,会存在空指针异常
|
|
|
- if (CollUtil.isEmpty(cacheRoleIds)) {
|
|
|
- return Collections.emptySet();
|
|
|
- }
|
|
|
- Set<Long> roleIds = new HashSet<>(cacheRoleIds);
|
|
|
- // 过滤角色状态
|
|
|
- if (CollectionUtil.isNotEmpty(roleStatuses)) {
|
|
|
- roleIds.removeIf(roleId -> {
|
|
|
- RoleDO role = roleService.getRoleFromCache(roleId);
|
|
|
- return role == null || !roleStatuses.contains(role.getStatus());
|
|
|
- });
|
|
|
+ public boolean hasAnyRoles(Long userId, String... roles) {
|
|
|
+ // 如果为空,说明已经有权限
|
|
|
+ if (ArrayUtil.isEmpty(roles)) {
|
|
|
+ return true;
|
|
|
}
|
|
|
- return roleIds;
|
|
|
- }
|
|
|
|
|
|
- @Override
|
|
|
- public Set<Long> getRoleMenuIds(Long roleId) {
|
|
|
- // 如果是管理员的情况下,获取全部菜单编号
|
|
|
- if (roleService.hasAnySuperAdmin(Collections.singleton(roleId))) {
|
|
|
- return convertSet(menuService.getMenuList(), MenuDO::getId);
|
|
|
+ // 获得当前登录的角色。如果为空,说明没有权限
|
|
|
+ List<RoleDO> roleList = getEnableUserRoleListByUserIdFromCache(userId);
|
|
|
+ if (CollUtil.isEmpty(roleList)) {
|
|
|
+ return false;
|
|
|
}
|
|
|
- // 如果是非管理员的情况下,获得拥有的菜单编号
|
|
|
- return convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
|
|
|
+
|
|
|
+ // 判断是否有角色
|
|
|
+ Set<String> userRoles = convertSet(roleList, RoleDO::getCode);
|
|
|
+ return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
|
|
|
}
|
|
|
|
|
|
+ // ========== 角色-菜单的相关方法 ==========
|
|
|
+
|
|
|
@Override
|
|
|
- @Transactional(rollbackFor = Exception.class)
|
|
|
+ @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
|
|
|
+ @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
|
|
|
+ allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快
|
|
|
public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
|
|
|
// 获得角色拥有菜单编号
|
|
|
- Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId),
|
|
|
- RoleMenuDO::getMenuId);
|
|
|
+ Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
|
|
|
// 计算新增和删除的菜单编号
|
|
|
Collection<Long> createMenuIds = CollUtil.subtract(menuIds, dbMenuIds);
|
|
|
Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIds);
|
|
|
// 执行新增和删除。对于已经授权的菜单,不用做任何处理
|
|
|
- if (!CollectionUtil.isEmpty(createMenuIds)) {
|
|
|
+ if (CollUtil.isNotEmpty(createMenuIds)) {
|
|
|
roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
|
|
|
RoleMenuDO entity = new RoleMenuDO();
|
|
|
entity.setRoleId(roleId);
|
|
@@ -214,34 +149,57 @@ public class PermissionServiceImpl implements PermissionService {
|
|
|
return entity;
|
|
|
}));
|
|
|
}
|
|
|
- if (!CollectionUtil.isEmpty(deleteMenuIds)) {
|
|
|
+ if (CollUtil.isNotEmpty(deleteMenuIds)) {
|
|
|
roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
|
|
|
}
|
|
|
- // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
|
|
|
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
|
|
+ }
|
|
|
|
|
|
- @Override
|
|
|
- public void afterCommit() {
|
|
|
- permissionProducer.sendRoleMenuRefreshMessage();
|
|
|
- }
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ @Caching(evict = {
|
|
|
+ @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
|
|
|
+ allEntries = true), // allEntries 清空所有缓存,此处无法方便获得 roleId 对应的 menu 缓存们
|
|
|
+ @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST,
|
|
|
+ allEntries = true) // allEntries 清空所有缓存,此处无法方便获得 roleId 对应的 user 缓存们
|
|
|
+ })
|
|
|
+ public void processRoleDeleted(Long roleId) {
|
|
|
+ // 标记删除 UserRole
|
|
|
+ userRoleMapper.deleteListByRoleId(roleId);
|
|
|
+ // 标记删除 RoleMenu
|
|
|
+ roleMenuMapper.deleteListByRoleId(roleId);
|
|
|
+ }
|
|
|
|
|
|
- });
|
|
|
+ @Override
|
|
|
+ @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
|
|
|
+ public void processMenuDeleted(Long menuId) {
|
|
|
+ roleMenuMapper.deleteListByMenuId(menuId);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public Set<Long> getUserRoleIdListByUserId(Long userId) {
|
|
|
- return convertSet(userRoleMapper.selectListByUserId(userId),
|
|
|
- UserRoleDO::getRoleId);
|
|
|
+ public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) {
|
|
|
+ if (CollUtil.isEmpty(roleIds)) {
|
|
|
+ return Collections.emptySet();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是管理员的情况下,获取全部菜单编号
|
|
|
+ if (roleService.hasAnySuperAdmin(roleIds)) {
|
|
|
+ return convertSet(menuService.getMenuList(), MenuDO::getId);
|
|
|
+ }
|
|
|
+ // 如果是非管理员的情况下,获得拥有的菜单编号
|
|
|
+ return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
|
|
|
- return convertSet(userRoleMapper.selectListByRoleIds(roleIds),
|
|
|
- UserRoleDO::getUserId);
|
|
|
+ @Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
|
|
|
+ public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) {
|
|
|
+ return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId);
|
|
|
}
|
|
|
|
|
|
+ // ========== 用户-角色的相关方法 ==========
|
|
|
+
|
|
|
@Override
|
|
|
- @Transactional(rollbackFor = Exception.class)
|
|
|
+ @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
|
|
|
+ @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
|
|
|
public void assignUserRole(Long userId, Set<Long> roleIds) {
|
|
|
// 获得角色拥有角色编号
|
|
|
Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId),
|
|
@@ -261,137 +219,68 @@ public class PermissionServiceImpl implements PermissionService {
|
|
|
if (!CollectionUtil.isEmpty(deleteMenuIds)) {
|
|
|
userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);
|
|
|
}
|
|
|
- // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
|
|
|
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
|
|
-
|
|
|
- @Override
|
|
|
- public void afterCommit() {
|
|
|
- permissionProducer.sendUserRoleRefreshMessage();
|
|
|
- }
|
|
|
-
|
|
|
- });
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds) {
|
|
|
- roleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds);
|
|
|
+ @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
|
|
|
+ public void processUserDeleted(Long userId) {
|
|
|
+ userRoleMapper.deleteListByUserId(userId);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- @Transactional(rollbackFor = Exception.class)
|
|
|
- public void processRoleDeleted(Long roleId) {
|
|
|
- // 标记删除 UserRole
|
|
|
- userRoleMapper.deleteListByRoleId(roleId);
|
|
|
- // 标记删除 RoleMenu
|
|
|
- roleMenuMapper.deleteListByRoleId(roleId);
|
|
|
- // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
|
|
|
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
|
|
-
|
|
|
- @Override
|
|
|
- public void afterCommit() {
|
|
|
- permissionProducer.sendRoleMenuRefreshMessage();
|
|
|
- permissionProducer.sendUserRoleRefreshMessage();
|
|
|
- }
|
|
|
-
|
|
|
- });
|
|
|
+ public Set<Long> getUserRoleIdListByUserId(Long userId) {
|
|
|
+ return convertSet(userRoleMapper.selectListByUserId(userId), UserRoleDO::getRoleId);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- @Transactional(rollbackFor = Exception.class)
|
|
|
- public void processMenuDeleted(Long menuId) {
|
|
|
- roleMenuMapper.deleteListByMenuId(menuId);
|
|
|
- // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
|
|
|
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
|
|
-
|
|
|
- @Override
|
|
|
- public void afterCommit() {
|
|
|
- permissionProducer.sendRoleMenuRefreshMessage();
|
|
|
- }
|
|
|
-
|
|
|
- });
|
|
|
+ @Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
|
|
|
+ public Set<Long> getUserRoleIdListByUserIdFromCache(Long userId) {
|
|
|
+ return getUserRoleIdListByUserId(userId);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- @Transactional(rollbackFor = Exception.class)
|
|
|
- public void processUserDeleted(Long userId) {
|
|
|
- userRoleMapper.deleteListByUserId(userId);
|
|
|
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
|
|
-
|
|
|
- @Override
|
|
|
- public void afterCommit() {
|
|
|
- permissionProducer.sendUserRoleRefreshMessage();
|
|
|
- }
|
|
|
-
|
|
|
- });
|
|
|
+ public Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds) {
|
|
|
+ return convertSet(userRoleMapper.selectListByRoleIds(roleIds), UserRoleDO::getUserId);
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- public boolean hasAnyPermissions(Long userId, String... permissions) {
|
|
|
- // 如果为空,说明已经有权限
|
|
|
- if (ArrayUtil.isEmpty(permissions)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- // 获得当前登录的角色。如果为空,说明没有权限
|
|
|
- Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
|
|
|
- if (CollUtil.isEmpty(roleIds)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- // 判断是否是超管。如果是,当然符合条件
|
|
|
- if (roleService.hasAnySuperAdmin(roleIds)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- // 遍历权限,判断是否有一个满足
|
|
|
- return Arrays.stream(permissions).anyMatch(permission -> {
|
|
|
- List<MenuDO> menuList = menuService.getMenuListByPermissionFromCache(permission);
|
|
|
- // 采用严格模式,如果权限找不到对应的 Menu 的话,认为
|
|
|
- if (CollUtil.isEmpty(menuList)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- // 获得是否拥有该权限,任一一个
|
|
|
- return menuList.stream().anyMatch(menu -> CollUtil.containsAny(roleIds,
|
|
|
- menuRoleCache.get(menu.getId())));
|
|
|
- });
|
|
|
+ /**
|
|
|
+ * 获得用户拥有的角色,并且这些角色是开启状态的
|
|
|
+ *
|
|
|
+ * @param userId 用户编号
|
|
|
+ * @return 用户拥有的角色
|
|
|
+ */
|
|
|
+ @VisibleForTesting
|
|
|
+ List<RoleDO> getEnableUserRoleListByUserIdFromCache(Long userId) {
|
|
|
+ // 获得用户拥有的角色编号
|
|
|
+ Set<Long> roleIds = getSelf().getUserRoleIdListByUserIdFromCache(userId);
|
|
|
+ // 获得角色数组,并移除被禁用的
|
|
|
+ List<RoleDO> roles = roleService.getRoleListFromCache(roleIds);
|
|
|
+ roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()));
|
|
|
+ return roles;
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- public boolean hasAnyRoles(Long userId, String... roles) {
|
|
|
- // 如果为空,说明已经有权限
|
|
|
- if (ArrayUtil.isEmpty(roles)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
+ // ========== 用户-部门的相关方法 ==========
|
|
|
|
|
|
- // 获得当前登录的角色。如果为空,说明没有权限
|
|
|
- Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
|
|
|
- if (CollUtil.isEmpty(roleIds)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- // 判断是否是超管。如果是,当然符合条件
|
|
|
- if (roleService.hasAnySuperAdmin(roleIds)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- Set<String> userRoles = convertSet(roleService.getRoleListFromCache(roleIds),
|
|
|
- RoleDO::getCode);
|
|
|
- return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
|
|
|
+ @Override
|
|
|
+ public void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds) {
|
|
|
+ roleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@DataPermission(enable = false) // 关闭数据权限,不然就会出现递归获取数据权限的问题
|
|
|
- @TenantIgnore // 忽略多租户的自动过滤。如果不忽略,会导致添加租户时,因为切换租户,导致获取不到 User。即使忽略,本身该方法不存在跨租户的操作,不会存在问题。
|
|
|
public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
|
|
|
// 获得用户的角色
|
|
|
- Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
|
|
|
+ List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
|
|
|
+
|
|
|
// 如果角色为空,则只能查看自己
|
|
|
DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();
|
|
|
- if (CollUtil.isEmpty(roleIds)) {
|
|
|
+ if (CollUtil.isEmpty(roles)) {
|
|
|
result.setSelf(true);
|
|
|
return result;
|
|
|
}
|
|
|
- List<RoleDO> roles = roleService.getRoleListFromCache(roleIds);
|
|
|
|
|
|
// 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询
|
|
|
- Supplier<Long> userDeptIdCache = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
|
|
|
+ Supplier<Long> userDeptId = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
|
|
|
// 遍历每个角色,计算
|
|
|
for (RoleDO role : roles) {
|
|
|
// 为空时,跳过
|
|
@@ -408,20 +297,19 @@ public class PermissionServiceImpl implements PermissionService {
|
|
|
CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
|
|
|
// 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。
|
|
|
// 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉
|
|
|
- CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
|
|
|
+ CollUtil.addAll(result.getDeptIds(), userDeptId.get());
|
|
|
continue;
|
|
|
}
|
|
|
// 情况三,DEPT_ONLY
|
|
|
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
|
|
|
- CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptIdCache.get());
|
|
|
+ CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get());
|
|
|
continue;
|
|
|
}
|
|
|
// 情况四,DEPT_DEPT_AND_CHILD
|
|
|
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
|
|
|
- List<DeptDO> depts = deptService.getDeptListByParentIdFromCache(userDeptIdCache.get(), true);
|
|
|
- CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, DeptDO::getId));
|
|
|
+ CollUtil.addAll(result.getDeptIds(), deptService.getChildDeptIdListFromCache(userDeptId.get()));
|
|
|
// 添加本身部门编号
|
|
|
- CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
|
|
|
+ CollUtil.addAll(result.getDeptIds(), userDeptId.get());
|
|
|
continue;
|
|
|
}
|
|
|
// 情况五,SELF
|
|
@@ -430,9 +318,18 @@ public class PermissionServiceImpl implements PermissionService {
|
|
|
continue;
|
|
|
}
|
|
|
// 未知情况,error log 即可
|
|
|
- log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, JsonUtils.toJsonString(result));
|
|
|
+ log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, toJsonString(result));
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 获得自身的代理对象,解决 AOP 生效问题
|
|
|
+ *
|
|
|
+ * @return 自己
|
|
|
+ */
|
|
|
+ private PermissionServiceImpl getSelf() {
|
|
|
+ return SpringUtil.getBean(getClass());
|
|
|
+ }
|
|
|
+
|
|
|
}
|