diff --git a/yudao-admin-server/pom.xml b/yudao-admin-server/pom.xml index f3b91ac0f..4bd789b5c 100644 --- a/yudao-admin-server/pom.xml +++ b/yudao-admin-server/pom.xml @@ -122,6 +122,11 @@ yudao-spring-boot-starter-tenant + + cn.iocoder.boot + yudao-spring-boot-starter-data-permission + + org.apache.velocity velocity-engine-core diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/rule/DeptDataPermissionRule.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/rule/DeptDataPermissionRule.java new file mode 100644 index 000000000..ed5c2b9cf --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/rule/DeptDataPermissionRule.java @@ -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 DEPT_TABLE_CONFIG = new HashMap<>(); + /** + * 基于用户的表字段配置 + * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。 + * + * key:表名 + * value:字段名 + */ + private final Map USER_TABLE_CONFIG = new HashMap<>(); + /** + * 所有表名,是 {@link #DEPT_TABLE_CONFIG} 和 {@link #USER_TABLE_CONFIG} 的合集 + */ + private final Set TABLE_NAMES = new HashSet<>(); + + @Override + public Set 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 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 entityClass) { + addDeptTableConfig(entityClass, DEPT_COLUMN_NAME); + } + + public void addDeptTableConfig(Class entityClass, String columnName) { + String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName(); + DEPT_TABLE_CONFIG.put(tableName, columnName); + TABLE_NAMES.add(tableName); + } + + public void addUserTableConfig(Class entityClass) { + addUserTableConfig(entityClass, DEPT_COLUMN_NAME); + } + + public void addUserTableConfig(Class entityClass, String columnName) { + String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName(); + USER_TABLE_CONFIG.put(tableName, columnName); + TABLE_NAMES.add(tableName); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/DeptDataPermissionService.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/DeptDataPermissionService.java new file mode 100644 index 000000000..c7e44437f --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/DeptDataPermissionService.java @@ -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); + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/dto/DeptDataPermissionRespDTO.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/dto/DeptDataPermissionRespDTO.java new file mode 100644 index 000000000..3aa3aba51 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/dto/DeptDataPermissionRespDTO.java @@ -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 deptIds; + + public DeptDataPermissionRespDTO() { + this.all = false; + this.self = false; + this.deptIds = new HashSet<>(); + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/impl/DeptDataPermissionServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/impl/DeptDataPermissionServiceImpl.java new file mode 100644 index 000000000..b02c7e239 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/impl/DeptDataPermissionServiceImpl.java @@ -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 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 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; + } + +} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java index 2804b2813..6060f7e23 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java @@ -92,9 +92,7 @@ public class SysAuthServiceImpl implements SysAuthService { throw new UsernameNotFoundException(username); } // 创建 LoginUser 对象 - LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user); - loginUser.setPostIds(user.getPostIds()); - return loginUser; + return this.buildLoginUser(user); } @Override @@ -107,9 +105,7 @@ public class SysAuthServiceImpl implements SysAuthService { this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_MOCK, SysLoginResultEnum.SUCCESS); // 创建 LoginUser 对象 - LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user); - loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表 - return loginUser; + return this.buildLoginUser(user); } @Override @@ -117,10 +113,9 @@ public class SysAuthServiceImpl implements SysAuthService { // 判断验证码是否正确 this.verifyCaptcha(reqVO.getUsername(), reqVO.getUuid(), reqVO.getCode()); - // 使用账号密码,进行登录。 + // 使用账号密码,进行登录 LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); - loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表 - loginUser.setGroups(this.getUserPosts(loginUser.getPostIds())); + // 缓存登陆用户到 Redis 中,返回 sessionId 编号 return userSessionCoreService.createUserSession(loginUser, userIp, userAgent); } @@ -234,8 +229,7 @@ public class SysAuthServiceImpl implements SysAuthService { this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_SOCIAL, SysLoginResultEnum.SUCCESS); // 创建 LoginUser 对象 - LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user); - loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表 + LoginUser loginUser = this.buildLoginUser(user); // 绑定社交用户(更新) 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.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表 // 绑定社交用户(新增) socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum); @@ -305,15 +298,14 @@ public class SysAuthServiceImpl implements SysAuthService { return null; } // 刷新 LoginUser 缓存 - this.refreshLoginUserCache(token, loginUser); - return loginUser; + return this.refreshLoginUserCache(token, loginUser); } - private void refreshLoginUserCache(String token, LoginUser loginUser) { + private LoginUser refreshLoginUserCache(String token, LoginUser loginUser) { // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存 if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() < userSessionCoreService.getSessionTimeoutMillis() / 3) { - return; + return loginUser; } // 重新加载 SysUserDO 信息 @@ -323,9 +315,18 @@ public class SysAuthServiceImpl implements SysAuthService { } // 刷新 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.setRoleIds(this.getUserRoleIds(loginUser.getId())); - userSessionCoreService.refreshUserSession(token, loginUser); + loginUser.setGroups(this.getUserPosts(user.getPostIds())); + return loginUser; } } diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/dept/impl/SysDeptServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/dept/impl/SysDeptServiceImpl.java index cac9bf565..2eccdf721 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/dept/impl/SysDeptServiceImpl.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/dept/impl/SysDeptServiceImpl.java @@ -169,9 +169,12 @@ public class SysDeptServiceImpl implements SysDeptService { @Override public List getDeptsByParentIdFromCache(Long parentId, boolean recursive) { - List result = new ArrayList<>(); + if (parentId == null) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); // TODO 芋艿:待优化,新增缓存,避免每次遍历的计算 // 递归,简单粗暴 - this.listDeptsByParentIdFromCache(result, parentId, + this.getDeptsByParentIdFromCache(result, parentId, recursive ? Integer.MAX_VALUE : 1, // 如果递归获取,则无限;否则,只递归 1 次 parentDeptCache); return result; @@ -185,8 +188,8 @@ public class SysDeptServiceImpl implements SysDeptService { * @param recursiveCount 递归次数 * @param parentDeptMap 父部门 Map,使用缓存,避免变化 */ - private void listDeptsByParentIdFromCache(List result, Long parentId, int recursiveCount, - Multimap parentDeptMap) { + private void getDeptsByParentIdFromCache(List result, Long parentId, int recursiveCount, + Multimap parentDeptMap) { // 递归次数为 0,结束! if (recursiveCount == 0) { return; @@ -198,7 +201,7 @@ public class SysDeptServiceImpl implements SysDeptService { } result.addAll(depts); // 继续递归 - depts.forEach(dept -> listDeptsByParentIdFromCache(result, dept.getId(), + depts.forEach(dept -> getDeptsByParentIdFromCache(result, dept.getId(), recursiveCount - 1, parentDeptMap)); } diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/permission/impl/SysRoleServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/permission/impl/SysRoleServiceImpl.java index c233f4f85..0e82c5051 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/permission/impl/SysRoleServiceImpl.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/permission/impl/SysRoleServiceImpl.java @@ -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.service.permission.SysPermissionService; 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.collect.ImmutableMap; import lombok.extern.slf4j.Slf4j; @@ -127,6 +128,7 @@ public class SysRoleServiceImpl implements SysRoleService { SysRoleDO role = SysRoleConvert.INSTANCE.convert(reqVO); role.setType(SysRoleTypeEnum.CUSTOM.getType()); role.setStatus(CommonStatusEnum.ENABLE.getStatus()); + role.setDataScope(DataScopeEnum.ALL.getScope()); // 默认可查看所有数据。原因是,可能一些项目不需要项目权限 roleMapper.insert(role); // 发送刷新消息 roleProducer.sendRoleRefreshMessage(); diff --git a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java index 492128e6b..11696fe25 100644 --- a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java +++ b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java @@ -89,7 +89,6 @@ public class SysAuthServiceImplTest extends BaseDbUnitTest { LoginUser loginUser = (LoginUser) authService.loadUserByUsername(username); // 校验 AssertUtils.assertPojoEquals(user, loginUser, "updateTime"); - assertNull(loginUser.getRoleIds()); // 此时不会加载角色,所以是空的 } @Test diff --git a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/permission/SysRoleServiceTest.java b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/permission/SysRoleServiceTest.java index 3b39e976c..08bd6521b 100644 --- a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/permission/SysRoleServiceTest.java +++ b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/permission/SysRoleServiceTest.java @@ -133,11 +133,11 @@ public class SysRoleServiceTest extends BaseDbUnitTest { //调用 Set 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); - assertEquals(DataScopeEnum.DEPT_CUSTOM.getScore(), newRoleDO.getDataScope()); + assertEquals(DataScopeEnum.DEPT_CUSTOM.getScope(), newRoleDO.getDataScope()); Set newDeptIdSet = newRoleDO.getDataScopeDeptIds(); assertTrue(deptIdSet.size() == newDeptIdSet.size()); @@ -242,7 +242,7 @@ public class SysRoleServiceTest extends BaseDbUnitTest { o.setCode("code"); o.setType(SysRoleTypeEnum.CUSTOM.getType()); o.setStatus(1); - o.setDataScope(DataScopeEnum.ALL.getScore()); + o.setDataScope(DataScopeEnum.ALL.getScope()); }); roleMapper.insert(roleDO); @@ -293,7 +293,7 @@ public class SysRoleServiceTest extends BaseDbUnitTest { o.setName(name); o.setType(typeEnum.getType()); o.setStatus(status); - o.setDataScope(scopeEnum.getScore()); + o.setDataScope(scopeEnum.getScope()); o.setCode(code); }); return roleDO; diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 980e09705..300635c6b 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -369,6 +369,12 @@ ${revision} + + cn.iocoder.boot + yudao-spring-boot-starter-data-permission + ${revision} + + org.projectlombok lombok diff --git a/yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/DataPermissionAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/DataPermissionAutoConfiguration.java index 01e4bccdb..44bd502a2 100644 --- a/yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/DataPermissionAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/DataPermissionAutoConfiguration.java @@ -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.DataPermissionRuleFactory; 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.Configuration; import java.util.List; +/** + * 数据全新啊的自动配置类 + * + * @author 芋道源码 + */ @Configuration public class DataPermissionAutoConfiguration { @@ -19,9 +26,15 @@ public class DataPermissionAutoConfiguration { } @Bean - public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(List rules) { + public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(MybatisPlusInterceptor interceptor, + List rules) { + // 创建 DataPermissionDatabaseInterceptor 拦截器 DataPermissionRuleFactory ruleFactory = dataPermissionRuleFactory(rules); - return new DataPermissionDatabaseInterceptor(ruleFactory); + DataPermissionDatabaseInterceptor inner = new DataPermissionDatabaseInterceptor(ruleFactory); + // 添加到 interceptor 中 + // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定 + MyBatisUtils.addInterceptor(interceptor, inner, 0); + return inner; } @Bean diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java index 91db0b568..3833ee62d 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java @@ -1,5 +1,6 @@ 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.UserTypeEnum; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -28,10 +29,6 @@ public class LoginUser implements UserDetails { * 关联 {@link UserTypeEnum} */ private Integer userType; - /** - * 角色编号数组 - */ - private Set roleIds; /** * 最后更新时间 */ @@ -56,7 +53,10 @@ public class LoginUser implements UserDetails { // ========== UserTypeEnum.ADMIN 独有字段 ========== // TODO 芋艿:可以通过定义一个 Map exts 的方式,去除管理员的字段。不过这样会导致系统比较复杂,所以暂时不去掉先; - + /** + * 角色编号数组 + */ + private Set roleIds; /** * 部门编号 */ @@ -71,6 +71,15 @@ public class LoginUser implements UserDetails { // TODO jason:这个字段,改成 postCodes 明确更好哈 private List groups; + // ========== 上下文 ========== + /** + * 上下文字段,不进行持久化 + * + * 1. 用于基于 LoginUser 维度的临时缓存 + */ + @JsonIgnore + private Map context; + @Override @JsonIgnore// 避免序列化 public String getPassword() { @@ -115,4 +124,17 @@ public class LoginUser implements UserDetails { return true; // 返回 true,不依赖 Spring Security 判断 } + // ========== 上下文 ========== + + public void setContext(String key, Object value) { + if (context == null) { + context = new HashMap<>(); + } + context.put(key, value); + } + + public T getContext(String key, Class type) { + return MapUtil.get(context, key, type); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/enums/DataScopeEnum.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/enums/DataScopeEnum.java index ac3396ce3..c67a526d4 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/enums/DataScopeEnum.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/enums/DataScopeEnum.java @@ -15,14 +15,16 @@ import lombok.Getter; public enum DataScopeEnum { ALL(1), // 全部数据权限 + DEPT_CUSTOM(2), // 指定部门数据权限 DEPT_ONLY(3), // 部门数据权限 DEPT_AND_CHILD(4), // 部门及以下数据权限 - DEPT_SELF(5); // 仅本人数据权限 + + SELF(5); // 仅本人数据权限 /** * 范围 */ - private final Integer score; + private final Integer scope; } diff --git a/更新日志.md b/更新日志.md index 29f109c3d..14f95b04f 100644 --- a/更新日志.md +++ b/更新日志.md @@ -29,6 +29,7 @@ * 【新增】多租户,支持 Web、Security、Job、MQ、Async、DB、Redis 组件 * 【新增】数据权限,内置基于部门过滤的规则 * 【新增】用户前台的昵称、头像的修改 +* 【优化】管理后台的登陆成功后,LoginUser 使用统一方法补全信息 ### 🐞 Bug Fixes