Browse Source

基于部门的数据权限

YunaiV 3 years ago
parent
commit
986cb72421
15 changed files with 409 additions and 36 deletions
  1. 5 0
      yudao-admin-server/pom.xml
  2. 173 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/rule/DeptDataPermissionRule.java
  3. 21 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/DeptDataPermissionService.java
  4. 37 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/dto/DeptDataPermissionRespDTO.java
  5. 88 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/impl/DeptDataPermissionServiceImpl.java
  6. 18 17
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java
  7. 8 5
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/dept/impl/SysDeptServiceImpl.java
  8. 2 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/permission/impl/SysRoleServiceImpl.java
  9. 0 1
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java
  10. 4 4
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/permission/SysRoleServiceTest.java
  11. 6 0
      yudao-dependencies/pom.xml
  12. 15 2
      yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/DataPermissionAutoConfiguration.java
  13. 27 5
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java
  14. 4 2
      yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/enums/DataScopeEnum.java
  15. 1 0
      更新日志.md

+ 5 - 0
yudao-admin-server/pom.xml

@@ -122,6 +122,11 @@
             <artifactId>yudao-spring-boot-starter-tenant</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-data-permission</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.apache.velocity</groupId>
             <artifactId>velocity-engine-core</artifactId>

+ 173 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/rule/DeptDataPermissionRule.java

