Commit 986cb72421c35b514709e2cafebc22a412a22646

Authored by YunaiV
1 parent b0855cc6

基于部门的数据权限

Showing 15 changed files with 409 additions and 36 deletions
yudao-admin-server/pom.xml
@@ -123,6 +123,11 @@ @@ -123,6 +123,11 @@
123 </dependency> 123 </dependency>
124 124
125 <dependency> 125 <dependency>
  126 + <groupId>cn.iocoder.boot</groupId>
  127 + <artifactId>yudao-spring-boot-starter-data-permission</artifactId>
  128 + </dependency>
  129 +
  130 + <dependency>
126 <groupId>org.apache.velocity</groupId> 131 <groupId>org.apache.velocity</groupId>
127 <artifactId>velocity-engine-core</artifactId> 132 <artifactId>velocity-engine-core</artifactId>
128 </dependency> 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,9 +92,7 @@ public class SysAuthServiceImpl implements SysAuthService {
92 throw new UsernameNotFoundException(username); 92 throw new UsernameNotFoundException(username);
93 } 93 }
94 // 创建 LoginUser 对象 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 @Override 98 @Override
@@ -107,9 +105,7 @@ public class SysAuthServiceImpl implements SysAuthService { @@ -107,9 +105,7 @@ public class SysAuthServiceImpl implements SysAuthService {
107 this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_MOCK, SysLoginResultEnum.SUCCESS); 105 this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_MOCK, SysLoginResultEnum.SUCCESS);
108 106
109 // 创建 LoginUser 对象 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 @Override 111 @Override
@@ -117,10 +113,9 @@ public class SysAuthServiceImpl implements SysAuthService { @@ -117,10 +113,9 @@ public class SysAuthServiceImpl implements SysAuthService {
117 // 判断验证码是否正确 113 // 判断验证码是否正确
118 this.verifyCaptcha(reqVO.getUsername(), reqVO.getUuid(), reqVO.getCode()); 114 this.verifyCaptcha(reqVO.getUsername(), reqVO.getUuid(), reqVO.getCode());
119 115
120 - // 使用账号密码,进行登录。 116 + // 使用账号密码,进行登录
121 LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); 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 // 缓存登陆用户到 Redis 中,返回 sessionId 编号 119 // 缓存登陆用户到 Redis 中,返回 sessionId 编号
125 return userSessionCoreService.createUserSession(loginUser, userIp, userAgent); 120 return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
126 } 121 }
@@ -234,8 +229,7 @@ public class SysAuthServiceImpl implements SysAuthService { @@ -234,8 +229,7 @@ public class SysAuthServiceImpl implements SysAuthService {
234 this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_SOCIAL, SysLoginResultEnum.SUCCESS); 229 this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_SOCIAL, SysLoginResultEnum.SUCCESS);
235 230
236 // 创建 LoginUser 对象 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 socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum); 235 socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum);
@@ -252,7 +246,6 @@ public class SysAuthServiceImpl implements SysAuthService { @@ -252,7 +246,6 @@ public class SysAuthServiceImpl implements SysAuthService {
252 246
253 // 使用账号密码,进行登录。 247 // 使用账号密码,进行登录。
254 LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); 248 LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
255 - loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表  
256 249
257 // 绑定社交用户(新增) 250 // 绑定社交用户(新增)
258 socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum); 251 socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum);
@@ -305,15 +298,14 @@ public class SysAuthServiceImpl implements SysAuthService { @@ -305,15 +298,14 @@ public class SysAuthServiceImpl implements SysAuthService {
305 return null; 298 return null;
306 } 299 }
307 // 刷新 LoginUser 缓存 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 // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存 305 // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存
314 if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() < 306 if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() <
315 userSessionCoreService.getSessionTimeoutMillis() / 3) { 307 userSessionCoreService.getSessionTimeoutMillis() / 3) {
316 - return; 308 + return loginUser;
317 } 309 }
318 310
319 // 重新加载 SysUserDO 信息 311 // 重新加载 SysUserDO 信息
@@ -323,9 +315,18 @@ public class SysAuthServiceImpl implements SysAuthService { @@ -323,9 +315,18 @@ public class SysAuthServiceImpl implements SysAuthService {
323 } 315 }
324 316
325 // 刷新 LoginUser 缓存 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 loginUser.setDeptId(user.getDeptId()); 326 loginUser.setDeptId(user.getDeptId());
327 loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); 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,9 +169,12 @@ public class SysDeptServiceImpl implements SysDeptService {
169 169
170 @Override 170 @Override
171 public List<SysDeptDO> getDeptsByParentIdFromCache(Long parentId, boolean recursive) { 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 recursive ? Integer.MAX_VALUE : 1, // 如果递归获取,则无限;否则,只递归 1 次 178 recursive ? Integer.MAX_VALUE : 1, // 如果递归获取,则无限;否则,只递归 1 次
176 parentDeptCache); 179 parentDeptCache);
177 return result; 180 return result;
@@ -185,8 +188,8 @@ public class SysDeptServiceImpl implements SysDeptService { @@ -185,8 +188,8 @@ public class SysDeptServiceImpl implements SysDeptService {
185 * @param recursiveCount 递归次数 188 * @param recursiveCount 递归次数
186 * @param parentDeptMap 父部门 Map,使用缓存,避免变化 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 // 递归次数为 0,结束! 193 // 递归次数为 0,结束!
191 if (recursiveCount == 0) { 194 if (recursiveCount == 0) {
192 return; 195 return;
@@ -198,7 +201,7 @@ public class SysDeptServiceImpl implements SysDeptService { @@ -198,7 +201,7 @@ public class SysDeptServiceImpl implements SysDeptService {
198 } 201 }
199 result.addAll(depts); 202 result.addAll(depts);
200 // 继续递归 203 // 继续递归
201 - depts.forEach(dept -> listDeptsByParentIdFromCache(result, dept.getId(), 204 + depts.forEach(dept -> getDeptsByParentIdFromCache(result, dept.getId(),
202 recursiveCount - 1, parentDeptMap)); 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,6 +18,7 @@ import cn.iocoder.yudao.adminserver.modules.system.enums.permission.SysRoleTypeE
18 import cn.iocoder.yudao.adminserver.modules.system.mq.producer.permission.SysRoleProducer; 18 import cn.iocoder.yudao.adminserver.modules.system.mq.producer.permission.SysRoleProducer;
19 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService; 19 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
20 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService; 20 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService;
  21 +import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum;
21 import com.google.common.annotations.VisibleForTesting; 22 import com.google.common.annotations.VisibleForTesting;
22 import com.google.common.collect.ImmutableMap; 23 import com.google.common.collect.ImmutableMap;
23 import lombok.extern.slf4j.Slf4j; 24 import lombok.extern.slf4j.Slf4j;
@@ -127,6 +128,7 @@ public class SysRoleServiceImpl implements SysRoleService { @@ -127,6 +128,7 @@ public class SysRoleServiceImpl implements SysRoleService {
127 SysRoleDO role = SysRoleConvert.INSTANCE.convert(reqVO); 128 SysRoleDO role = SysRoleConvert.INSTANCE.convert(reqVO);
128 role.setType(SysRoleTypeEnum.CUSTOM.getType()); 129 role.setType(SysRoleTypeEnum.CUSTOM.getType());
129 role.setStatus(CommonStatusEnum.ENABLE.getStatus()); 130 role.setStatus(CommonStatusEnum.ENABLE.getStatus());
  131 + role.setDataScope(DataScopeEnum.ALL.getScope()); // 默认可查看所有数据。原因是,可能一些项目不需要项目权限
130 roleMapper.insert(role); 132 roleMapper.insert(role);
131 // 发送刷新消息 133 // 发送刷新消息
132 roleProducer.sendRoleRefreshMessage(); 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,7 +89,6 @@ public class SysAuthServiceImplTest extends BaseDbUnitTest {
89 LoginUser loginUser = (LoginUser) authService.loadUserByUsername(username); 89 LoginUser loginUser = (LoginUser) authService.loadUserByUsername(username);
90 // 校验 90 // 校验
91 AssertUtils.assertPojoEquals(user, loginUser, "updateTime"); 91 AssertUtils.assertPojoEquals(user, loginUser, "updateTime");
92 - assertNull(loginUser.getRoleIds()); // 此时不会加载角色,所以是空的  
93 } 92 }
94 93
95 @Test 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,11 +133,11 @@ public class SysRoleServiceTest extends BaseDbUnitTest {
133 133
134 //调用 134 //调用
135 Set<Long> deptIdSet = Arrays.asList(1L, 2L, 3L, 4L, 5L).stream().collect(Collectors.toSet()); 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 SysRoleDO newRoleDO = roleMapper.selectById(roleId); 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 Set<Long> newDeptIdSet = newRoleDO.getDataScopeDeptIds(); 142 Set<Long> newDeptIdSet = newRoleDO.getDataScopeDeptIds();
143 assertTrue(deptIdSet.size() == newDeptIdSet.size()); 143 assertTrue(deptIdSet.size() == newDeptIdSet.size());
@@ -242,7 +242,7 @@ public class SysRoleServiceTest extends BaseDbUnitTest { @@ -242,7 +242,7 @@ public class SysRoleServiceTest extends BaseDbUnitTest {
242 o.setCode("code"); 242 o.setCode("code");
243 o.setType(SysRoleTypeEnum.CUSTOM.getType()); 243 o.setType(SysRoleTypeEnum.CUSTOM.getType());
244 o.setStatus(1); 244 o.setStatus(1);
245 - o.setDataScope(DataScopeEnum.ALL.getScore()); 245 + o.setDataScope(DataScopeEnum.ALL.getScope());
246 }); 246 });
247 roleMapper.insert(roleDO); 247 roleMapper.insert(roleDO);
248 248
@@ -293,7 +293,7 @@ public class SysRoleServiceTest extends BaseDbUnitTest { @@ -293,7 +293,7 @@ public class SysRoleServiceTest extends BaseDbUnitTest {
293 o.setName(name); 293 o.setName(name);
294 o.setType(typeEnum.getType()); 294 o.setType(typeEnum.getType());
295 o.setStatus(status); 295 o.setStatus(status);
296 - o.setDataScope(scopeEnum.getScore()); 296 + o.setDataScope(scopeEnum.getScope());
297 o.setCode(code); 297 o.setCode(code);
298 }); 298 });
299 return roleDO; 299 return roleDO;
yudao-dependencies/pom.xml
@@ -370,6 +370,12 @@ @@ -370,6 +370,12 @@
370 </dependency> 370 </dependency>
371 371
372 <dependency> 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 <groupId>org.projectlombok</groupId> 379 <groupId>org.projectlombok</groupId>
374 <artifactId>lombok</artifactId> 380 <artifactId>lombok</artifactId>
375 <version>${lombok.version}</version> 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,11 +5,18 @@ import cn.iocoder.yudao.framework.datapermission.core.db.DataPermissionDatabaseI
5 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule; 5 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
6 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory; 6 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
7 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl; 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 import org.springframework.context.annotation.Bean; 10 import org.springframework.context.annotation.Bean;
9 import org.springframework.context.annotation.Configuration; 11 import org.springframework.context.annotation.Configuration;
10 12
11 import java.util.List; 13 import java.util.List;
12 14
  15 +/**
  16 + * 数据全新啊的自动配置类
  17 + *
  18 + * @author 芋道源码
  19 + */
13 @Configuration 20 @Configuration
14 public class DataPermissionAutoConfiguration { 21 public class DataPermissionAutoConfiguration {
15 22
@@ -19,9 +26,15 @@ public class DataPermissionAutoConfiguration { @@ -19,9 +26,15 @@ public class DataPermissionAutoConfiguration {
19 } 26 }
20 27
21 @Bean 28 @Bean
22 - public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(List<DataPermissionRule> rules) { 29 + public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(MybatisPlusInterceptor interceptor,
  30 + List<DataPermissionRule> rules) {
  31 + // 创建 DataPermissionDatabaseInterceptor 拦截器
23 DataPermissionRuleFactory ruleFactory = dataPermissionRuleFactory(rules); 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 @Bean 40 @Bean
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java
1 package cn.iocoder.yudao.framework.security.core; 1 package cn.iocoder.yudao.framework.security.core;
2 2
  3 +import cn.hutool.core.map.MapUtil;
3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 5 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 import com.fasterxml.jackson.annotation.JsonIgnore; 6 import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -29,10 +30,6 @@ public class LoginUser implements UserDetails { @@ -29,10 +30,6 @@ public class LoginUser implements UserDetails {
29 */ 30 */
30 private Integer userType; 31 private Integer userType;
31 /** 32 /**
32 - * 角色编号数组  
33 - */  
34 - private Set<Long> roleIds;  
35 - /**  
36 * 最后更新时间 33 * 最后更新时间
37 */ 34 */
38 private Date updateTime; 35 private Date updateTime;
@@ -56,7 +53,10 @@ public class LoginUser implements UserDetails { @@ -56,7 +53,10 @@ public class LoginUser implements UserDetails {
56 53
57 // ========== UserTypeEnum.ADMIN 独有字段 ========== 54 // ========== UserTypeEnum.ADMIN 独有字段 ==========
58 // TODO 芋艿:可以通过定义一个 Map<String, String> exts 的方式,去除管理员的字段。不过这样会导致系统比较复杂,所以暂时不去掉先; 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,6 +71,15 @@ public class LoginUser implements UserDetails {
71 // TODO jason:这个字段,改成 postCodes 明确更好哈 71 // TODO jason:这个字段,改成 postCodes 明确更好哈
72 private List<String> groups; 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 @Override 83 @Override
75 @JsonIgnore// 避免序列化 84 @JsonIgnore// 避免序列化
76 public String getPassword() { 85 public String getPassword() {
@@ -115,4 +124,17 @@ public class LoginUser implements UserDetails { @@ -115,4 +124,17 @@ public class LoginUser implements UserDetails {
115 return true; // 返回 true,不依赖 Spring Security 判断 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,14 +15,16 @@ import lombok.Getter;
15 public enum DataScopeEnum { 15 public enum DataScopeEnum {
16 16
17 ALL(1), // 全部数据权限 17 ALL(1), // 全部数据权限
  18 +
18 DEPT_CUSTOM(2), // 指定部门数据权限 19 DEPT_CUSTOM(2), // 指定部门数据权限
19 DEPT_ONLY(3), // 部门数据权限 20 DEPT_ONLY(3), // 部门数据权限
20 DEPT_AND_CHILD(4), // 部门及以下数据权限 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 }
更新日志.md
@@ -29,6 +29,7 @@ @@ -29,6 +29,7 @@
29 * 【新增】多租户,支持 Web、Security、Job、MQ、Async、DB、Redis 组件 29 * 【新增】多租户,支持 Web、Security、Job、MQ、Async、DB、Redis 组件
30 * 【新增】数据权限,内置基于部门过滤的规则 30 * 【新增】数据权限,内置基于部门过滤的规则
31 * 【新增】用户前台的昵称、头像的修改 31 * 【新增】用户前台的昵称、头像的修改
  32 +* 【优化】管理后台的登陆成功后,LoginUser 使用统一方法补全信息
32 33
33 ### 🐞 Bug Fixes 34 ### 🐞 Bug Fixes
34 35