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 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 }
... ...
更新日志.md
... ... @@ -29,6 +29,7 @@
29 29 * 【新增】多租户,支持 Web、Security、Job、MQ、Async、DB、Redis 组件
30 30 * 【新增】数据权限,内置基于部门过滤的规则
31 31 * 【新增】用户前台的昵称、头像的修改
  32 +* 【优化】管理后台的登陆成功后,LoginUser 使用统一方法补全信息
32 33  
33 34 ### 🐞 Bug Fixes
34 35  
... ...