@@ -0,0 +1,173 @@
+package cn.iocoder.yudao.adminserver.framework.datapermission.core.rule;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.DeptDataPermissionService;
+import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.dto.DeptDataPermissionRespDTO;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
+import cn.iocoder.yudao.framework.security.core.LoginUser;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.expression.Alias;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.LongValue;
+import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
+import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
+import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
+import net.sf.jsqlparser.expression.operators.relational.InExpression;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 基于部门的 {@link DataPermissionRule} 数据权限规则实现
+ *
+ * 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。
+ *
+ * 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改?
+ * 1. 一般情况下,dept_id 不进行修改,则会导致用户看到之前的数据。【yudao-admin-server 采用该方案】
+ * 2. 部分情况下,希望该用户还是能看到之前的数据,则有两种方式解决:【需要你改造该 DeptDataPermissionRule 的实现代码】
+ *  1)编写洗数据的脚本,将 dept_id 修改成新部门的编号;【建议】
+ *      最终过滤条件是 WHERE dept_id = ?
+ *  2)洗数据的话,可能涉及的数据量较大,也可以采用 user_id 进行过滤的方式,此时需要获取到 dept_id 对应的所有 user_id 用户编号;
+ *      最终过滤条件是 WHERE user_id IN (?, ?, ? ...)
+ *  3)想要保证原 dept_id 和 user_id 都可以看的到,此时使用 dept_id 和 user_id 一起过滤;
+ *      最终过滤条件是 WHERE dept_id = ? OR user_id IN (?, ?, ? ...)
+ *
+ * @author 芋道源码
+ */
+@AllArgsConstructor
+@Slf4j
+public class DeptDataPermissionRule implements DataPermissionRule {
+
+    private static final String DEPT_COLUMN_NAME = "dept_id";
+    private static final String USER_COLUMN_NAME = "user_id";
+
+    private final DeptDataPermissionService deptDataPermissionService;
+
+    /**
+     * 基于部门的表字段配置
+     * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
+     *
+     * key:表名
+     * value:字段名
+     */
+    private final Map<String, String> DEPT_TABLE_CONFIG = new HashMap<>();
+    /**
+     * 基于用户的表字段配置
+     * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
+     *
+     * key:表名
+     * value:字段名
+     */
+    private final Map<String, String> USER_TABLE_CONFIG = new HashMap<>();
+    /**
+     * 所有表名,是 {@link #DEPT_TABLE_CONFIG} 和 {@link #USER_TABLE_CONFIG} 的合集
+     */
+    private final Set<String> TABLE_NAMES = new HashSet<>();
+
+    @Override
+    public Set<String> getTableNames() {
+        return TABLE_NAMES;
+    }
+
+    @Override
+    public Expression getExpression(String tableName, Alias tableAlias) {
+        // 只有有登陆用户的情况下,才进行数据权限的处理
+        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
+        if (loginUser == null) {
+            return null;
+        }
+
+        // 获得数据权限
+        DeptDataPermissionRespDTO deptDataPermission = deptDataPermissionService.getDeptDataPermission(loginUser);
+        if (deptDataPermission == null) {
+            log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
+            return null;
+        }
+
+        // 情况一,如果是 ALL 可查看全部,则无需拼接条件
+        if (deptDataPermission.getAll()) {
+            return null;
+        }
+
+        // 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限
+        if (CollUtil.isEmpty(deptDataPermission.getDeptIds())
+            && Boolean.FALSE.equals(deptDataPermission.getSelf())) {
+            return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空
+        }
+
+        // 情况三,拼接 Dept 和 User 的条件,最后组合
+        Expression deptExpression = this.buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());
+        Expression userExpression = this.buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
+        if (deptExpression == null && userExpression == null) {
+            log.error("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",
+                    JsonUtils.toJsonString(loginUser), tableName, tableAlias, JsonUtils.toJsonString(deptDataPermission));
+            throw new NullPointerException(String.format("LoginUser(%d) tableName(%s) tableAlias(%s) 构建的条件为空",
+                    loginUser.getId(), tableName, tableAlias.getName()));
+        }
+        if (deptExpression == null) {
+            return userExpression;
+        }
+        if (userExpression == null) {
+            return deptExpression;
+        }
+        // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE dept_id IN ? OR user_id = ?
+        return new OrExpression(deptExpression, userExpression);
+    }
+
+    private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {
+        // 如果不存在配置,则无需作为条件
+        String columnName = DEPT_TABLE_CONFIG.get(tableName);
+        if (StrUtil.isEmpty(columnName)) {
+            return null;
+        }
+        // 拼接条件
+        return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
+                new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)));
+    }
+
+    private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {
+        // 如果不查看自己,则无需作为条件
+        if (Boolean.FALSE.equals(self)) {
+            return null;
+        }
+        String columnName = USER_TABLE_CONFIG.get(tableName);
+        if (StrUtil.isEmpty(columnName)) {
+            return null;
+        }
+        // 拼接条件
+        return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId));
+    }
+
+    // ==================== 添加配置 ====================
+
+    public void addDeptTableConfig(Class<? extends BaseDO> entityClass) {
+        addDeptTableConfig(entityClass, DEPT_COLUMN_NAME);
+    }
+
+    public void addDeptTableConfig(Class<? extends BaseDO> entityClass, String columnName) {
+        String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
+        DEPT_TABLE_CONFIG.put(tableName, columnName);
+        TABLE_NAMES.add(tableName);
+    }
+
+    public void addUserTableConfig(Class<? extends BaseDO> entityClass) {
+        addUserTableConfig(entityClass, DEPT_COLUMN_NAME);
+    }
+
+    public void addUserTableConfig(Class<? extends BaseDO> entityClass, String columnName) {
+        String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
+        USER_TABLE_CONFIG.put(tableName, columnName);
+        TABLE_NAMES.add(tableName);
+    }
+
+}

+ 21 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/DeptDataPermissionService.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.adminserver.framework.datapermission.core.service;
+
+import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.dto.DeptDataPermissionRespDTO;
+import cn.iocoder.yudao.framework.security.core.LoginUser;
+
+/**
+ * 基于部门的数据权限 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface DeptDataPermissionService {
+
+    /**
+     * 获得登陆用户的部门数据权限
+     *
+     * @param loginUser 登陆用户
+     * @return 部门数据权限
+     */
+    DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser);
+
+}

