重构 Dept 数据权限逻辑,统一收到 yudao-spring-boot-starter-data-permission 包下

This commit is contained in:
YunaiV 2021-12-13 09:27:46 +08:00
parent 986cb72421
commit 8278b65777
17 changed files with 213 additions and 141 deletions

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.adminserver.framework.datapermission.core.service;
import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.security.core.LoginUser;
/**
* 基于部门的数据权限 Service 接口
*
* @author 芋道源码
*/
public interface DeptDataPermissionService {
/**
* 获得登陆用户的部门数据权限
*
* @param loginUser 登陆用户
* @return 部门数据权限
*/
DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser);
}

View File

@ -1,88 +0,0 @@
package cn.iocoder.yudao.adminserver.framework.datapermission.core.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.DeptDataPermissionService;
import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysDeptDO;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysRoleDO;
import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysDeptService;
import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Objects;
/**
* 基于部门的数据权限 Service 实现类
*
* @author 芋道源码
*/
@RequiredArgsConstructor
@Slf4j
public class DeptDataPermissionServiceImpl implements DeptDataPermissionService {
/**
* LoginUser Context 缓存 Key
*/
private static final String CONTEXT_KEY = DeptDataPermissionServiceImpl.class.getSimpleName();
private final SysRoleService roleService;
private final SysDeptService deptService;
@Override
public DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser) {
// 判断是否 context 已经缓存
DeptDataPermissionRespDTO result = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
if (result != null) {
return result;
}
// 创建 DeptDataPermissionRespDTO 对象
result = new DeptDataPermissionRespDTO();
List<SysRoleDO> roles = roleService.getRolesFromCache(loginUser.getRoleIds());
for (SysRoleDO role : roles) {
// 为空时跳过
if (role.getDataScope() == null) {
continue;
}
// 情况一ALL
if (Objects.equals(role.getDataScope(), DataScopeEnum.ALL.getScope())) {
result.setAll(true);
continue;
}
// 情况二DEPT_CUSTOM
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_CUSTOM.getScope())) {
CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
continue;
}
// 情况三DEPT_ONLY
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
CollectionUtils.addIfNotNull(result.getDeptIds(), loginUser.getDeptId());
continue;
}
// 情况四DEPT_DEPT_AND_CHILD
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
List<SysDeptDO> depts = deptService.getDeptsByParentIdFromCache(loginUser.getDeptId(), true);
CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, SysDeptDO::getId));
continue;
}
// 情况五SELF
if (Objects.equals(role.getDataScope(), DataScopeEnum.SELF.getScope())) {
result.setSelf(true);
continue;
}
// 未知情况error log 即可
log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", loginUser.getId(), JsonUtils.toJsonString(result));
}
// 添加到缓存并返回
loginUser.setContext(CONTEXT_KEY, result);
return null;
}
}

View File

@ -1,3 +1,4 @@
### 请求 /system/user/page 接口 => 没有权限
GET {{baseUrl}}/system/user/page?pageNo=1&pageSize=10
Authorization: Bearer test104 # 使用测试账号
Authorization: Bearer test1 # 使用测试账号
tenant-id: 1

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.adminserver.modules.system.framework.datapermission.config;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysDeptDO;
import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO;
import cn.iocoder.yudao.framework.datapermission.core.dept.rule.DeptDataPermissionRuleCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* system 模块的数据权限 Configuration
*
* @author 芋道源码
*/
@Configuration
public class SysDataPermissionConfiguration {
@Bean
public DeptDataPermissionRuleCustomizer sysDeptDataPermissionRuleCustomizer() {
return rule -> {
rule.addDeptColumn(SysUserDO.class);
rule.addDeptColumn(SysDeptDO.class, "id");
};
}
}

View File

