Commit 986cb72421c35b514709e2cafebc22a412a22646
1 parent
b0855cc6
基于部门的数据权限
Showing
15 changed files
with
409 additions
and
36 deletions
yudao-admin-server/pom.xml
| ... | ... | @@ -123,6 +123,11 @@ |
| 123 | 123 | </dependency> |
| 124 | 124 | |
| 125 | 125 | <dependency> |
| 126 | + <groupId>cn.iocoder.boot</groupId> | |
| 127 | + <artifactId>yudao-spring-boot-starter-data-permission</artifactId> | |
| 128 | + </dependency> | |
| 129 | + | |
| 130 | + <dependency> | |
| 126 | 131 | <groupId>org.apache.velocity</groupId> |
| 127 | 132 | <artifactId>velocity-engine-core</artifactId> |
| 128 | 133 | </dependency> | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/rule/DeptDataPermissionRule.java
0 → 100644
| 1 | +package cn.iocoder.yudao.adminserver.framework.datapermission.core.rule; | |
| 2 | + | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.core.util.StrUtil; | |
| 5 | +import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.DeptDataPermissionService; | |
| 6 | +import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.dto.DeptDataPermissionRespDTO; | |
| 7 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 8 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 9 | +import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule; | |
| 10 | +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | |
| 11 | +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; | |
| 12 | +import cn.iocoder.yudao.framework.security.core.LoginUser; | |
| 13 | +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | |
| 14 | +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; | |
| 15 | +import lombok.AllArgsConstructor; | |
| 16 | +import lombok.extern.slf4j.Slf4j; | |
| 17 | +import net.sf.jsqlparser.expression.Alias; | |
| 18 | +import net.sf.jsqlparser.expression.Expression; | |
| 19 | +import net.sf.jsqlparser.expression.LongValue; | |
| 20 | +import net.sf.jsqlparser.expression.operators.conditional.OrExpression; | |
| 21 | +import net.sf.jsqlparser.expression.operators.relational.EqualsTo; | |
| 22 | +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; | |
| 23 | +import net.sf.jsqlparser.expression.operators.relational.InExpression; | |
| 24 | + | |
| 25 | +import java.util.HashMap; | |
| 26 | +import java.util.HashSet; | |
| 27 | +import java.util.Map; | |
| 28 | +import java.util.Set; | |
| 29 | + | |
| 30 | +/** | |
| 31 | + * 基于部门的 {@link DataPermissionRule} 数据权限规则实现 | |
| 32 | + * | |
| 33 | + * 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。 | |
| 34 | + * | |
| 35 | + * 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改? | |
| 36 | + * 1. 一般情况下,dept_id 不进行修改,则会导致用户看到之前的数据。【yudao-admin-server 采用该方案】 | |
| 37 | + * 2. 部分情况下,希望该用户还是能看到之前的数据,则有两种方式解决:【需要你改造该 DeptDataPermissionRule 的实现代码】 | |
| 38 | + * 1)编写洗数据的脚本,将 dept_id 修改成新部门的编号;【建议】 | |
| 39 | + * 最终过滤条件是 WHERE dept_id = ? | |
| 40 | + * 2)洗数据的话,可能涉及的数据量较大,也可以采用 user_id 进行过滤的方式,此时需要获取到 dept_id 对应的所有 user_id 用户编号; | |
| 41 | + * 最终过滤条件是 WHERE user_id IN (?, ?, ? ...) | |
| 42 | + * 3)想要保证原 dept_id 和 user_id 都可以看的到,此时使用 dept_id 和 user_id 一起过滤; | |
| 43 | + * 最终过滤条件是 WHERE dept_id = ? OR user_id IN (?, ?, ? ...) | |
| 44 | + * | |
| 45 | + * @author 芋道源码 | |
| 46 | + */ | |
| 47 | +@AllArgsConstructor | |
| 48 | +@Slf4j | |
| 49 | +public class DeptDataPermissionRule implements DataPermissionRule { | |
| 50 | + | |
| 51 | + private static final String DEPT_COLUMN_NAME = "dept_id"; | |
| 52 | + private static final String USER_COLUMN_NAME = "user_id"; | |
| 53 | + | |
| 54 | + private final DeptDataPermissionService deptDataPermissionService; | |
| 55 | + | |
| 56 | + /** | |
| 57 | + * 基于部门的表字段配置 | |
| 58 | + * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。 | |
| 59 | + * | |
| 60 | + * key:表名 | |
| 61 | + * value:字段名 | |
| 62 | + */ | |
| 63 | + private final Map<String, String> DEPT_TABLE_CONFIG = new HashMap<>(); | |
| 64 | + /** | |
| 65 | + * 基于用户的表字段配置 | |
| 66 | + * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。 | |
| 67 | + * | |
| 68 | + * key:表名 | |
| 69 | + * value:字段名 | |
| 70 | + */ | |
| 71 | + private final Map<String, String> USER_TABLE_CONFIG = new HashMap<>(); | |
| 72 | + /** | |
| 73 | + * 所有表名,是 {@link #DEPT_TABLE_CONFIG} 和 {@link #USER_TABLE_CONFIG} 的合集 | |
| 74 | + */ | |
| 75 | + private final Set<String> TABLE_NAMES = new HashSet<>(); | |
| 76 | + | |
| 77 | + @Override | |
| 78 | + public Set<String> getTableNames() { | |
| 79 | + return TABLE_NAMES; | |
| 80 | + } | |
| 81 | + | |
| 82 | + @Override | |
| 83 | + public Expression getExpression(String tableName, Alias tableAlias) { | |
| 84 | + // 只有有登陆用户的情况下,才进行数据权限的处理 | |
| 85 | + LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); | |
| 86 | + if (loginUser == null) { | |
| 87 | + return null; | |
| 88 | + } | |
| 89 | + | |
| 90 | + // 获得数据权限 | |
| 91 | + DeptDataPermissionRespDTO deptDataPermission = deptDataPermissionService.getDeptDataPermission(loginUser); | |
| 92 | + if (deptDataPermission == null) { | |
| 93 | + log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser)); | |
| 94 | + return null; | |
| 95 | + } | |
| 96 | + | |
| 97 | + // 情况一,如果是 ALL 可查看全部,则无需拼接条件 | |
| 98 | + if (deptDataPermission.getAll()) { | |
| 99 | + return null; | |
| 100 | + } | |
| 101 | + | |
| 102 | + // 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限 | |
| 103 | + if (CollUtil.isEmpty(deptDataPermission.getDeptIds()) | |
| 104 | + && Boolean.FALSE.equals(deptDataPermission.getSelf())) { | |
| 105 | + return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空 | |
| 106 | + } | |
| 107 | + | |
| 108 | + // 情况三,拼接 Dept 和 User 的条件,最后组合 | |
| 109 | + Expression deptExpression = this.buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds()); | |
| 110 | + Expression userExpression = this.buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId()); | |
| 111 | + if (deptExpression == null && userExpression == null) { | |
| 112 | + log.error("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]", | |
| 113 | + JsonUtils.toJsonString(loginUser), tableName, tableAlias, JsonUtils.toJsonString(deptDataPermission)); | |
| 114 | + throw new NullPointerException(String.format("LoginUser(%d) tableName(%s) tableAlias(%s) 构建的条件为空", | |
| 115 | + loginUser.getId(), tableName, tableAlias.getName())); | |
| 116 | + } | |
| 117 | + if (deptExpression == null) { | |
| 118 | + return userExpression; | |
| 119 | + } | |
| 120 | + if (userExpression == null) { | |
| 121 | + return deptExpression; | |
| 122 | + } | |
| 123 | + // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE dept_id IN ? OR user_id = ? | |
| 124 | + return new OrExpression(deptExpression, userExpression); | |
| 125 | + } | |
| 126 | + | |
| 127 | + private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) { | |
| 128 | + // 如果不存在配置,则无需作为条件 | |
| 129 | + String columnName = DEPT_TABLE_CONFIG.get(tableName); | |
| 130 | + if (StrUtil.isEmpty(columnName)) { | |
| 131 | + return null; | |
| 132 | + } | |
| 133 | + // 拼接条件 | |
| 134 | + return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), | |
| 135 | + new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new))); | |
| 136 | + } | |
| 137 | + | |
| 138 | + private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) { | |
| 139 | + // 如果不查看自己,则无需作为条件 | |
| 140 | + if (Boolean.FALSE.equals(self)) { | |
| 141 | + return null; | |
| 142 | + } | |
| 143 | + String columnName = USER_TABLE_CONFIG.get(tableName); | |
| 144 | + if (StrUtil.isEmpty(columnName)) { | |
| 145 | + return null; | |
| 146 | + } | |
| 147 | + // 拼接条件 | |
| 148 | + return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId)); | |
| 149 | + } | |
| 150 | + | |
| 151 | + // ==================== 添加配置 ==================== | |
| 152 | + | |
| 153 | + public void addDeptTableConfig(Class<? extends BaseDO> entityClass) { | |
| 154 | + addDeptTableConfig(entityClass, DEPT_COLUMN_NAME); | |
| 155 | + } | |
| 156 | + | |
| 157 | + public void addDeptTableConfig(Class<? extends BaseDO> entityClass, String columnName) { | |
| 158 | + String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName(); | |
| 159 | + DEPT_TABLE_CONFIG.put(tableName, columnName); | |
| 160 | + TABLE_NAMES.add(tableName); | |
| 161 | + } | |
| 162 | + | |
| 163 | + public void addUserTableConfig(Class<? extends BaseDO> entityClass) { | |
| 164 | + addUserTableConfig(entityClass, DEPT_COLUMN_NAME); | |
| 165 | + } | |
| 166 | + | |
| 167 | + public void addUserTableConfig(Class<? extends BaseDO> entityClass, String columnName) { | |
| 168 | + String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName(); | |
| 169 | + USER_TABLE_CONFIG.put(tableName, columnName); | |
| 170 | + TABLE_NAMES.add(tableName); | |
| 171 | + } | |
| 172 | + | |
| 173 | +} | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/DeptDataPermissionService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.adminserver.framework.datapermission.core.service; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.dto.DeptDataPermissionRespDTO; | |
| 4 | +import cn.iocoder.yudao.framework.security.core.LoginUser; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * 基于部门的数据权限 Service 接口 | |
| 8 | + * | |
| 9 | + * @author 芋道源码 | |
| 10 | + */ | |
| 11 | +public interface DeptDataPermissionService { | |
| 12 | + | |
| 13 | + /** | |
| 14 | + * 获得登陆用户的部门数据权限 | |
| 15 | + * | |
| 16 | + * @param loginUser 登陆用户 | |
| 17 | + * @return 部门数据权限 | |
| 18 | + */ | |
| 19 | + DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser); | |
| 20 | + | |
| 21 | +} | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/dto/DeptDataPermissionRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.adminserver.framework.datapermission.core.service.dto; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +import java.util.ArrayList; | |
| 6 | +import java.util.HashSet; | |
| 7 | +import java.util.List; | |
| 8 | +import java.util.Set; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * 部门的数据权限 Response DTO | |
| 12 | + * | |
| 13 | + * @author 芋道源码 | |
| 14 | + */ | |
| 15 | +@Data | |
| 16 | +public class DeptDataPermissionRespDTO { | |
| 17 | + | |
| 18 | + /** | |
| 19 | + * 是否可查看全部数据 | |
| 20 | + */ | |
| 21 | + private Boolean all; | |
| 22 | + /** | |
| 23 | + * 是否可查看自己的数据 | |
| 24 | + */ | |
| 25 | + private Boolean self; | |
| 26 | + /** | |
| 27 | + * 可查看的部门编号数组 | |
| 28 | + */ | |
| 29 | + private Set<Long> deptIds; | |
| 30 | + | |
| 31 | + public DeptDataPermissionRespDTO() { | |
| 32 | + this.all = false; | |
| 33 | + this.self = false; | |
| 34 | + this.deptIds = new HashSet<>(); | |
| 35 | + } | |
| 36 | + | |
| 37 | +} | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/impl/DeptDataPermissionServiceImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.adminserver.framework.datapermission.core.service.impl; | |
| 2 | + | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.DeptDataPermissionService; | |
| 5 | +import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.dto.DeptDataPermissionRespDTO; | |
| 6 | +import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysDeptDO; | |
| 7 | +import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysRoleDO; | |
| 8 | +import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysDeptService; | |
| 9 | +import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService; | |
| 10 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 11 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 12 | +import cn.iocoder.yudao.framework.security.core.LoginUser; | |
| 13 | +import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum; | |
| 14 | +import lombok.RequiredArgsConstructor; | |
| 15 | +import lombok.extern.slf4j.Slf4j; | |
| 16 | + | |
| 17 | +import java.util.List; | |
| 18 | +import java.util.Objects; | |
| 19 | + | |
| 20 | +/** | |
| 21 | + * 基于部门的数据权限 Service 实现类 | |
| 22 | + * | |
| 23 | + * @author 芋道源码 | |
| 24 | + */ | |
| 25 | +@RequiredArgsConstructor | |
| 26 | +@Slf4j | |
| 27 | +public class DeptDataPermissionServiceImpl implements DeptDataPermissionService { | |
| 28 | + | |
| 29 | + /** | |
| 30 | + * LoginUser 的 Context 缓存 Key | |
| 31 | + */ | |
| 32 | + private static final String CONTEXT_KEY = DeptDataPermissionServiceImpl.class.getSimpleName(); | |
| 33 | + | |
| 34 | + private final SysRoleService roleService; | |
| 35 | + private final SysDeptService deptService; | |
| 36 | + | |
| 37 | + @Override | |
| 38 | + public DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser) { | |
| 39 | + // 判断是否 context 已经缓存 | |
| 40 | + DeptDataPermissionRespDTO result = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class); | |
| 41 | + if (result != null) { | |
| 42 | + return result; | |
| 43 | + } | |
| 44 | + | |
| 45 | + // 创建 DeptDataPermissionRespDTO 对象 | |
| 46 | + result = new DeptDataPermissionRespDTO(); | |
| 47 | + List<SysRoleDO> roles = roleService.getRolesFromCache(loginUser.getRoleIds()); | |
| 48 | + for (SysRoleDO role : roles) { | |
| 49 | + // 为空时,跳过 | |
| 50 | + if (role.getDataScope() == null) { | |
| 51 | + continue; | |
| 52 | + } | |
| 53 | + // 情况一,ALL | |
| 54 | + if (Objects.equals(role.getDataScope(), DataScopeEnum.ALL.getScope())) { | |
| 55 | + result.setAll(true); | |
| 56 | + continue; | |
| 57 | + } | |
| 58 | + // 情况二,DEPT_CUSTOM | |
| 59 | + if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_CUSTOM.getScope())) { | |
| 60 | + CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds()); | |
| 61 | + continue; | |
| 62 | + } | |
| 63 | + // 情况三,DEPT_ONLY | |
| 64 | + if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) { | |
| 65 | + CollectionUtils.addIfNotNull(result.getDeptIds(), loginUser.getDeptId()); | |
| 66 | + continue; | |
| 67 | + } | |
| 68 | + // 情况四,DEPT_DEPT_AND_CHILD | |
| 69 | + if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) { | |
| 70 | + List<SysDeptDO> depts = deptService.getDeptsByParentIdFromCache(loginUser.getDeptId(), true); | |
| 71 | + CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, SysDeptDO::getId)); | |
| 72 | + continue; | |
| 73 | + } | |
| 74 | + // 情况五,SELF | |
| 75 | + if (Objects.equals(role.getDataScope(), DataScopeEnum.SELF.getScope())) { | |
| 76 | + result.setSelf(true); | |
| 77 | + continue; | |
| 78 | + } | |
| 79 | + // 未知情况,error log 即可 | |
| 80 | + log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", loginUser.getId(), JsonUtils.toJsonString(result)); | |
| 81 | + } | |
| 82 | + | |
| 83 | + // 添加到缓存,并返回 | |
| 84 | + loginUser.setContext(CONTEXT_KEY, result); | |
| 85 | + return null; | |
| 86 | + } | |
| 87 | + | |
| 88 | +} | ... | ... |
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 { |
| 92 | 92 | throw new UsernameNotFoundException(username); |
| 93 | 93 | } |
| 94 | 94 | // 创建 LoginUser 对象 |
| 95 | - LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user); | |
| 96 | - loginUser.setPostIds(user.getPostIds()); | |
| 97 | - return loginUser; | |
| 95 | + return this.buildLoginUser(user); | |
| 98 | 96 | } |
| 99 | 97 | |
| 100 | 98 | @Override |
| ... | ... | @@ -107,9 +105,7 @@ public class SysAuthServiceImpl implements SysAuthService { |
| 107 | 105 | this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_MOCK, SysLoginResultEnum.SUCCESS); |
| 108 | 106 | |
| 109 | 107 | // 创建 LoginUser 对象 |
| 110 | - LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user); | |
| 111 | - loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表 | |
| 112 | - return loginUser; | |
| 108 | + return this.buildLoginUser(user); | |
| 113 | 109 | } |
| 114 | 110 | |
| 115 | 111 | @Override |
| ... | ... | @@ -117,10 +113,9 @@ public class SysAuthServiceImpl implements SysAuthService { |
| 117 | 113 | // 判断验证码是否正确 |
| 118 | 114 | this.verifyCaptcha(reqVO.getUsername(), reqVO.getUuid(), reqVO.getCode()); |
| 119 | 115 | |
| 120 | - // 使用账号密码,进行登录。 | |
| 116 | + // 使用账号密码,进行登录 | |
| 121 | 117 | LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); |
| 122 | - loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表 | |
| 123 | - loginUser.setGroups(this.getUserPosts(loginUser.getPostIds())); | |
| 118 | + | |
| 124 | 119 | // 缓存登陆用户到 Redis 中,返回 sessionId 编号 |
| 125 | 120 | return userSessionCoreService.createUserSession(loginUser, userIp, userAgent); |
| 126 | 121 | } |
| ... | ... | @@ -234,8 +229,7 @@ public class SysAuthServiceImpl implements SysAuthService { |
| 234 | 229 | this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_SOCIAL, SysLoginResultEnum.SUCCESS); |
| 235 | 230 | |
| 236 | 231 | // 创建 LoginUser 对象 |
| 237 | - LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user); | |
| 238 | - loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表 | |
| 232 | + LoginUser loginUser = this.buildLoginUser(user); | |
| 239 | 233 | |
| 240 | 234 | // 绑定社交用户(更新) |
| 241 | 235 | socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum); |
| ... | ... | @@ -252,7 +246,6 @@ public class SysAuthServiceImpl implements SysAuthService { |
| 252 | 246 | |
| 253 | 247 | // 使用账号密码,进行登录。 |
| 254 | 248 | LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); |
| 255 | - loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表 | |
| 256 | 249 | |
| 257 | 250 | // 绑定社交用户(新增) |
| 258 | 251 | socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum); |
| ... | ... | @@ -305,15 +298,14 @@ public class SysAuthServiceImpl implements SysAuthService { |
| 305 | 298 | return null; |
| 306 | 299 | } |
| 307 | 300 | // 刷新 LoginUser 缓存 |
| 308 | - this.refreshLoginUserCache(token, loginUser); | |
| 309 | - return loginUser; | |
| 301 | + return this.refreshLoginUserCache(token, loginUser); | |
| 310 | 302 | } |
| 311 | 303 | |
| 312 | - private void refreshLoginUserCache(String token, LoginUser loginUser) { | |
| 304 | + private LoginUser refreshLoginUserCache(String token, LoginUser loginUser) { | |
| 313 | 305 | // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存 |
| 314 | 306 | if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() < |
| 315 | 307 | userSessionCoreService.getSessionTimeoutMillis() / 3) { |
| 316 | - return; | |
| 308 | + return loginUser; | |
| 317 | 309 | } |
| 318 | 310 | |
| 319 | 311 | // 重新加载 SysUserDO 信息 |
| ... | ... | @@ -323,9 +315,18 @@ public class SysAuthServiceImpl implements SysAuthService { |
| 323 | 315 | } |
| 324 | 316 | |
| 325 | 317 | // 刷新 LoginUser 缓存 |
| 318 | + LoginUser newLoginUser= this.buildLoginUser(user); | |
| 319 | + userSessionCoreService.refreshUserSession(token, newLoginUser); | |
| 320 | + return newLoginUser; | |
| 321 | + } | |
| 322 | + | |
| 323 | + private LoginUser buildLoginUser(SysUserDO user) { | |
| 324 | + LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user); | |
| 325 | + // 补全字段 | |
| 326 | 326 | loginUser.setDeptId(user.getDeptId()); |
| 327 | 327 | loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); |
| 328 | - userSessionCoreService.refreshUserSession(token, loginUser); | |
| 328 | + loginUser.setGroups(this.getUserPosts(user.getPostIds())); | |
| 329 | + return loginUser; | |
| 329 | 330 | } |
| 330 | 331 | |
| 331 | 332 | } | ... | ... |
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 { |
| 169 | 169 | |
| 170 | 170 | @Override |
| 171 | 171 | public List<SysDeptDO> getDeptsByParentIdFromCache(Long parentId, boolean recursive) { |
| 172 | - List<SysDeptDO> result = new ArrayList<>(); | |
| 172 | + if (parentId == null) { | |
| 173 | + return Collections.emptyList(); | |
| 174 | + } | |
| 175 | + List<SysDeptDO> result = new ArrayList<>(); // TODO 芋艿:待优化,新增缓存,避免每次遍历的计算 | |
| 173 | 176 | // 递归,简单粗暴 |
| 174 | - this.listDeptsByParentIdFromCache(result, parentId, | |
| 177 | + this.getDeptsByParentIdFromCache(result, parentId, | |
| 175 | 178 | recursive ? Integer.MAX_VALUE : 1, // 如果递归获取,则无限;否则,只递归 1 次 |
| 176 | 179 | parentDeptCache); |
| 177 | 180 | return result; |
| ... | ... | @@ -185,8 +188,8 @@ public class SysDeptServiceImpl implements SysDeptService { |
| 185 | 188 | * @param recursiveCount 递归次数 |
| 186 | 189 | * @param parentDeptMap 父部门 Map,使用缓存,避免变化 |
| 187 | 190 | */ |
| 188 | - private void listDeptsByParentIdFromCache(List<SysDeptDO> result, Long parentId, int recursiveCount, | |
| 189 | - Multimap<Long, SysDeptDO> parentDeptMap) { | |
| 191 | + private void getDeptsByParentIdFromCache(List<SysDeptDO> result, Long parentId, int recursiveCount, | |
| 192 | + Multimap<Long, SysDeptDO> parentDeptMap) { | |
| 190 | 193 | // 递归次数为 0,结束! |
| 191 | 194 | if (recursiveCount == 0) { |
| 192 | 195 | return; |
| ... | ... | @@ -198,7 +201,7 @@ public class SysDeptServiceImpl implements SysDeptService { |
| 198 | 201 | } |
| 199 | 202 | result.addAll(depts); |
| 200 | 203 | // 继续递归 |
| 201 | - depts.forEach(dept -> listDeptsByParentIdFromCache(result, dept.getId(), | |
| 204 | + depts.forEach(dept -> getDeptsByParentIdFromCache(result, dept.getId(), | |
| 202 | 205 | recursiveCount - 1, parentDeptMap)); |
| 203 | 206 | } |
| 204 | 207 | ... | ... |
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 |
| 18 | 18 | import cn.iocoder.yudao.adminserver.modules.system.mq.producer.permission.SysRoleProducer; |
| 19 | 19 | import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService; |
| 20 | 20 | import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService; |
| 21 | +import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum; | |
| 21 | 22 | import com.google.common.annotations.VisibleForTesting; |
| 22 | 23 | import com.google.common.collect.ImmutableMap; |
| 23 | 24 | import lombok.extern.slf4j.Slf4j; |
| ... | ... | @@ -127,6 +128,7 @@ public class SysRoleServiceImpl implements SysRoleService { |
| 127 | 128 | SysRoleDO role = SysRoleConvert.INSTANCE.convert(reqVO); |
| 128 | 129 | role.setType(SysRoleTypeEnum.CUSTOM.getType()); |
| 129 | 130 | role.setStatus(CommonStatusEnum.ENABLE.getStatus()); |
| 131 | + role.setDataScope(DataScopeEnum.ALL.getScope()); // 默认可查看所有数据。原因是,可能一些项目不需要项目权限 | |
| 130 | 132 | roleMapper.insert(role); |
| 131 | 133 | // 发送刷新消息 |
| 132 | 134 | roleProducer.sendRoleRefreshMessage(); | ... | ... |
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 { |
| 89 | 89 | LoginUser loginUser = (LoginUser) authService.loadUserByUsername(username); |
| 90 | 90 | // 校验 |
| 91 | 91 | AssertUtils.assertPojoEquals(user, loginUser, "updateTime"); |
| 92 | - assertNull(loginUser.getRoleIds()); // 此时不会加载角色,所以是空的 | |
| 93 | 92 | } |
| 94 | 93 | |
| 95 | 94 | @Test | ... | ... |
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 { |
| 133 | 133 | |
| 134 | 134 | //调用 |
| 135 | 135 | Set<Long> deptIdSet = Arrays.asList(1L, 2L, 3L, 4L, 5L).stream().collect(Collectors.toSet()); |
| 136 | - sysRoleService.updateRoleDataScope(roleId, DataScopeEnum.DEPT_CUSTOM.getScore(), deptIdSet); | |
| 136 | + sysRoleService.updateRoleDataScope(roleId, DataScopeEnum.DEPT_CUSTOM.getScope(), deptIdSet); | |
| 137 | 137 | |
| 138 | 138 | //断言 |
| 139 | 139 | SysRoleDO newRoleDO = roleMapper.selectById(roleId); |
| 140 | - assertEquals(DataScopeEnum.DEPT_CUSTOM.getScore(), newRoleDO.getDataScope()); | |
| 140 | + assertEquals(DataScopeEnum.DEPT_CUSTOM.getScope(), newRoleDO.getDataScope()); | |
| 141 | 141 | |
| 142 | 142 | Set<Long> newDeptIdSet = newRoleDO.getDataScopeDeptIds(); |
| 143 | 143 | assertTrue(deptIdSet.size() == newDeptIdSet.size()); |
| ... | ... | @@ -242,7 +242,7 @@ public class SysRoleServiceTest extends BaseDbUnitTest { |
| 242 | 242 | o.setCode("code"); |
| 243 | 243 | o.setType(SysRoleTypeEnum.CUSTOM.getType()); |
| 244 | 244 | o.setStatus(1); |
| 245 | - o.setDataScope(DataScopeEnum.ALL.getScore()); | |
| 245 | + o.setDataScope(DataScopeEnum.ALL.getScope()); | |
| 246 | 246 | }); |
| 247 | 247 | roleMapper.insert(roleDO); |
| 248 | 248 | |
| ... | ... | @@ -293,7 +293,7 @@ public class SysRoleServiceTest extends BaseDbUnitTest { |
| 293 | 293 | o.setName(name); |
| 294 | 294 | o.setType(typeEnum.getType()); |
| 295 | 295 | o.setStatus(status); |
| 296 | - o.setDataScope(scopeEnum.getScore()); | |
| 296 | + o.setDataScope(scopeEnum.getScope()); | |
| 297 | 297 | o.setCode(code); |
| 298 | 298 | }); |
| 299 | 299 | return roleDO; | ... | ... |
yudao-dependencies/pom.xml
| ... | ... | @@ -370,6 +370,12 @@ |
| 370 | 370 | </dependency> |
| 371 | 371 | |
| 372 | 372 | <dependency> |
| 373 | + <groupId>cn.iocoder.boot</groupId> | |
| 374 | + <artifactId>yudao-spring-boot-starter-data-permission</artifactId> | |
| 375 | + <version>${revision}</version> | |
| 376 | + </dependency> | |
| 377 | + | |
| 378 | + <dependency> | |
| 373 | 379 | <groupId>org.projectlombok</groupId> |
| 374 | 380 | <artifactId>lombok</artifactId> |
| 375 | 381 | <version>${lombok.version}</version> | ... | ... |
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 |
| 5 | 5 | import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule; |
| 6 | 6 | import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory; |
| 7 | 7 | import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl; |
| 8 | +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; | |
| 9 | +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | |
| 8 | 10 | import org.springframework.context.annotation.Bean; |
| 9 | 11 | import org.springframework.context.annotation.Configuration; |
| 10 | 12 | |
| 11 | 13 | import java.util.List; |
| 12 | 14 | |
| 15 | +/** | |
| 16 | + * 数据全新啊的自动配置类 | |
| 17 | + * | |
| 18 | + * @author 芋道源码 | |
| 19 | + */ | |
| 13 | 20 | @Configuration |
| 14 | 21 | public class DataPermissionAutoConfiguration { |
| 15 | 22 | |
| ... | ... | @@ -19,9 +26,15 @@ public class DataPermissionAutoConfiguration { |
| 19 | 26 | } |
| 20 | 27 | |
| 21 | 28 | @Bean |
| 22 | - public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(List<DataPermissionRule> rules) { | |
| 29 | + public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(MybatisPlusInterceptor interceptor, | |
| 30 | + List<DataPermissionRule> rules) { | |
| 31 | + // 创建 DataPermissionDatabaseInterceptor 拦截器 | |
| 23 | 32 | DataPermissionRuleFactory ruleFactory = dataPermissionRuleFactory(rules); |
| 24 | - return new DataPermissionDatabaseInterceptor(ruleFactory); | |
| 33 | + DataPermissionDatabaseInterceptor inner = new DataPermissionDatabaseInterceptor(ruleFactory); | |
| 34 | + // 添加到 interceptor 中 | |
| 35 | + // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定 | |
| 36 | + MyBatisUtils.addInterceptor(interceptor, inner, 0); | |
| 37 | + return inner; | |
| 25 | 38 | } |
| 26 | 39 | |
| 27 | 40 | @Bean | ... | ... |
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java
| 1 | 1 | package cn.iocoder.yudao.framework.security.core; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.map.MapUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; |
| 5 | 6 | import com.fasterxml.jackson.annotation.JsonIgnore; |
| ... | ... | @@ -29,10 +30,6 @@ public class LoginUser implements UserDetails { |
| 29 | 30 | */ |
| 30 | 31 | private Integer userType; |
| 31 | 32 | /** |
| 32 | - * 角色编号数组 | |
| 33 | - */ | |
| 34 | - private Set<Long> roleIds; | |
| 35 | - /** | |
| 36 | 33 | * 最后更新时间 |
| 37 | 34 | */ |
| 38 | 35 | private Date updateTime; |
| ... | ... | @@ -56,7 +53,10 @@ public class LoginUser implements UserDetails { |
| 56 | 53 | |
| 57 | 54 | // ========== UserTypeEnum.ADMIN 独有字段 ========== |
| 58 | 55 | // TODO 芋艿:可以通过定义一个 Map<String, String> exts 的方式,去除管理员的字段。不过这样会导致系统比较复杂,所以暂时不去掉先; |
| 59 | - | |
| 56 | + /** | |
| 57 | + * 角色编号数组 | |
| 58 | + */ | |
| 59 | + private Set<Long> roleIds; | |
| 60 | 60 | /** |
| 61 | 61 | * 部门编号 |
| 62 | 62 | */ |
| ... | ... | @@ -71,6 +71,15 @@ public class LoginUser implements UserDetails { |
| 71 | 71 | // TODO jason:这个字段,改成 postCodes 明确更好哈 |
| 72 | 72 | private List<String> groups; |
| 73 | 73 | |
| 74 | + // ========== 上下文 ========== | |
| 75 | + /** | |
| 76 | + * 上下文字段,不进行持久化 | |
| 77 | + * | |
| 78 | + * 1. 用于基于 LoginUser 维度的临时缓存 | |
| 79 | + */ | |
| 80 | + @JsonIgnore | |
| 81 | + private Map<String, Object> context; | |
| 82 | + | |
| 74 | 83 | @Override |
| 75 | 84 | @JsonIgnore// 避免序列化 |
| 76 | 85 | public String getPassword() { |
| ... | ... | @@ -115,4 +124,17 @@ public class LoginUser implements UserDetails { |
| 115 | 124 | return true; // 返回 true,不依赖 Spring Security 判断 |
| 116 | 125 | } |
| 117 | 126 | |
| 127 | + // ========== 上下文 ========== | |
| 128 | + | |
| 129 | + public void setContext(String key, Object value) { | |
| 130 | + if (context == null) { | |
| 131 | + context = new HashMap<>(); | |
| 132 | + } | |
| 133 | + context.put(key, value); | |
| 134 | + } | |
| 135 | + | |
| 136 | + public <T> T getContext(String key, Class<T> type) { | |
| 137 | + return MapUtil.get(context, key, type); | |
| 138 | + } | |
| 139 | + | |
| 118 | 140 | } | ... | ... |
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; |
| 15 | 15 | public enum DataScopeEnum { |
| 16 | 16 | |
| 17 | 17 | ALL(1), // 全部数据权限 |
| 18 | + | |
| 18 | 19 | DEPT_CUSTOM(2), // 指定部门数据权限 |
| 19 | 20 | DEPT_ONLY(3), // 部门数据权限 |
| 20 | 21 | DEPT_AND_CHILD(4), // 部门及以下数据权限 |
| 21 | - DEPT_SELF(5); // 仅本人数据权限 | |
| 22 | + | |
| 23 | + SELF(5); // 仅本人数据权限 | |
| 22 | 24 | |
| 23 | 25 | /** |
| 24 | 26 | * 范围 |
| 25 | 27 | */ |
| 26 | - private final Integer score; | |
| 28 | + private final Integer scope; | |
| 27 | 29 | |
| 28 | 30 | } | ... | ... |