+ 37 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/dto/DeptDataPermissionRespDTO.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.adminserver.framework.datapermission.core.service.dto;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 部门的数据权限 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class DeptDataPermissionRespDTO {
+
+    /**
+     * 是否可查看全部数据
+     */
+    private Boolean all;
+    /**
+     * 是否可查看自己的数据
+     */
+    private Boolean self;
+    /**
+     * 可查看的部门编号数组
+     */
+    private Set<Long> deptIds;
+
+    public DeptDataPermissionRespDTO() {
+        this.all = false;
+        this.self = false;
+        this.deptIds = new HashSet<>();
+    }
+
+}

+ 88 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/datapermission/core/service/impl/DeptDataPermissionServiceImpl.java

@@ -0,0 +1,88 @@
+package cn.iocoder.yudao.adminserver.framework.datapermission.core.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.DeptDataPermissionService;
+import cn.iocoder.yudao.adminserver.framework.datapermission.core.service.dto.DeptDataPermissionRespDTO;
+import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysDeptDO;
+import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysRoleDO;
+import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysDeptService;
+import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.security.core.LoginUser;
+import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 基于部门的数据权限 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Slf4j
+public class DeptDataPermissionServiceImpl implements DeptDataPermissionService {
+
+    /**
+     * LoginUser 的 Context 缓存 Key
+     */
+    private static final String CONTEXT_KEY = DeptDataPermissionServiceImpl.class.getSimpleName();
+
+    private final SysRoleService roleService;
+    private final SysDeptService deptService;
+
+    @Override
+    public DeptDataPermissionRespDTO getDeptDataPermission(LoginUser loginUser) {
+        // 判断是否 context 已经缓存
+        DeptDataPermissionRespDTO result = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
+        if (result != null) {
+            return result;
+        }
+
+        // 创建 DeptDataPermissionRespDTO 对象
+        result = new DeptDataPermissionRespDTO();
+        List<SysRoleDO> roles = roleService.getRolesFromCache(loginUser.getRoleIds());
+        for (SysRoleDO role : roles) {
+            // 为空时,跳过
+            if (role.getDataScope() == null) {
+                continue;
+            }
+            // 情况一,ALL
+            if (Objects.equals(role.getDataScope(), DataScopeEnum.ALL.getScope())) {
+                result.setAll(true);
+                continue;
+            }
+            // 情况二,DEPT_CUSTOM
+            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_CUSTOM.getScope())) {
+                CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
+                continue;
+            }
+            // 情况三,DEPT_ONLY
+            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
+                CollectionUtils.addIfNotNull(result.getDeptIds(), loginUser.getDeptId());
+                continue;
+            }
+            // 情况四,DEPT_DEPT_AND_CHILD
+            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
+                List<SysDeptDO> depts = deptService.getDeptsByParentIdFromCache(loginUser.getDeptId(), true);
+                CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, SysDeptDO::getId));
+                continue;
+            }
+            // 情况五,SELF
+            if (Objects.equals(role.getDataScope(), DataScopeEnum.SELF.getScope())) {
+                result.setSelf(true);
+                continue;
+            }
+            // 未知情况,error log 即可
+            log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", loginUser.getId(), JsonUtils.toJsonString(result));
+        }
+
+        // 添加到缓存,并返回
+        loginUser.setContext(CONTEXT_KEY, result);
+        return null;
+    }
+
+}

+ 18 - 17
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java

@@ -92,9 +92,7 @@ public class SysAuthServiceImpl implements SysAuthService {
             throw new UsernameNotFoundException(username);
         }
         // 创建 LoginUser 对象
-        LoginUser loginUser =  SysAuthConvert.INSTANCE.convert(user);
-        loginUser.setPostIds(user.getPostIds());
-        return loginUser;
+        return this.buildLoginUser(user);
     }
 
     @Override
@@ -107,9 +105,7 @@ public class SysAuthServiceImpl implements SysAuthService {
         this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_MOCK, SysLoginResultEnum.SUCCESS);
 
         // 创建 LoginUser 对象
-        LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
-        loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
-        return loginUser;
+        return this.buildLoginUser(user);
     }
 
     @Override
