基于部门的数据权限
This commit is contained in:
parent
b0855cc626
commit
986cb72421
|
@ -122,6 +122,11 @@
|
||||||
<artifactId>yudao-spring-boot-starter-tenant</artifactId>
|
<artifactId>yudao-spring-boot-starter-tenant</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-spring-boot-starter-data-permission</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.velocity</groupId>
|
<groupId>org.apache.velocity</groupId>
|
||||||
<artifactId>velocity-engine-core</artifactId>
|
<artifactId>velocity-engine-core</artifactId>
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
package cn.iocoder.yudao.adminserver.framework.datapermission.core.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.common.util.collection.CollectionUtils;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||||
|
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||||
|
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||||
|
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.sf.jsqlparser.expression.Alias;
|
||||||
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
|
import net.sf.jsqlparser.expression.LongValue;
|
||||||
|
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||||
|
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||||
|
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||||
|
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于部门的 {@link DataPermissionRule} 数据权限规则实现
|
||||||
|
*
|
||||||
|
* 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。
|
||||||
|
*
|
||||||
|
* 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改?
|
||||||
|
* 1. 一般情况下,dept_id 不进行修改,则会导致用户看到之前的数据。【yudao-admin-server 采用该方案】
|
||||||
|
* 2. 部分情况下,希望该用户还是能看到之前的数据,则有两种方式解决:【需要你改造该 DeptDataPermissionRule 的实现代码】
|
||||||
|
* 1)编写洗数据的脚本,将 dept_id 修改成新部门的编号;【建议】
|
||||||
|
* 最终过滤条件是 WHERE dept_id = ?
|
||||||
|
* 2)洗数据的话,可能涉及的数据量较大,也可以采用 user_id 进行过滤的方式,此时需要获取到 dept_id 对应的所有 user_id 用户编号;
|
||||||
|
* 最终过滤条件是 WHERE user_id IN (?, ?, ? ...)
|
||||||
|
* 3)想要保证原 dept_id 和 user_id 都可以看的到,此时使用 dept_id 和 user_id 一起过滤;
|
||||||
|
* 最终过滤条件是 WHERE dept_id = ? OR user_id IN (?, ?, ? ...)
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于部门的表字段配置
|
||||||
|
* 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
|
||||||
|
*
|
||||||
|
* key:表名
|
||||||
|
* value:字段名
|
||||||
|
*/
|
||||||
|
private final Map<String, String> DEPT_TABLE_CONFIG = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* 基于用户的表字段配置
|
||||||
|
* 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
|
||||||
|
*
|
||||||
|
* key:表名
|
||||||
|
* value:字段名
|
||||||
|
*/
|
||||||
|
private final Map<String, String> USER_TABLE_CONFIG = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* 所有表名,是 {@link #DEPT_TABLE_CONFIG} 和 {@link #USER_TABLE_CONFIG} 的合集
|
||||||
|
*/
|
||||||
|
private final Set<String> TABLE_NAMES = new HashSet<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getTableNames() {
|
||||||
|
return TABLE_NAMES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Expression getExpression(String tableName, Alias tableAlias) {
|
||||||
|
// 只有有登陆用户的情况下,才进行数据权限的处理
|
||||||
|
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获得数据权限
|
||||||
|
DeptDataPermissionRespDTO deptDataPermission = deptDataPermissionService.getDeptDataPermission(loginUser);
|
||||||
|
if (deptDataPermission == null) {
|
||||||
|
log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况一,如果是 ALL 可查看全部,则无需拼接条件
|
||||||
|
if (deptDataPermission.getAll()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限
|
||||||
|
if (CollUtil.isEmpty(deptDataPermission.getDeptIds())
|
||||||
|
&& Boolean.FALSE.equals(deptDataPermission.getSelf())) {
|
||||||
|
return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况三,拼接 Dept 和 User 的条件,最后组合
|
||||||
|
Expression deptExpression = this.buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());
|
||||||
|
Expression userExpression = this.buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
|
||||||
|
if (deptExpression == null && userExpression == null) {
|
||||||
|
log.error("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",
|
||||||
|
JsonUtils.toJsonString(loginUser), tableName, tableAlias, JsonUtils.toJsonString(deptDataPermission));
|
||||||
|
throw new NullPointerException(String.format("LoginUser(%d) tableName(%s) tableAlias(%s) 构建的条件为空",
|
||||||
|
loginUser.getId(), tableName, tableAlias.getName()));
|
||||||
|
}
|
||||||
|
if (deptExpression == null) {
|
||||||
|
return userExpression;
|
||||||
|
}
|
||||||
|
if (userExpression == null) {
|
||||||
|
return deptExpression;
|
||||||
|
}
|
||||||
|
// 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE dept_id IN ? OR user_id = ?
|
||||||
|
return new OrExpression(deptExpression, userExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {
|
||||||
|
// 如果不存在配置,则无需作为条件
|
||||||
|
String columnName = DEPT_TABLE_CONFIG.get(tableName);
|
||||||
|
if (StrUtil.isEmpty(columnName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 拼接条件
|
||||||
|
return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
|
||||||
|
new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {
|
||||||
|
// 如果不查看自己,则无需作为条件
|
||||||
|
if (Boolean.FALSE.equals(self)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String columnName = USER_TABLE_CONFIG.get(tableName);
|
||||||
|
if (StrUtil.isEmpty(columnName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 拼接条件
|
||||||
|
return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 添加配置 ====================
|
||||||
|
|
||||||
|
public void addDeptTableConfig(Class<? extends BaseDO> entityClass) {
|
||||||
|
addDeptTableConfig(entityClass, DEPT_COLUMN_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDeptTableConfig(Class<? extends BaseDO> entityClass, String columnName) {
|
||||||
|
String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
|
||||||
|
DEPT_TABLE_CONFIG.put(tableName, columnName);
|
||||||
|
TABLE_NAMES.add(tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addUserTableConfig(Class<? extends BaseDO> entityClass) {
|
||||||
|
addUserTableConfig(entityClass, DEPT_COLUMN_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addUserTableConfig(Class<? extends BaseDO> entityClass, String columnName) {
|
||||||
|
String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
|
||||||
|
USER_TABLE_CONFIG.put(tableName, columnName);
|
||||||
|
TABLE_NAMES.add(tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package cn.iocoder.yudao.adminserver.framework.datapermission.core.service.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门的数据权限 Response DTO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DeptDataPermissionRespDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可查看全部数据
|
||||||
|
*/
|
||||||
|
private Boolean all;
|
||||||
|
/**
|
||||||
|
* 是否可查看自己的数据
|
||||||
|
*/
|
||||||
|
private Boolean self;
|
||||||
|
/**
|
||||||
|
* 可查看的部门编号数组
|
||||||
|
*/
|
||||||
|
private Set<Long> deptIds;
|
||||||
|
|
||||||
|
public DeptDataPermissionRespDTO() {
|
||||||
|
this.all = false;
|
||||||
|
this.self = false;
|
||||||
|
this.deptIds = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -92,9 +92,7 @@ public class SysAuthServiceImpl implements SysAuthService {
|
||||||
throw new UsernameNotFoundException(username);
|
throw new UsernameNotFoundException(username);
|
||||||
}
|
}
|
||||||
// 创建 LoginUser 对象
|
// 创建 LoginUser 对象
|
||||||
LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
|
return this.buildLoginUser(user);
|
||||||
loginUser.setPostIds(user.getPostIds());
|
|
||||||
return loginUser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -107,9 +105,7 @@ public class SysAuthServiceImpl implements SysAuthService {
|
||||||
this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_MOCK, SysLoginResultEnum.SUCCESS);
|
this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_MOCK, SysLoginResultEnum.SUCCESS);
|
||||||
|
|
||||||
// 创建 LoginUser 对象
|
// 创建 LoginUser 对象
|
||||||
LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
|
return this.buildLoginUser(user);
|
||||||
loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
|
|
||||||
return loginUser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -117,10 +113,9 @@ public class SysAuthServiceImpl implements SysAuthService {
|
||||||
// 判断验证码是否正确
|
// 判断验证码是否正确
|
||||||
this.verifyCaptcha(reqVO.getUsername(), reqVO.getUuid(), reqVO.getCode());
|
this.verifyCaptcha(reqVO.getUsername(), reqVO.getUuid(), reqVO.getCode());
|
||||||
|
|
||||||
// 使用账号密码,进行登录。
|
// 使用账号密码,进行登录
|
||||||
LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
|
LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
|
||||||
loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
|
|
||||||
loginUser.setGroups(this.getUserPosts(loginUser.getPostIds()));
|
|
||||||
// 缓存登陆用户到 Redis 中,返回 sessionId 编号
|
// 缓存登陆用户到 Redis 中,返回 sessionId 编号
|
||||||
return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
|
return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
|
||||||
}
|
}
|
||||||
|
@ -234,8 +229,7 @@ public class SysAuthServiceImpl implements SysAuthService {
|
||||||
this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_SOCIAL, SysLoginResultEnum.SUCCESS);
|
this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_SOCIAL, SysLoginResultEnum.SUCCESS);
|
||||||
|
|
||||||
// 创建 LoginUser 对象
|
// 创建 LoginUser 对象
|
||||||
LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
|
LoginUser loginUser = this.buildLoginUser(user);
|
||||||
loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
|
|
||||||
|
|
||||||
// 绑定社交用户(更新)
|
// 绑定社交用户(更新)
|
||||||
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum);
|
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum);
|
||||||
|
@ -252,7 +246,6 @@ public class SysAuthServiceImpl implements SysAuthService {
|
||||||
|
|
||||||
// 使用账号密码,进行登录。
|
// 使用账号密码,进行登录。
|
||||||
LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
|
LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
|
||||||
loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
|
|
||||||
|
|
||||||
// 绑定社交用户(新增)
|
// 绑定社交用户(新增)
|
||||||
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum);
|
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum);
|
||||||
|
@ -305,15 +298,14 @@ public class SysAuthServiceImpl implements SysAuthService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// 刷新 LoginUser 缓存
|
// 刷新 LoginUser 缓存
|
||||||
this.refreshLoginUserCache(token, loginUser);
|
return this.refreshLoginUserCache(token, loginUser);
|
||||||
return loginUser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshLoginUserCache(String token, LoginUser loginUser) {
|
private LoginUser refreshLoginUserCache(String token, LoginUser loginUser) {
|
||||||
// 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存
|
// 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存
|
||||||
if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() <
|
if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() <
|
||||||
userSessionCoreService.getSessionTimeoutMillis() / 3) {
|
userSessionCoreService.getSessionTimeoutMillis() / 3) {
|
||||||
return;
|
return loginUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新加载 SysUserDO 信息
|
// 重新加载 SysUserDO 信息
|
||||||
|
@ -323,9 +315,18 @@ public class SysAuthServiceImpl implements SysAuthService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新 LoginUser 缓存
|
// 刷新 LoginUser 缓存
|
||||||
|
LoginUser newLoginUser= this.buildLoginUser(user);
|
||||||
|
userSessionCoreService.refreshUserSession(token, newLoginUser);
|
||||||
|
return newLoginUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoginUser buildLoginUser(SysUserDO user) {
|
||||||
|
LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
|
||||||
|
// 补全字段
|
||||||
loginUser.setDeptId(user.getDeptId());
|
loginUser.setDeptId(user.getDeptId());
|
||||||
loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId()));
|
loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId()));
|
||||||
userSessionCoreService.refreshUserSession(token, loginUser);
|
loginUser.setGroups(this.getUserPosts(user.getPostIds()));
|
||||||
|
return loginUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,9 +169,12 @@ public class SysDeptServiceImpl implements SysDeptService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<SysDeptDO> getDeptsByParentIdFromCache(Long parentId, boolean recursive) {
|
public List<SysDeptDO> getDeptsByParentIdFromCache(Long parentId, boolean recursive) {
|
||||||
List<SysDeptDO> result = new ArrayList<>();
|
if (parentId == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<SysDeptDO> result = new ArrayList<>(); // TODO 芋艿:待优化,新增缓存,避免每次遍历的计算
|
||||||
// 递归,简单粗暴
|
// 递归,简单粗暴
|
||||||
this.listDeptsByParentIdFromCache(result, parentId,
|
this.getDeptsByParentIdFromCache(result, parentId,
|
||||||
recursive ? Integer.MAX_VALUE : 1, // 如果递归获取,则无限;否则,只递归 1 次
|
recursive ? Integer.MAX_VALUE : 1, // 如果递归获取,则无限;否则,只递归 1 次
|
||||||
parentDeptCache);
|
parentDeptCache);
|
||||||
return result;
|
return result;
|
||||||
|
@ -185,8 +188,8 @@ public class SysDeptServiceImpl implements SysDeptService {
|
||||||
* @param recursiveCount 递归次数
|
* @param recursiveCount 递归次数
|
||||||
* @param parentDeptMap 父部门 Map,使用缓存,避免变化
|
* @param parentDeptMap 父部门 Map,使用缓存,避免变化
|
||||||
*/
|
*/
|
||||||
private void listDeptsByParentIdFromCache(List<SysDeptDO> result, Long parentId, int recursiveCount,
|
private void getDeptsByParentIdFromCache(List<SysDeptDO> result, Long parentId, int recursiveCount,
|
||||||
Multimap<Long, SysDeptDO> parentDeptMap) {
|
Multimap<Long, SysDeptDO> parentDeptMap) {
|
||||||
// 递归次数为 0,结束!
|
// 递归次数为 0,结束!
|
||||||
if (recursiveCount == 0) {
|
if (recursiveCount == 0) {
|
||||||
return;
|
return;
|
||||||
|
@ -198,7 +201,7 @@ public class SysDeptServiceImpl implements SysDeptService {
|
||||||
}
|
}
|
||||||
result.addAll(depts);
|
result.addAll(depts);
|
||||||
// 继续递归
|
// 继续递归
|
||||||
depts.forEach(dept -> listDeptsByParentIdFromCache(result, dept.getId(),
|
depts.forEach(dept -> getDeptsByParentIdFromCache(result, dept.getId(),
|
||||||
recursiveCount - 1, parentDeptMap));
|
recursiveCount - 1, parentDeptMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import cn.iocoder.yudao.adminserver.modules.system.enums.permission.SysRoleTypeE
|
||||||
import cn.iocoder.yudao.adminserver.modules.system.mq.producer.permission.SysRoleProducer;
|
import cn.iocoder.yudao.adminserver.modules.system.mq.producer.permission.SysRoleProducer;
|
||||||
import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
|
import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
|
||||||
import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService;
|
import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService;
|
||||||
|
import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -127,6 +128,7 @@ public class SysRoleServiceImpl implements SysRoleService {
|
||||||
SysRoleDO role = SysRoleConvert.INSTANCE.convert(reqVO);
|
SysRoleDO role = SysRoleConvert.INSTANCE.convert(reqVO);
|
||||||
role.setType(SysRoleTypeEnum.CUSTOM.getType());
|
role.setType(SysRoleTypeEnum.CUSTOM.getType());
|
||||||
role.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
role.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
role.setDataScope(DataScopeEnum.ALL.getScope()); // 默认可查看所有数据。原因是,可能一些项目不需要项目权限
|
||||||
roleMapper.insert(role);
|
roleMapper.insert(role);
|
||||||
// 发送刷新消息
|
// 发送刷新消息
|
||||||
roleProducer.sendRoleRefreshMessage();
|
roleProducer.sendRoleRefreshMessage();
|
||||||
|
|
|
@ -89,7 +89,6 @@ public class SysAuthServiceImplTest extends BaseDbUnitTest {
|
||||||
LoginUser loginUser = (LoginUser) authService.loadUserByUsername(username);
|
LoginUser loginUser = (LoginUser) authService.loadUserByUsername(username);
|
||||||
// 校验
|
// 校验
|
||||||
AssertUtils.assertPojoEquals(user, loginUser, "updateTime");
|
AssertUtils.assertPojoEquals(user, loginUser, "updateTime");
|
||||||
assertNull(loginUser.getRoleIds()); // 此时不会加载角色,所以是空的
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -133,11 +133,11 @@ public class SysRoleServiceTest extends BaseDbUnitTest {
|
||||||
|
|
||||||
//调用
|
//调用
|
||||||
Set<Long> deptIdSet = Arrays.asList(1L, 2L, 3L, 4L, 5L).stream().collect(Collectors.toSet());
|
Set<Long> deptIdSet = Arrays.asList(1L, 2L, 3L, 4L, 5L).stream().collect(Collectors.toSet());
|
||||||
sysRoleService.updateRoleDataScope(roleId, DataScopeEnum.DEPT_CUSTOM.getScore(), deptIdSet);
|
sysRoleService.updateRoleDataScope(roleId, DataScopeEnum.DEPT_CUSTOM.getScope(), deptIdSet);
|
||||||
|
|
||||||
//断言
|
//断言
|
||||||
SysRoleDO newRoleDO = roleMapper.selectById(roleId);
|
SysRoleDO newRoleDO = roleMapper.selectById(roleId);
|
||||||
assertEquals(DataScopeEnum.DEPT_CUSTOM.getScore(), newRoleDO.getDataScope());
|
assertEquals(DataScopeEnum.DEPT_CUSTOM.getScope(), newRoleDO.getDataScope());
|
||||||
|
|
||||||
Set<Long> newDeptIdSet = newRoleDO.getDataScopeDeptIds();
|
Set<Long> newDeptIdSet = newRoleDO.getDataScopeDeptIds();
|
||||||
assertTrue(deptIdSet.size() == newDeptIdSet.size());
|
assertTrue(deptIdSet.size() == newDeptIdSet.size());
|
||||||
|
@ -242,7 +242,7 @@ public class SysRoleServiceTest extends BaseDbUnitTest {
|
||||||
o.setCode("code");
|
o.setCode("code");
|
||||||
o.setType(SysRoleTypeEnum.CUSTOM.getType());
|
o.setType(SysRoleTypeEnum.CUSTOM.getType());
|
||||||
o.setStatus(1);
|
o.setStatus(1);
|
||||||
o.setDataScope(DataScopeEnum.ALL.getScore());
|
o.setDataScope(DataScopeEnum.ALL.getScope());
|
||||||
});
|
});
|
||||||
roleMapper.insert(roleDO);
|
roleMapper.insert(roleDO);
|
||||||
|
|
||||||
|
@ -293,7 +293,7 @@ public class SysRoleServiceTest extends BaseDbUnitTest {
|
||||||
o.setName(name);
|
o.setName(name);
|
||||||
o.setType(typeEnum.getType());
|
o.setType(typeEnum.getType());
|
||||||
o.setStatus(status);
|
o.setStatus(status);
|
||||||
o.setDataScope(scopeEnum.getScore());
|
o.setDataScope(scopeEnum.getScope());
|
||||||
o.setCode(code);
|
o.setCode(code);
|
||||||
});
|
});
|
||||||
return roleDO;
|
return roleDO;
|
||||||
|
|
|
@ -369,6 +369,12 @@
|
||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-spring-boot-starter-data-permission</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
|
|
|
@ -5,11 +5,18 @@ import cn.iocoder.yudao.framework.datapermission.core.db.DataPermissionDatabaseI
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl;
|
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据全新啊的自动配置类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class DataPermissionAutoConfiguration {
|
public class DataPermissionAutoConfiguration {
|
||||||
|
|
||||||
|
@ -19,9 +26,15 @@ public class DataPermissionAutoConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(List<DataPermissionRule> rules) {
|
public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(MybatisPlusInterceptor interceptor,
|
||||||
|
List<DataPermissionRule> rules) {
|
||||||
|
// 创建 DataPermissionDatabaseInterceptor 拦截器
|
||||||
DataPermissionRuleFactory ruleFactory = dataPermissionRuleFactory(rules);
|
DataPermissionRuleFactory ruleFactory = dataPermissionRuleFactory(rules);
|
||||||
return new DataPermissionDatabaseInterceptor(ruleFactory);
|
DataPermissionDatabaseInterceptor inner = new DataPermissionDatabaseInterceptor(ruleFactory);
|
||||||
|
// 添加到 interceptor 中
|
||||||
|
// 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定
|
||||||
|
MyBatisUtils.addInterceptor(interceptor, inner, 0);
|
||||||
|
return inner;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package cn.iocoder.yudao.framework.security.core;
|
package cn.iocoder.yudao.framework.security.core;
|
||||||
|
|
||||||
|
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.enums.UserTypeEnum;
|
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
@ -28,10 +29,6 @@ public class LoginUser implements UserDetails {
|
||||||
* 关联 {@link UserTypeEnum}
|
* 关联 {@link UserTypeEnum}
|
||||||
*/
|
*/
|
||||||
private Integer userType;
|
private Integer userType;
|
||||||
/**
|
|
||||||
* 角色编号数组
|
|
||||||
*/
|
|
||||||
private Set<Long> roleIds;
|
|
||||||
/**
|
/**
|
||||||
* 最后更新时间
|
* 最后更新时间
|
||||||
*/
|
*/
|
||||||
|
@ -56,7 +53,10 @@ public class LoginUser implements UserDetails {
|
||||||
|
|
||||||
// ========== UserTypeEnum.ADMIN 独有字段 ==========
|
// ========== UserTypeEnum.ADMIN 独有字段 ==========
|
||||||
// TODO 芋艿:可以通过定义一个 Map<String, String> exts 的方式,去除管理员的字段。不过这样会导致系统比较复杂,所以暂时不去掉先;
|
// TODO 芋艿:可以通过定义一个 Map<String, String> exts 的方式,去除管理员的字段。不过这样会导致系统比较复杂,所以暂时不去掉先;
|
||||||
|
/**
|
||||||
|
* 角色编号数组
|
||||||
|
*/
|
||||||
|
private Set<Long> roleIds;
|
||||||
/**
|
/**
|
||||||
* 部门编号
|
* 部门编号
|
||||||
*/
|
*/
|
||||||
|
@ -71,6 +71,15 @@ public class LoginUser implements UserDetails {
|
||||||
// TODO jason:这个字段,改成 postCodes 明确更好哈
|
// TODO jason:这个字段,改成 postCodes 明确更好哈
|
||||||
private List<String> groups;
|
private List<String> groups;
|
||||||
|
|
||||||
|
// ========== 上下文 ==========
|
||||||
|
/**
|
||||||
|
* 上下文字段,不进行持久化
|
||||||
|
*
|
||||||
|
* 1. 用于基于 LoginUser 维度的临时缓存
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
private Map<String, Object> context;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@JsonIgnore// 避免序列化
|
@JsonIgnore// 避免序列化
|
||||||
public String getPassword() {
|
public String getPassword() {
|
||||||
|
@ -115,4 +124,17 @@ public class LoginUser implements UserDetails {
|
||||||
return true; // 返回 true,不依赖 Spring Security 判断
|
return true; // 返回 true,不依赖 Spring Security 判断
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== 上下文 ==========
|
||||||
|
|
||||||
|
public void setContext(String key, Object value) {
|
||||||
|
if (context == null) {
|
||||||
|
context = new HashMap<>();
|
||||||
|
}
|
||||||
|
context.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getContext(String key, Class<T> type) {
|
||||||
|
return MapUtil.get(context, key, type);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,14 +15,16 @@ import lombok.Getter;
|
||||||
public enum DataScopeEnum {
|
public enum DataScopeEnum {
|
||||||
|
|
||||||
ALL(1), // 全部数据权限
|
ALL(1), // 全部数据权限
|
||||||
|
|
||||||
DEPT_CUSTOM(2), // 指定部门数据权限
|
DEPT_CUSTOM(2), // 指定部门数据权限
|
||||||
DEPT_ONLY(3), // 部门数据权限
|
DEPT_ONLY(3), // 部门数据权限
|
||||||
DEPT_AND_CHILD(4), // 部门及以下数据权限
|
DEPT_AND_CHILD(4), // 部门及以下数据权限
|
||||||
DEPT_SELF(5); // 仅本人数据权限
|
|
||||||
|
SELF(5); // 仅本人数据权限
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 范围
|
* 范围
|
||||||
*/
|
*/
|
||||||
private final Integer score;
|
private final Integer scope;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue