1. 修复回滚父子角色功能时错误的代码逻辑,补全单元测试用例

2. 新增支持切换后业务菜单查询需限定只查询该公司业务数据能力
This commit is contained in:
chenbowen
2025-07-10 19:05:58 +08:00
parent 92959efdc6
commit 7f0957d9c4
60 changed files with 1749 additions and 64 deletions

View File

@@ -0,0 +1,36 @@
package cn.iocoder.yudao.framework.datapermission.config;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
import cn.iocoder.yudao.framework.datapermission.core.rule.company.CompanyDataPermissionRule;
import cn.iocoder.yudao.framework.datapermission.core.rule.company.CompanyDataPermissionRuleCustomizer;
import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRule;
import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import java.util.List;
/**
* 基于部门的数据权限 AutoConfiguration
*
* @author 芋道源码
*/
@AutoConfiguration
@ConditionalOnClass(LoginUser.class)
@ConditionalOnBean(value = {CompanyDataPermissionRuleCustomizer.class})
public class YudaoCompanyDataPermissionAutoConfiguration {
@Bean
public CompanyDataPermissionRule companyDataPermissionRule(List<CompanyDataPermissionRuleCustomizer> customizers) {
// 创建 CompanyDataPermissionRule 对象
CompanyDataPermissionRule rule = new CompanyDataPermissionRule();
// 补全表配置
customizers.forEach(customizer -> customizer.customize(rule));
return rule;
}
}

View File

@@ -24,8 +24,7 @@ import java.util.List;
public class YudaoDeptDataPermissionAutoConfiguration {
@Bean
public DeptDataPermissionRule deptDataPermissionRule(PermissionCommonApi permissionApi,
List<DeptDataPermissionRuleCustomizer> customizers) {
public DeptDataPermissionRule deptDataPermissionRule(PermissionCommonApi permissionApi, List<DeptDataPermissionRuleCustomizer> customizers) {
// Cloud 专属逻辑:优先使用本地的 PermissionApi 实现类,而不是 Feign 调用
// 原因:在创建租户时,租户还没创建好,导致 Feign 调用获取数据权限时,报“租户不存在”的错误
try {

View File

@@ -0,0 +1,79 @@
package cn.iocoder.yudao.framework.datapermission.core.rule.company;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import java.util.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserCompanyId;
/**
* 基于公司类型部门的 {@link DataPermissionRule} 数据权限规则实现
* 注意,使用 CompanyDataPermissionRule 时,需要保证表中有 company_id 公司编号的字段,可自定义。
*
* @author chenbowen
*/
@AllArgsConstructor
@Slf4j
public class CompanyDataPermissionRule implements DataPermissionRule {
static final Expression EXPRESSION_NULL = new NullValue();
/**
* 基于部门的表字段配置
* 一般情况下,每个表的部门编号字段是 dept_id通过该配置自定义。
* key表名
* value字段名
*/
private final Map<String, String> companyColumns = new HashMap<>();
/**
* 所有表名,是 {@link #companyColumns} 的合集
*/
private final Set<String> TABLE_NAMES = new HashSet<>();
@Override
public Set<String> getTableNames() {
return TABLE_NAMES;
}
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
// 业务拼接 Company 的条件
if (getLoginUserCompanyId() == null) {
// 如果没有登录用户的公司编号,则不需要拼接条件
return null;
}
Expression companyExpression = buildCompanyExpression(tableName, tableAlias, Collections.singleton(getLoginUserCompanyId()));
return Objects.requireNonNullElse(companyExpression, EXPRESSION_NULL);
}
private Expression buildCompanyExpression(String tableName, Alias tableAlias, Set<Long> companyIds) {
// 如果不存在配置,则无需作为条件
String columnName = companyColumns.get(tableName);
if (StrUtil.isEmpty(columnName)) {
return null;
}
// 如果为空,则无条件
if (CollUtil.isEmpty(companyIds)) {
return null;
}
// 拼接条件
return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
// Parenthesis 的目的,是提供 (1,2,3) 的 () 左右括号
new ParenthesedExpressionList<>(new ExpressionList<>(CollectionUtils.convertList(companyIds, LongValue::new))));
}
public void addCompanyColumn(String tableName, String columnName) {
companyColumns.put(tableName, columnName);
TABLE_NAMES.add(tableName);
}
}

View File

@@ -0,0 +1,21 @@
package cn.iocoder.yudao.framework.datapermission.core.rule.company;
import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRule;
/**
* {@link DeptDataPermissionRule} 的自定义配置接口
*
* @author 芋道源码
*/
@FunctionalInterface
public interface CompanyDataPermissionRuleCustomizer {
/**
* 自定义该权限规则
* 1. 调用 {@link CompanyDataPermissionRule#addCompanyColumn(Class, String)} 方法,配置基于 dept_id 的过滤规则
*
* @param rule 权限规则
*/
void customize(CompanyDataPermissionRule rule);
}

View File

@@ -121,6 +121,11 @@ public class DeptDataPermissionRule implements DataPermissionRule {
// 添加到上下文中,避免重复计算
loginUser.setContext(CONTEXT_KEY, deptDataPermission);
}
// 如果不归属任何部门,且无可查询所有部门的权限,提示当前用户未关联任何部门
// if (CollUtil.isEmpty(deptDataPermission.getDeptIds()) && !deptDataPermission.getAll()) {
// log.error("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 未关联任何部门]", JsonUtils.toJsonString(loginUser), tableName, tableAlias.getName(), JsonUtils.toJsonString(deptDataPermission));
// throw new NullPointerException("当前登录用户未关联任何部门,请先联系管理员进行部门关联");
// }
// 如果开启了公司上下文,且缓存的公司编号不等于 CompanyContextHolder 的公司编号,则更新缓存
if(!CompanyContextHolder.isIgnore()) {
Long companyId = CompanyContextHolder.getCompanyId();

View File

@@ -1,3 +1,4 @@
cn.iocoder.yudao.framework.datapermission.config.YudaoDataPermissionAutoConfiguration
cn.iocoder.yudao.framework.datapermission.config.YudaoDeptDataPermissionAutoConfiguration
cn.iocoder.yudao.framework.datapermission.config.YudaoCompanyDataPermissionAutoConfiguration
cn.iocoder.yudao.framework.datapermission.config.YudaoDataPermissionRpcAutoConfiguration