@@ -117,10 +113,9 @@ public class SysAuthServiceImpl implements SysAuthService {
         // 判断验证码是否正确
         this.verifyCaptcha(reqVO.getUsername(), reqVO.getUuid(), reqVO.getCode());
 
-        // 使用账号密码,进行登录
+        // 使用账号密码,进行登录
         LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
-        loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
-        loginUser.setGroups(this.getUserPosts(loginUser.getPostIds()));
+
         // 缓存登陆用户到 Redis 中,返回 sessionId 编号
         return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
     }
@@ -234,8 +229,7 @@ public class SysAuthServiceImpl implements SysAuthService {
         this.createLoginLog(user.getUsername(), SysLoginLogTypeEnum.LOGIN_SOCIAL, SysLoginResultEnum.SUCCESS);
 
         // 创建 LoginUser 对象
-        LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
-        loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
+        LoginUser loginUser = this.buildLoginUser(user);
 
         // 绑定社交用户(更新)
         socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum);
@@ -252,7 +246,6 @@ public class SysAuthServiceImpl implements SysAuthService {
 
         // 使用账号密码,进行登录。
         LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
-        loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
 
         // 绑定社交用户(新增)
         socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum);
@@ -305,15 +298,14 @@ public class SysAuthServiceImpl implements SysAuthService {
             return null;
         }
         // 刷新 LoginUser 缓存
-        this.refreshLoginUserCache(token, loginUser);
-        return loginUser;
+        return this.refreshLoginUserCache(token, loginUser);
     }
 
-    private void refreshLoginUserCache(String token, LoginUser loginUser) {
+    private LoginUser refreshLoginUserCache(String token, LoginUser loginUser) {
         // 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存
         if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() <
                 userSessionCoreService.getSessionTimeoutMillis() / 3) {
-            return;
+            return loginUser;
         }
 
         // 重新加载 SysUserDO 信息
@@ -323,9 +315,18 @@ public class SysAuthServiceImpl implements SysAuthService {
         }
 
         // 刷新 LoginUser 缓存
+        LoginUser newLoginUser= this.buildLoginUser(user);
+        userSessionCoreService.refreshUserSession(token, newLoginUser);
+        return newLoginUser;
+    }
+
+    private LoginUser buildLoginUser(SysUserDO user) {
+        LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
+        // 补全字段
         loginUser.setDeptId(user.getDeptId());
         loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId()));
-        userSessionCoreService.refreshUserSession(token, loginUser);
+        loginUser.setGroups(this.getUserPosts(user.getPostIds()));
+        return loginUser;
     }
 
 }

+ 8 - 5
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/dept/impl/SysDeptServiceImpl.java