@ -0,0 +1,4 @@
/**
* system 模块的数据权限配置
*/
package cn.iocoder.yudao.adminserver.modules.system.framework.datapermission;

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.adminserver.modules.system.service.permission;
import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
import cn.iocoder.yudao.framework.security.core.service.SecurityPermissionFrameworkService;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysMenuDO;
import org.springframework.lang.Nullable;
@ -15,7 +16,7 @@ import java.util.Set;
*
* @author 芋道源码
*/
public interface SysPermissionService extends SecurityPermissionFrameworkService {
public interface SysPermissionService extends SecurityPermissionFrameworkService, DeptDataPermissionFrameworkService {
/**
* 初始化权限的本地缓存

View File

@ -3,19 +3,25 @@ package cn.iocoder.yudao.adminserver.modules.system.service.permission.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.permission.SysRoleMenuMapper;
import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.permission.SysUserRoleMapper;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysDeptDO;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysMenuDO;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysRoleDO;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysRoleMenuDO;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysUserRoleDO;
import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.permission.SysRoleMenuMapper;
import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.permission.SysUserRoleMapper;
import cn.iocoder.yudao.adminserver.modules.system.mq.producer.permission.SysPermissionProducer;
import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysDeptService;
import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysMenuService;
import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService;
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.dept.service.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
@ -39,6 +45,11 @@ import java.util.*;
@Slf4j
public class SysPermissionServiceImpl implements SysPermissionService {
/**
* LoginUser Context 缓存 Key
*/
private static final String CONTEXT_KEY = SysPermissionServiceImpl.class.getSimpleName();
/**
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
* 因为已经通过 Redis Pub/Sub 机制所以频率不需要高
@ -75,6 +86,8 @@ public class SysPermissionServiceImpl implements SysPermissionService {
private SysRoleService roleService;
@Resource
private SysMenuService menuService;
@Resource
private SysDeptService deptService;
@Resource
private SysPermissionProducer permissionProducer;
@ -329,4 +342,58 @@ public class SysPermissionServiceImpl implements SysPermissionService {
return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
}
@Override
public DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser) {
// 判断是否 context 已经缓存
DeptDataPermissionRespDTO result = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
if (result != null) {
return result;
}
// 创建 DeptDataPermissionRespDTO 对象
result = new DeptDataPermissionRespDTO();
List<SysRoleDO> roles = roleService.getRolesFromCache(loginUser.getRoleIds());
for (SysRoleDO role : roles) {
// 为空时跳过
if (role.getDataScope() == null) {
continue;
}
// 情况一ALL
if (Objects.equals(role.getDataScope(), DataScopeEnum.ALL.getScope())) {
result.setAll(true);
continue;
}
// 情况二DEPT_CUSTOM
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_CUSTOM.getScope())) {
CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
// 自定义可见部门时保证可以看到自己所在的部门否则一些场景下可能会有问题
// 例如说登录时基于 t_user username 查询会可能被 dept_id 过滤掉
CollUtil.addAll(result.getDeptIds(), loginUser.getDeptId());
continue;
}
// 情况三DEPT_ONLY
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
CollectionUtils.addIfNotNull(result.getDeptIds(), loginUser.getDeptId());
continue;
}
// 情况四DEPT_DEPT_AND_CHILD
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
List<SysDeptDO> depts = deptService.getDeptsByParentIdFromCache(loginUser.getDeptId(), true);
CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, SysDeptDO::getId));
continue;
}
// 情况五SELF
if (Objects.equals(role.getDataScope(), DataScopeEnum.SELF.getScope())) {
result.setSelf(true);
continue;
}
// 未知情况error log 即可
log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", loginUser.getId(), JsonUtils.toJsonString(result));
}
// 添加到缓存并返回
loginUser.setContext(CONTEXT_KEY, result);
return result;
}
}

View File

@ -21,6 +21,13 @@
<artifactId>yudao-common</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
<optional>true</optional> <!-- 可选,如果使用 DeptDataPermissionRule 必须提供 -->
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>

View File

@ -18,7 +18,7 @@ import java.util.List;
* @author 芋道源码
*/
@Configuration
public class DataPermissionAutoConfiguration {
public class YudaoDataPermissionAutoConfiguration {
@Bean
public DataPermissionRuleFactory dataPermissionRuleFactory(List<DataPermissionRule> rules) {

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.framework.datapermission.config;
import cn.iocoder.yudao.framework.datapermission.core.dept.rule.DeptDataPermissionRule;
import cn.iocoder.yudao.framework.datapermission.core.dept.rule.DeptDataPermissionRuleCustomizer;
import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* 基于部门的数据权限 AutoConfiguration
*
* @author 芋道源码
*/
@Configuration
@ConditionalOnClass(LoginUser.class)
@ConditionalOnBean(value = {DeptDataPermissionFrameworkService.class, DeptDataPermissionRuleCustomizer.class})
public class YudaoDeptDataPermissionAutoConfiguration {
@Bean
public DeptDataPermissionRule deptDataPermissionRule(DeptDataPermissionFrameworkService service,
List<DeptDataPermissionRuleCustomizer> customizers) {
// 创建 DeptDataPermissionRule 对象
DeptDataPermissionRule rule = new DeptDataPermissionRule(service);
// 补全表配置
customizers.forEach(customizer -> customizer.customize(rule));
return rule;
}
}

View File

@ -0,0 +1,6 @@
/**
* 基于部门的数据权限规则
*
* @author 芋道源码
*/
package cn.iocoder.yudao.framework.datapermission.core.dept;

View File

@ -1,9 +1,9 @@
package cn.iocoder.yudao.adminserver.framework.datapermission.core.rule;
package cn.iocoder.yudao.framework.datapermission.core.dept.rule;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.DeptDataPermissionService;
import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.datapermission.core.dept.service.DeptDataPermissionFrameworkService;
import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
@ -51,7 +51,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
private static final String DEPT_COLUMN_NAME = "dept_id";
private static final String USER_COLUMN_NAME = "user_id";
private final DeptDataPermissionService deptDataPermissionService;
private final DeptDataPermissionFrameworkService deptDataPermissionService;
/**
* 基于部门的表字段配置
@ -60,7 +60,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
* key表名
* value字段名
*/
private final Map<String, String> DEPT_TABLE_CONFIG = new HashMap<>();
private final Map<String, String> deptColumns = new HashMap<>();
/**
* 基于用户的表字段配置
* 一般情况下每个表的部门编号字段是 dept_id通过该配置自定义
@ -68,9 +68,9 @@ public class DeptDataPermissionRule implements DataPermissionRule {
* key表名
* value字段名
*/
private final Map<String, String> USER_TABLE_CONFIG = new HashMap<>();
private final Map<String, String> userColumns = new HashMap<>();
/**
* 所有表名 {@link #DEPT_TABLE_CONFIG} {@link #USER_TABLE_CONFIG} 的合集
* 所有表名 {@link #deptColumns} {@link #userColumns} 的合集
*/
private final Set<String> TABLE_NAMES = new HashSet<>();
@ -126,7 +126,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {
// 如果不存在配置则无需作为条件
String columnName = DEPT_TABLE_CONFIG.get(tableName);
String columnName = deptColumns.get(tableName);
if (StrUtil.isEmpty(columnName)) {
return null;
}
@ -140,7 +140,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
if (Boolean.FALSE.equals(self)) {
return null;
}
String columnName = USER_TABLE_CONFIG.get(tableName);
String columnName = userColumns.get(tableName);
if (StrUtil.isEmpty(columnName)) {
return null;
}
@ -150,23 +150,23 @@ public class DeptDataPermissionRule implements DataPermissionRule {
// ==================== 添加配置 ====================
public void addDeptTableConfig(Class<? extends BaseDO> entityClass) {
addDeptTableConfig(entityClass, DEPT_COLUMN_NAME);
public void addDeptColumn(Class<? extends BaseDO> entityClass) {
addDeptColumn(entityClass, DEPT_COLUMN_NAME);
}
public void addDeptTableConfig(Class<? extends BaseDO> entityClass, String columnName) {
public void addDeptColumn(Class<? extends BaseDO> entityClass, String columnName) {
String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
DEPT_TABLE_CONFIG.put(tableName, columnName);
deptColumns.put(tableName, columnName);
TABLE_NAMES.add(tableName);
}
public void addUserTableConfig(Class<? extends BaseDO> entityClass) {
addUserTableConfig(entityClass, DEPT_COLUMN_NAME);
public void addUserColumn(Class<? extends BaseDO> entityClass) {
addUserColumn(entityClass, USER_COLUMN_NAME);
}
public void addUserTableConfig(Class<? extends BaseDO> entityClass, String columnName) {
public void addUserColumn(Class<? extends BaseDO> entityClass, String columnName) {
String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
USER_TABLE_CONFIG.put(tableName, columnName);
userColumns.put(tableName, columnName);
TABLE_NAMES.add(tableName);
}

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.framework.datapermission.core.dept.rule;
/**
* {@link DeptDataPermissionRule} 的自定义配置接口
*
* @author 芋道源码
*/
@FunctionalInterface
public interface DeptDataPermissionRuleCustomizer {
/**
* 自定义该权限规则
* 1. 调用 {@link DeptDataPermissionRule#addDeptColumn(Class, String)} 方法配置基于 dept_id 的过滤规则
* 2. 调用 {@link DeptDataPermissionRule#addUserColumn(Class, String)} 方法配置基于 user_id 的过滤规则
*
* @param rule 权限规则
*/
void customize(DeptDataPermissionRule rule);
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.framework.datapermission.core.dept.service;
import cn.iocoder.yudao.framework.datapermission.core.dept.service.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.security.core.LoginUser;
/**
* 基于部门的数据权限 Framework Service 接口
* 目前的实现类是 SysPermissionServiceImpl
*
* @author 芋道源码
*/
public interface DeptDataPermissionFrameworkService {
/**
* 获得登陆用户的部门数据权限
*
* @param loginUser 登陆用户
* @return 部门数据权限
*/
DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser);
}

View File

@ -1,10 +1,8 @@
package cn.iocoder.yudao.adminserver.framework.datapermission.core.service.dto;
package cn.iocoder.yudao.framework.datapermission.core.dept.service.dto;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**

View File

@ -1,2 +1,3 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.framework.datapermission.config.DataPermissionAutoConfiguration
cn.iocoder.yudao.framework.datapermission.config.YudaoDataPermissionAutoConfiguration,\
cn.iocoder.yudao.framework.datapermission.config.YudaoDeptDataPermissionAutoConfiguration

View File

@ -22,11 +22,6 @@
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>