|
@@ -2,49 +2,30 @@ package cn.iocoder.yudao.framework.datapermission.core.rule.device;
|
|
|
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
import cn.hutool.core.collection.CollUtil;
|
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
|
|
|
+import cn.iocoder.yudao.framework.datapermission.core.config.DevicePermissionConfig;
|
|
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
|
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
|
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
|
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
|
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
|
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
|
-import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
|
|
|
|
|
+import cn.iocoder.yudao.module.pms.api.permission.DevicePersonPermissionApi;
|
|
|
|
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
|
import lombok.AllArgsConstructor;
|
|
import lombok.AllArgsConstructor;
|
|
-import lombok.Getter;
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import net.sf.jsqlparser.expression.Alias;
|
|
import net.sf.jsqlparser.expression.Alias;
|
|
import net.sf.jsqlparser.expression.Expression;
|
|
import net.sf.jsqlparser.expression.Expression;
|
|
-import net.sf.jsqlparser.expression.LongValue;
|
|
|
|
-import net.sf.jsqlparser.expression.NullValue;
|
|
|
|
-import net.sf.jsqlparser.expression.operators.relational.*;
|
|
|
|
-import net.sf.jsqlparser.schema.Table;
|
|
|
|
-import net.sf.jsqlparser.statement.select.PlainSelect;
|
|
|
|
-import org.springframework.stereotype.Component;
|
|
|
|
-import net.sf.jsqlparser.schema.Column;
|
|
|
|
-
|
|
|
|
-import java.util.HashMap;
|
|
|
|
|
|
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
|
|
|
+
|
|
import java.util.HashSet;
|
|
import java.util.HashSet;
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
|
/**
|
|
/**
|
|
- * 基于部门的 {@link DataPermissionRule} 数据权限规则实现
|
|
|
|
- *
|
|
|
|
- * 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。
|
|
|
|
|
|
+ * 基于 设备责任人 的数据权限
|
|
*
|
|
*
|
|
- * 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改?
|
|
|
|
- * 1. 一般情况下,dept_id 不进行修改,则会导致用户看不到之前的数据。【yudao-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 芋道源码
|
|
|
|
|
|
+ * @author ruiqi
|
|
*/
|
|
*/
|
|
@AllArgsConstructor
|
|
@AllArgsConstructor
|
|
@Slf4j
|
|
@Slf4j
|
|
-@Component
|
|
|
|
public class DevicePersonDataPermissionRule implements DataPermissionRule {
|
|
public class DevicePersonDataPermissionRule implements DataPermissionRule {
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -52,34 +33,13 @@ public class DevicePersonDataPermissionRule implements DataPermissionRule {
|
|
*/
|
|
*/
|
|
protected static final String CONTEXT_KEY = DevicePersonDataPermissionRule.class.getSimpleName();
|
|
protected static final String CONTEXT_KEY = DevicePersonDataPermissionRule.class.getSimpleName();
|
|
|
|
|
|
- private static final String DEPT_COLUMN_NAME = "dept_id";
|
|
|
|
- private static final String USER_COLUMN_NAME = "user_id";
|
|
|
|
-
|
|
|
|
- static final Expression EXPRESSION_NULL = new NullValue();
|
|
|
|
-
|
|
|
|
- private final PermissionApi permissionApi;
|
|
|
|
-
|
|
|
|
// 存储表配置:key=表名, value=关联字段配置
|
|
// 存储表配置:key=表名, value=关联字段配置
|
|
- private final Map<String, DeviceTableConfig> tableConfigs = new ConcurrentHashMap<>();
|
|
|
|
|
|
+ private final Map<String, TableConfig> tableConfigs = new ConcurrentHashMap<>();
|
|
|
|
|
|
- private final DevicePersonService devicePersonService;
|
|
|
|
|
|
+ private final DevicePersonPermissionApi devicePersonService;
|
|
|
|
+
|
|
|
|
+ private final DevicePermissionConfig devicePermissionConfig;
|
|
|
|
|
|
- /**
|
|
|
|
- * 基于部门的表字段配置
|
|
|
|
- * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
|
|
|
|
- *
|
|
|
|
- * key:表名
|
|
|
|
- * value:字段名
|
|
|
|
- */
|
|
|
|
- private final Map<String, String> deptColumns = new HashMap<>();
|
|
|
|
- /**
|
|
|
|
- * 基于用户的表字段配置
|
|
|
|
- * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
|
|
|
|
- *
|
|
|
|
- * key:表名
|
|
|
|
- * value:字段名
|
|
|
|
- */
|
|
|
|
- private final Map<String, String> userColumns = new HashMap<>();
|
|
|
|
/**
|
|
/**
|
|
* 所有表名,是 {@link #deptColumns} 和 {@link #userColumns} 的合集
|
|
* 所有表名,是 {@link #deptColumns} 和 {@link #userColumns} 的合集
|
|
*/
|
|
*/
|
|
@@ -92,29 +52,74 @@ public class DevicePersonDataPermissionRule implements DataPermissionRule {
|
|
|
|
|
|
@Override
|
|
@Override
|
|
public Expression getExpression(String tableName, Alias tableAlias) {
|
|
public Expression getExpression(String tableName, Alias tableAlias) {
|
|
|
|
+ // 1. 获取表配置
|
|
|
|
+ TableConfig config = getTableConfig(tableName);
|
|
|
|
+ if (config == null) return null;
|
|
|
|
+
|
|
// 1. 获取当前登录用户
|
|
// 1. 获取当前登录用户
|
|
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
|
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
|
if (loginUser == null) {
|
|
if (loginUser == null) {
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
- // 2. 获取表配置
|
|
|
|
- DeviceTableConfig config = tableConfigs.get(tableName);
|
|
|
|
- if (config == null) {
|
|
|
|
- return null;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
// 3. 获取当前用户负责的设备ID集合
|
|
// 3. 获取当前用户负责的设备ID集合
|
|
Set<Long> responsibleDeviceIds = getResponsibleDeviceIds(loginUser);
|
|
Set<Long> responsibleDeviceIds = getResponsibleDeviceIds(loginUser);
|
|
|
|
+ if (CollUtil.isEmpty(responsibleDeviceIds)) return null;
|
|
|
|
+
|
|
|
|
+ // 5. 构建主表关联条件(EXISTS子查询)
|
|
|
|
+ return buildDataPermissionExpression(config, tableName, tableAlias, responsibleDeviceIds);
|
|
|
|
+ }
|
|
|
|
|
|
- // 4. 构建设备责任人条件
|
|
|
|
- Expression deviceExpression = buildDeviceExpression(config, tableAlias, responsibleDeviceIds);
|
|
|
|
- if (deviceExpression == null) {
|
|
|
|
|
|
+ private Expression buildDataPermissionExpression(TableConfig config,
|
|
|
|
+ String tableName,
|
|
|
|
+ Alias tableAlias,
|
|
|
|
+ Set<Long> deviceIds) {
|
|
|
|
+ try {
|
|
|
|
+ // 1. 构建子查询SQL
|
|
|
|
+ String subQuerySql = buildSubQuerySql(config, deviceIds);
|
|
|
|
+
|
|
|
|
+ // 2. 构建主表条件:主表关联字段 IN (子查询结果)
|
|
|
|
+ String inExpressionSql = buildInExpressionSql(config, tableName, tableAlias, subQuerySql);
|
|
|
|
+
|
|
|
|
+ // 3. 解析SQL为表达式
|
|
|
|
+ return CCJSqlParserUtil.parseCondExpression(inExpressionSql);
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ log.error("[buildDataPermissionExpression]构建设备责任人条件失败", e);
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- // 5. 构建主表关联条件(EXISTS子查询)
|
|
|
|
- return buildExistsExpression(config, tableAlias, deviceExpression);
|
|
|
|
|
|
+ private String buildSubQuerySql(TableConfig config, Set<Long> deviceIds) {
|
|
|
|
+ // 构建设备ID列表字符串
|
|
|
|
+ String deviceIdsStr = CollUtil.join(deviceIds, ", ");
|
|
|
|
+
|
|
|
|
+ // 构建子查询SQL
|
|
|
|
+ return String.format("(SELECT %s FROM %s WHERE %s IN (%s))",
|
|
|
|
+ config.getJoinColumn(),
|
|
|
|
+ config.getSubTable(),
|
|
|
|
+ config.getDeviceColumn(),
|
|
|
|
+ deviceIdsStr);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private String buildInExpressionSql(TableConfig config,
|
|
|
|
+ String tableName,
|
|
|
|
+ Alias tableAlias,
|
|
|
|
+ String subQuerySql) {
|
|
|
|
+ // 构建主表列名
|
|
|
|
+ String mainColumn = buildMainTableColumn(tableName, tableAlias, config.getPrimaryKey());
|
|
|
|
+
|
|
|
|
+ // 构建IN表达式SQL
|
|
|
|
+ return String.format("%s IN %s", mainColumn, subQuerySql);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private String buildMainTableColumn(String tableName, Alias tableAlias, String columnName) {
|
|
|
|
+ if (tableAlias != null) {
|
|
|
|
+ // 如果有别名,使用别名构建列
|
|
|
|
+ return tableAlias.getName() + "." + columnName;
|
|
|
|
+ } else {
|
|
|
|
+ // 没有别名,直接使用表名+列名
|
|
|
|
+ return tableName + "." + columnName;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
private Set<Long> getResponsibleDeviceIds(LoginUser loginUser) {
|
|
private Set<Long> getResponsibleDeviceIds(LoginUser loginUser) {
|
|
@@ -129,100 +134,85 @@ public class DevicePersonDataPermissionRule implements DataPermissionRule {
|
|
return deviceIds;
|
|
return deviceIds;
|
|
}
|
|
}
|
|
|
|
|
|
- private Expression buildDeviceExpression(DeviceTableConfig config,
|
|
|
|
- Alias tableAlias,
|
|
|
|
- Set<Long> deviceIds) {
|
|
|
|
- if (CollUtil.isEmpty(deviceIds)) {
|
|
|
|
- return null;
|
|
|
|
- }
|
|
|
|
|
|
+ // 添加表配置
|
|
|
|
+ public void addConfig(String mainTable, String primaryKey,
|
|
|
|
+ String subTable, String joinColumn, String deviceColumn) {
|
|
|
|
+ tableConfigs.put(mainTable, new TableConfig(primaryKey, subTable, joinColumn, deviceColumn));
|
|
|
|
+ TABLE_NAMES.add(mainTable);
|
|
|
|
+ }
|
|
|
|
|
|
- // 构建子表设备ID IN条件: device_id IN (?, ?, ...)
|
|
|
|
- Column deviceColumn = new Column(tableAlias != null ?
|
|
|
|
- tableAlias.getName() + "." + config.getDeviceColumn() :
|
|
|
|
- config.getDeviceColumn());
|
|
|
|
|
|
+ private TableConfig getTableConfig(String tableName) {
|
|
|
|
+ // 从统一配置中获取主表对应的实体类
|
|
|
|
+ Class<?> mainEntity = findMainEntityByTableName(tableName);
|
|
|
|
+ if (mainEntity == null) return null;
|
|
|
|
|
|
- ExpressionList<LongValue> valueList = new ExpressionList<>();
|
|
|
|
- for (Long deviceId : deviceIds) {
|
|
|
|
- valueList.add(new LongValue(deviceId));
|
|
|
|
- }
|
|
|
|
|
|
+ // 获取子表配置
|
|
|
|
+ DevicePermissionConfig.SubTableConfig subConfig =
|
|
|
|
+ devicePermissionConfig.getSubTableConfig(mainEntity);
|
|
|
|
|
|
- return new InExpression(deviceColumn, valueList);
|
|
|
|
- }
|
|
|
|
|
|
+ if (subConfig == null) return null;
|
|
|
|
|
|
- private Expression buildExistsExpression(DeviceTableConfig config,
|
|
|
|
- Alias tableAlias,
|
|
|
|
- Expression deviceExpression) {
|
|
|
|
- // 构建EXISTS子查询:
|
|
|
|
- // EXISTS (SELECT 1 FROM 子表
|
|
|
|
- // WHERE 子表.主表关联列 = 主表.主键列
|
|
|
|
- // AND (设备条件))
|
|
|
|
-
|
|
|
|
- // 1. 子查询表
|
|
|
|
- Table subTable = new Table(config.getSubTable());
|
|
|
|
- subTable.setAlias(new Alias("sub", false));
|
|
|
|
-
|
|
|
|
- // 2. WHERE条件: 子表.主表关联列 = 主表.主键列
|
|
|
|
- Column subTableJoinColumn = new Column("sub." + config.getSubTableJoinColumn());
|
|
|
|
- Column mainTablePkColumn = new Column(
|
|
|
|
- (tableAlias != null ? tableAlias.getName() + "." : "") + config.getMainTablePkColumn());
|
|
|
|
- EqualsTo joinCondition = new EqualsTo(subTableJoinColumn, mainTablePkColumn);
|
|
|
|
-
|
|
|
|
- // 3. 组合条件: 关联条件 AND 设备条件
|
|
|
|
- ParenthesedExpressionList conditions = new ParenthesedExpressionList();
|
|
|
|
- conditions.add(joinCondition);
|
|
|
|
- conditions.add(deviceExpression);
|
|
|
|
-
|
|
|
|
- // 4. 构建子查询
|
|
|
|
- SubSelect subSelect = new SubSelect();
|
|
|
|
- subSelect.setSelectBody(new PlainSelect()
|
|
|
|
- .addSelectItems(new LongValue(1))
|
|
|
|
- .withFromItem(subTable)
|
|
|
|
- .withWhere(conditions));
|
|
|
|
-
|
|
|
|
- return new ExistsExpression().withRightExpression(subSelect);
|
|
|
|
|
|
+ return new TableConfig(
|
|
|
|
+ "id", // 主键默认为id
|
|
|
|
+ subConfig.getSubTable(),
|
|
|
|
+ subConfig.getJoinColumn(),
|
|
|
|
+ subConfig.getDeviceColumn()
|
|
|
|
+ );
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * 添加设备责任人权限配置
|
|
|
|
- *
|
|
|
|
- * @param mainTable 主表名
|
|
|
|
- * @param mainTablePkColumn 主表主键列名
|
|
|
|
- * @param subTable 子表名
|
|
|
|
- * @param subTableJoinColumn 子表关联主表的列名
|
|
|
|
- * @param deviceColumn 设备ID列名
|
|
|
|
- */
|
|
|
|
- public void addConfig(String mainTable,
|
|
|
|
- String mainTablePkColumn,
|
|
|
|
- String subTable,
|
|
|
|
- String subTableJoinColumn,
|
|
|
|
- String deviceColumn) {
|
|
|
|
- tableConfigs.put(mainTable, new DeviceTableConfig(
|
|
|
|
- mainTablePkColumn, subTable, subTableJoinColumn, deviceColumn
|
|
|
|
- ));
|
|
|
|
|
|
+ private Class<?> findMainEntityByTableName(String tableName) {
|
|
|
|
+ for (Class<?> entity : devicePermissionConfig.getMainEntities()) {
|
|
|
|
+ String entityTable = TableInfoHelper.getTableInfo(entity).getTableName();
|
|
|
|
+ if (entityTable.equalsIgnoreCase(tableName)) {
|
|
|
|
+ return entity;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return null;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
- * 表配置类
|
|
|
|
|
|
+ * 表配置类(替代record)
|
|
*/
|
|
*/
|
|
- @Getter
|
|
|
|
- @AllArgsConstructor
|
|
|
|
- private static class DeviceTableConfig {
|
|
|
|
- /**
|
|
|
|
- * 主表主键列名
|
|
|
|
- */
|
|
|
|
- private final String mainTablePkColumn;
|
|
|
|
- /**
|
|
|
|
- * 子表名
|
|
|
|
- */
|
|
|
|
|
|
+ private static class TableConfig {
|
|
|
|
+ private final String primaryKey;
|
|
private final String subTable;
|
|
private final String subTable;
|
|
|
|
+ private final String joinColumn;
|
|
|
|
+ private final String deviceColumn;
|
|
|
|
+
|
|
|
|
+ public TableConfig(String primaryKey, String subTable,
|
|
|
|
+ String joinColumn, String deviceColumn) {
|
|
|
|
+ this.primaryKey = primaryKey;
|
|
|
|
+ this.subTable = subTable;
|
|
|
|
+ this.joinColumn = joinColumn;
|
|
|
|
+ this.deviceColumn = deviceColumn;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public String getPrimaryKey() {
|
|
|
|
+ return primaryKey;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public String getSubTable() {
|
|
|
|
+ return subTable;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public String getJoinColumn() {
|
|
|
|
+ return joinColumn;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public String getDeviceColumn() {
|
|
|
|
+ return deviceColumn;
|
|
|
|
+ }
|
|
|
|
+
|
|
/**
|
|
/**
|
|
- * 子表关联主表的列名
|
|
|
|
- */
|
|
|
|
- private final String subTableJoinColumn;
|
|
|
|
- /**
|
|
|
|
- * 设备ID列名
|
|
|
|
|
|
+ * 生成安全的子表别名
|
|
*/
|
|
*/
|
|
- private final String deviceColumn;
|
|
|
|
|
|
+ public String getSubTableAlias() {
|
|
|
|
+ // 替换特殊字符,避免SQL注入和语法错误
|
|
|
|
+ String safeAlias = "sub_" + subTable.replaceAll("[^a-zA-Z0-9]", "_");
|
|
|
|
+
|
|
|
|
+ // 确保别名长度合理
|
|
|
|
+ return safeAlias.length() > 30 ? safeAlias.substring(0, 30) : safeAlias;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|