@@ -169,9 +169,12 @@ public class SysDeptServiceImpl implements SysDeptService {
 
     @Override
     public List<SysDeptDO> getDeptsByParentIdFromCache(Long parentId, boolean recursive) {
-        List<SysDeptDO> result = new ArrayList<>();
+        if (parentId == null) {
+            return Collections.emptyList();
+        }
+        List<SysDeptDO> result = new ArrayList<>(); // TODO 芋艿:待优化,新增缓存,避免每次遍历的计算
         // 递归,简单粗暴
-        this.listDeptsByParentIdFromCache(result, parentId,
+        this.getDeptsByParentIdFromCache(result, parentId,
                 recursive ? Integer.MAX_VALUE : 1, // 如果递归获取,则无限;否则,只递归 1 次
                 parentDeptCache);
         return result;
@@ -185,8 +188,8 @@ public class SysDeptServiceImpl implements SysDeptService {
      * @param recursiveCount 递归次数
      * @param parentDeptMap 父部门 Map,使用缓存,避免变化
      */
-    private void listDeptsByParentIdFromCache(List<SysDeptDO> result, Long parentId, int recursiveCount,
-                                              Multimap<Long, SysDeptDO> parentDeptMap) {
+    private void getDeptsByParentIdFromCache(List<SysDeptDO> result, Long parentId, int recursiveCount,
+                                             Multimap<Long, SysDeptDO> parentDeptMap) {
         // 递归次数为 0,结束!
         if (recursiveCount == 0) {
             return;
@@ -198,7 +201,7 @@ public class SysDeptServiceImpl implements SysDeptService {
         }
         result.addAll(depts);
         // 继续递归
-        depts.forEach(dept -> listDeptsByParentIdFromCache(result, dept.getId(),
+        depts.forEach(dept -> getDeptsByParentIdFromCache(result, dept.getId(),
                 recursiveCount - 1, parentDeptMap));
     }
 

+ 2 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/permission/impl/SysRoleServiceImpl.java

@@ -18,6 +18,7 @@ import cn.iocoder.yudao.adminserver.modules.system.enums.permission.SysRoleTypeE
 import cn.iocoder.yudao.adminserver.modules.system.mq.producer.permission.SysRoleProducer;
 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService;
+import cn.iocoder.yudao.framework.security.core.enums.DataScopeEnum;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableMap;
 import lombok.extern.slf4j.Slf4j;
@@ -127,6 +128,7 @@ public class SysRoleServiceImpl implements SysRoleService {
         SysRoleDO role = SysRoleConvert.INSTANCE.convert(reqVO);
         role.setType(SysRoleTypeEnum.CUSTOM.getType());
         role.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        role.setDataScope(DataScopeEnum.ALL.getScope()); // 默认可查看所有数据。原因是,可能一些项目不需要项目权限
         roleMapper.insert(role);
         // 发送刷新消息
         roleProducer.sendRoleRefreshMessage();

+ 0 - 1
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java

@@ -89,7 +89,6 @@ public class SysAuthServiceImplTest extends BaseDbUnitTest {
         LoginUser loginUser = (LoginUser) authService.loadUserByUsername(username);
         // 校验
         AssertUtils.assertPojoEquals(user, loginUser, "updateTime");
-        assertNull(loginUser.getRoleIds()); // 此时不会加载角色,所以是空的
     }
 
     @Test

+ 4 - 4
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/permission/SysRoleServiceTest.java

@@ -133,11 +133,11 @@ public class SysRoleServiceTest extends BaseDbUnitTest {
 
         //调用
         Set<Long> deptIdSet = Arrays.asList(1L, 2L, 3L, 4L, 5L).stream().collect(Collectors.toSet());
-        sysRoleService.updateRoleDataScope(roleId, DataScopeEnum.DEPT_CUSTOM.getScore(), deptIdSet);
+        sysRoleService.updateRoleDataScope(roleId, DataScopeEnum.DEPT_CUSTOM.getScope(), deptIdSet);
 
         //断言
         SysRoleDO newRoleDO = roleMapper.selectById(roleId);
-        assertEquals(DataScopeEnum.DEPT_CUSTOM.getScore(), newRoleDO.getDataScope());
+        assertEquals(DataScopeEnum.DEPT_CUSTOM.getScope(), newRoleDO.getDataScope());
 
         Set<Long> newDeptIdSet = newRoleDO.getDataScopeDeptIds();
         assertTrue(deptIdSet.size() == newDeptIdSet.size());
@@ -242,7 +242,7 @@ public class SysRoleServiceTest extends BaseDbUnitTest {
             o.setCode("code");
             o.setType(SysRoleTypeEnum.CUSTOM.getType());
             o.setStatus(1);
-            o.setDataScope(DataScopeEnum.ALL.getScore());
+            o.setDataScope(DataScopeEnum.ALL.getScope());
         });
         roleMapper.insert(roleDO);
 
@@ -293,7 +293,7 @@ public class SysRoleServiceTest extends BaseDbUnitTest {
             o.setName(name);
             o.setType(typeEnum.getType());
             o.setStatus(status);
-            o.setDataScope(scopeEnum.getScore());
+            o.setDataScope(scopeEnum.getScope());
             o.setCode(code);
         });
         return roleDO;

+ 6 - 0
yudao-dependencies/pom.xml

@@ -369,6 +369,12 @@
                 <version>${revision}</version>
             </dependency>
 
+            <dependency>
+                <groupId>cn.iocoder.boot</groupId>
+                <artifactId>yudao-spring-boot-starter-data-permission</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
             <dependency>
                 <groupId>org.projectlombok</groupId>
                 <artifactId>lombok</artifactId>

+ 15 - 2
yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/DataPermissionAutoConfiguration.java

@@ -5,11 +5,18 @@ import cn.iocoder.yudao.framework.datapermission.core.db.DataPermissionDatabaseI
 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
 import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl;
+import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 import java.util.List;
 
+/**
+ * 数据全新啊的自动配置类
+ *
+ * @author 芋道源码
+ */
 @Configuration
 public class DataPermissionAutoConfiguration {
 
@@ -19,9 +26,15 @@ public class DataPermissionAutoConfiguration {
     }
 
     @Bean
-    public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(List<DataPermissionRule> rules) {
+    public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(MybatisPlusInterceptor interceptor,
+                                                                               List<DataPermissionRule> rules) {
+        // 创建 DataPermissionDatabaseInterceptor 拦截器
         DataPermissionRuleFactory ruleFactory = dataPermissionRuleFactory(rules);
-        return new DataPermissionDatabaseInterceptor(ruleFactory);
+        DataPermissionDatabaseInterceptor inner = new DataPermissionDatabaseInterceptor(ruleFactory);
+        // 添加到 interceptor 中
+        // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定
+        MyBatisUtils.addInterceptor(interceptor, inner, 0);
+        return inner;
     }
 
     @Bean

+ 27 - 5
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.framework.security.core;
 
+import cn.hutool.core.map.MapUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -28,10 +29,6 @@ public class LoginUser implements UserDetails {
      * 关联 {@link UserTypeEnum}
      */
     private Integer userType;
-    /**
-     * 角色编号数组
-     */
-    private Set<Long> roleIds;
     /**
      * 最后更新时间
      */
@@ -56,7 +53,10 @@ public class LoginUser implements UserDetails {
 
     // ========== UserTypeEnum.ADMIN 独有字段 ==========
     // TODO 芋艿:可以通过定义一个 Map<String, String> exts 的方式,去除管理员的字段。不过这样会导致系统比较复杂,所以暂时不去掉先;
-
+    /**
+     * 角色编号数组
+     */
+    private Set<Long> roleIds;
     /**
      * 部门编号
      */
@@ -71,6 +71,15 @@ public class LoginUser implements UserDetails {
     // TODO jason:这个字段,改成 postCodes 明确更好哈
     private List<String> groups;
 
+    // ========== 上下文 ==========
+    /**
+     * 上下文字段,不进行持久化
+     *
+     * 1. 用于基于 LoginUser 维度的临时缓存
+     */
+    @JsonIgnore
+    private Map<String, Object> context;
+
     @Override
     @JsonIgnore// 避免序列化
     public String getPassword() {
@@ -115,4 +124,17 @@ public class LoginUser implements UserDetails {
         return true;  // 返回 true,不依赖 Spring Security 判断
     }
 
+    // ========== 上下文 ==========
+
+    public void setContext(String key, Object value) {
+        if (context == null) {
+            context = new HashMap<>();
+        }
+        context.put(key, value);
+    }
+
+    public <T> T getContext(String key, Class<T> type) {
+        return MapUtil.get(context, key, type);
+    }
+
 }

+ 4 - 2
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/enums/DataScopeEnum.java

@@ -15,14 +15,16 @@ import lombok.Getter;
 public enum DataScopeEnum {
 
     ALL(1), // 全部数据权限
+
     DEPT_CUSTOM(2), // 指定部门数据权限
     DEPT_ONLY(3), // 部门数据权限
     DEPT_AND_CHILD(4), // 部门及以下数据权限
-    DEPT_SELF(5); // 仅本人数据权限
+
+    SELF(5); // 仅本人数据权限
 
     /**
      * 范围
      */
-    private final Integer score;
+    private final Integer scope;
 
 }

+ 1 - 0
更新日志.md

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