Przeglądaj źródła

pms 设备责任人数据权限增强

zhangcl 1 tydzień temu
rodzic
commit
5a53eddabf

+ 228 - 0
yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/device/DevicePersonDataPermissionRule.java

@@ -0,0 +1,228 @@
+package cn.iocoder.yudao.framework.datapermission.core.rule.device;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
+import cn.iocoder.yudao.framework.security.core.LoginUser;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+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.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 java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+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 芋道源码
+ */
+@AllArgsConstructor
+@Slf4j
+@Component
+public class DevicePersonDataPermissionRule implements DataPermissionRule {
+
+    /**
+     * LoginUser 的 Context 缓存 Key
+     */
+    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=关联字段配置
+    private final Map<String, DeviceTableConfig> tableConfigs = new ConcurrentHashMap<>();
+
+    private final DevicePersonService devicePersonService;
+
+    /**
+     * 基于部门的表字段配置
+     * 一般情况下,每个表的部门编号字段是 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} 的合集
+     */
+    private final Set<String> TABLE_NAMES = new HashSet<>();
+
+    @Override
+    public Set<String> getTableNames() {
+        return TABLE_NAMES;
+    }
+
+    @Override
+    public Expression getExpression(String tableName, Alias tableAlias) {
+        // 1. 获取当前登录用户
+        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
+        if (loginUser == null) {
+            return null;
+        }
+
+        // 2. 获取表配置
+        DeviceTableConfig config = tableConfigs.get(tableName);
+        if (config == null) {
+            return null;
+        }
+
+        // 3. 获取当前用户负责的设备ID集合
+        Set<Long> responsibleDeviceIds = getResponsibleDeviceIds(loginUser);
+
+        // 4. 构建设备责任人条件
+        Expression deviceExpression = buildDeviceExpression(config, tableAlias, responsibleDeviceIds);
+        if (deviceExpression == null) {
+            return null;
+        }
+
+        // 5. 构建主表关联条件(EXISTS子查询)
+        return buildExistsExpression(config, tableAlias, deviceExpression);
+    }
+
+    private Set<Long> getResponsibleDeviceIds(LoginUser loginUser) {
+        // 使用上下文缓存,避免重复查询
+        Set<Long> deviceIds = loginUser.getContext(CONTEXT_KEY, Set.class);
+        if (deviceIds == null) {
+            deviceIds = devicePersonService.getResponsibleDeviceIds(loginUser.getId());
+            loginUser.setContext(CONTEXT_KEY, deviceIds);
+            log.debug("[getResponsibleDeviceIds][用户({}) 负责的设备: {}]",
+                    loginUser.getId(), JsonUtils.toJsonString(deviceIds));
+        }
+        return deviceIds;
+    }
+
+    private Expression buildDeviceExpression(DeviceTableConfig config,
+                                             Alias tableAlias,
+                                             Set<Long> deviceIds) {
+        if (CollUtil.isEmpty(deviceIds)) {
+            return null;
+        }
+
+        // 构建子表设备ID IN条件: device_id IN (?, ?, ...)
+        Column deviceColumn = new Column(tableAlias != null ?
+                tableAlias.getName() + "." + config.getDeviceColumn() :
+                config.getDeviceColumn());
+
+        ExpressionList<LongValue> valueList = new ExpressionList<>();
+        for (Long deviceId : deviceIds) {
+            valueList.add(new LongValue(deviceId));
+        }
+
+        return new InExpression(deviceColumn, valueList);
+    }
+
+    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);
+    }
+
+    /**
+     * 添加设备责任人权限配置
+     *
+     * @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
+        ));
+    }
+
+    /**
+     * 表配置类
+     */
+    @Getter
+    @AllArgsConstructor
+    private static class DeviceTableConfig {
+        /**
+         * 主表主键列名
+         */
+        private final String mainTablePkColumn;
+        /**
+         * 子表名
+         */
+        private final String subTable;
+        /**
+         * 子表关联主表的列名
+         */
+        private final String subTableJoinColumn;
+        /**
+         * 设备ID列名
+         */
+        private final String deviceColumn;
+    }
+
+}

+ 20 - 0
yudao-module-pms/yudao-module-pms-api/src/main/java/cn/iocoder/yudao/module/pms/api/permission/DevicePersonPermissionApi.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.pms.api.permission;
+
+import java.util.Set;
+
+/**
+ * 权限 API 接口
+ *
+ * @author ruiqi
+ */
+public interface DevicePersonPermissionApi {
+
+    /**
+     * 查询以 userId 为责任人的设备id集合
+     *
+     * @param userId 设备责任人id
+     * @return 设备id集合
+     */
+    Set<Long> getResponsibleDeviceIds(Long userId);
+
+}

+ 39 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/api/permission/DevicePersonPermissionApiImpl.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.pms.api.permission;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.pms.controller.admin.iotdeviceperson.vo.IotDevicePersonPageReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.iotdeviceperson.IotDevicePersonDO;
+import cn.iocoder.yudao.module.pms.service.iotdeviceperson.IotDevicePersonService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 权限 API 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+public class DevicePersonPermissionApiImpl implements DevicePersonPermissionApi {
+
+    @Resource
+    private IotDevicePersonService devicePersonService;
+
+    @Override
+    public Set<Long> getResponsibleDeviceIds(Long userId) {
+        IotDevicePersonPageReqVO reqVO = new IotDevicePersonPageReqVO();
+        Set<Long> personIds = new HashSet<>();
+        personIds.add(userId);
+        reqVO.setPersonIds(personIds);
+        List<IotDevicePersonDO> devicePersons = devicePersonService.getByPersonIds(reqVO);
+        if (CollUtil.isNotEmpty(devicePersons)) {
+            devicePersons.forEach(devicePerson -> {
+
+            });
+        }
+        return Set.of();
+    }
+}