From d32978347568fe2a2822e2c988864695f0b47a92 Mon Sep 17 00:00:00 2001
From: zhaowenyu <35589726@qq.com>
Date: Sat, 01 Nov 2025 12:30:42 +0800
Subject: [PATCH] 2025-11-1 zwy 对机场设备列表、绑定设备列表增加权限配置,新增DataScopeAspectPlus切面文件适配MyBatis-Plus。

---
 ard-admin/src/main/java/com/ard/web/controller/sample/DeviceController.java        |   31 ++--
 ard-sample/src/main/java/com/ard/sample/manage/service/IDeviceService.java         |   12 +
 ard-sample/src/main/java/com/ard/sample/manage/mapper/IDeviceMapper.java           |   20 ++
 ard-framework/src/main/java/com/ard/framework/aspectj/DataScopeAspectPlus.java     |  189 +++++++++++++++++++++++++++
 ard-sample/src/main/java/com/ard/sample/manage/service/impl/DeviceServiceImpl.java |   90 +++++++++++-
 ard-common/src/main/java/com/ard/common/annotation/DataScopePlus.java              |   27 ++-
 6 files changed, 333 insertions(+), 36 deletions(-)

diff --git a/ard-admin/src/main/java/com/ard/web/controller/sample/DeviceController.java b/ard-admin/src/main/java/com/ard/web/controller/sample/DeviceController.java
index 0b561b6..16b7c1f 100644
--- a/ard-admin/src/main/java/com/ard/web/controller/sample/DeviceController.java
+++ b/ard-admin/src/main/java/com/ard/web/controller/sample/DeviceController.java
@@ -95,9 +95,11 @@
      * @return
      */
     @ApiOperation("设备列表")
+    @PreAuthorize("@ss.hasPermi('device:dock:list')")
     @GetMapping("/list")
     public ResponseResult<List<DeviceDTO>> getDevices() {
-        List<DeviceDTO> devicesList = deviceService.getDevicesTopoForWeb();
+//        List<DeviceDTO> devicesList = deviceService.getDevicesTopoForWeb();
+        List<DeviceDTO> devicesList = deviceService.getDockList();
         return ResponseResult.success(devicesList);
     }
 
@@ -140,6 +142,7 @@
      */
     @ApiOperation("绑定设备列表")
     @GetMapping("/list/bound")
+    @PreAuthorize("@ss.hasPermi('device:dock:list')")
     public ResponseResult<PaginationData<DeviceDTO>> getBoundDevicesWithDomain(Integer domain,
                                                                                @RequestParam(defaultValue = "1") Long page,
                                                                                @RequestParam(value = "page_size",
@@ -214,19 +217,19 @@
         return ResponseResult.success();
     }
 
-    /**
-     * 查询机场设备列表
-     */
-    @PreAuthorize("@ss.hasPermi('device:camera:list')")
-    @GetMapping("/test/list")
-    @ApiOperation("查询机场设备列表")
-    public TableDataInfo list(DeviceDTO device) {
-        startPage();
-        DeviceEntity deviceEntity = deviceDTO2Entity(device);
-        QueryWrapper<DeviceEntity> queryWrapper=new QueryWrapper<>(deviceEntity);
-        List<DeviceEntity> list = deviceService.selectList(queryWrapper);
-        return getDataTable(list);
-    }
+//    /**
+//     * 查询机场设备列表
+//     */
+//    @PreAuthorize("@ss.hasPermi('device:camera:list')")
+//    @GetMapping("/test/list")
+//    @ApiOperation("查询机场设备列表")
+//    public TableDataInfo list(DeviceDTO device) {
+//        startPage();
+//        DeviceEntity deviceEntity = deviceDTO2Entity(device);
+//        QueryWrapper<DeviceEntity> queryWrapper=new QueryWrapper<>(deviceEntity);
+//        List<DeviceEntity> list = deviceService.selectList(queryWrapper);
+//        return getDataTable(list);
+//    }
 
     private DeviceEntity deviceDTO2Entity(DeviceDTO dto) {
         DeviceEntity.DeviceEntityBuilder builder = DeviceEntity.builder();
diff --git a/ard-common/src/main/java/com/ard/common/annotation/DataScopePlus.java b/ard-common/src/main/java/com/ard/common/annotation/DataScopePlus.java
index 1f9e49c..44548e0 100644
--- a/ard-common/src/main/java/com/ard/common/annotation/DataScopePlus.java
+++ b/ard-common/src/main/java/com/ard/common/annotation/DataScopePlus.java
@@ -1,29 +1,34 @@
 package com.ard.common.annotation;
 
-import java.lang.annotation.*;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 
 /**
- * 数据权限过滤注解
- * 
+ * MyBatis-Plus 专用数据权限过滤注解
+ * 用于标注需要进行数据权限过滤的方法,配合 QueryWrapper 使用
+ *
  * @author ard
  */
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
-public @interface DataScopePlus
-{
+public @interface DataScopePlus {
     /**
-     * 部门表的别名
+     * 部门表的别名(需与 Wrapper 中设置的表别名一致)
      */
-    public String deptAlias() default "";
+    String deptAlias() default "";
 
     /**
-     * 用户表的别名
+     * 用户表的别名(需与 Wrapper 中设置的表别名一致)
      */
-    public String userAlias() default "";
+    String userAlias() default "";
 
     /**
-     * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来
+     * 权限字符(用于匹配角色对应的权限)
+     * 多个权限用逗号分隔,默认从 PermissionContextHolder 获取
      */
-    public String permission() default "";
+    String permission() default "";
 }
diff --git a/ard-framework/src/main/java/com/ard/framework/aspectj/DataScopeAspectPlus.java b/ard-framework/src/main/java/com/ard/framework/aspectj/DataScopeAspectPlus.java
new file mode 100644
index 0000000..ad12e1f
--- /dev/null
+++ b/ard-framework/src/main/java/com/ard/framework/aspectj/DataScopeAspectPlus.java
@@ -0,0 +1,189 @@
+package com.ard.framework.aspectj;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.stereotype.Component;
+import com.ard.common.annotation.DataScopePlus;
+import com.ard.common.constant.UserConstants;
+import com.ard.common.core.domain.entity.SysRole;
+import com.ard.common.core.domain.entity.SysUser;
+import com.ard.common.core.domain.model.LoginUser;
+import com.ard.common.core.text.Convert;
+import com.ard.common.utils.SecurityUtils;
+import com.ard.common.utils.StringUtils;
+import com.ard.framework.security.context.PermissionContextHolder;
+import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
+
+/**
+ * MyBatis-Plus 专用数据权限切面
+ * 仅处理带有 @MyBatisPlusDataScope 注解的方法,且参数包含 QueryWrapper/LambdaQueryWrapper
+ */
+@Aspect
+@Component
+public class DataScopeAspectPlus {
+    /**
+     * 全部数据权限
+     */
+    public static final String DATA_SCOPE_ALL = "1";
+
+    /**
+     * 自定数据权限(部门维度)
+     */
+    public static final String DATA_SCOPE_CUSTOM = "2";
+
+    /**
+     * 部门数据权限(仅本部门)
+     */
+    public static final String DATA_SCOPE_DEPT = "3";
+
+    /**
+     * 部门及以下数据权限
+     */
+    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
+
+    /**
+     * 仅本人数据权限
+     */
+    public static final String DATA_SCOPE_SELF = "5";
+
+    /**
+     * 拦截带有 @MyBatisPlusDataScope 注解的方法,处理部门权限
+     */
+    @Before("@annotation(dataScope)")
+    public void doBefore(JoinPoint point, DataScopePlus dataScope) throws Throwable {
+        // 遍历方法参数,找到 Wrapper 类型的参数
+        AbstractWrapper<?, ?, ?> wrapper = findWrapperFromArgs(point.getArgs());
+        if (wrapper != null) {
+            handleDataScope(wrapper, dataScope); // 找到后执行权限逻辑
+        }
+    }
+
+    /**
+     * 从方法参数中找到 AbstractWrapper 类型的参数(兼容任意位置)
+     */
+    private AbstractWrapper<?, ?, ?> findWrapperFromArgs(Object[] args) {
+        if (args == null || args.length == 0) {
+            return null;
+        }
+        for (Object arg : args) {
+            // 判断是否为 MyBatis-Plus 的 Wrapper 类型
+            if (arg instanceof AbstractWrapper) {
+                return (AbstractWrapper<?, ?, ?>) arg;
+            }
+        }
+        return null; // 没找到 Wrapper,不执行权限过滤
+    }
+
+    /**
+     * 处理部门数据权限
+     */
+    protected void handleDataScope(AbstractWrapper<?, ?, ?> wrapper, DataScopePlus dataScope) {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNotNull(loginUser)) {
+            SysUser currentUser = loginUser.getUser();
+            // 超级管理员跳过权限过滤
+            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) {
+                String permission = StringUtils.defaultIfEmpty(
+                        dataScope.permission(),
+                        PermissionContextHolder.getContext()
+                );
+                // 生成部门权限条件并注入 Wrapper
+                generateDeptScopeSql(wrapper, currentUser, dataScope.deptAlias(), dataScope.userAlias(), permission);
+            }
+        }
+    }
+
+    /**
+     * 仅生成部门维度的权限 SQL(移除用户相关逻辑)
+     */
+    private void generateDeptScopeSql(AbstractWrapper<?, ?, ?> wrapper, SysUser user,
+                                      String deptAlias, String userAlias, String permission) {
+        StringBuilder sqlBuilder = new StringBuilder();
+        List<String> conditions = new ArrayList<>();
+        List<String> customRoleIds = new ArrayList<>();
+
+        // 收集拥有自定义部门权限的角色ID
+        user.getRoles().forEach(role -> {
+            if (DATA_SCOPE_CUSTOM.equals(role.getDataScope())
+                    && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL)
+                    && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) {
+                customRoleIds.add(Convert.toStr(role.getRoleId()));
+            }
+        });
+
+        // 遍历角色生,生成对应的数据权限条件
+        for (SysRole role : user.getRoles()) {
+            String dataScope = role.getDataScope();
+            // 跳过已处理的权限类型或禁用角色
+            if (conditions.contains(dataScope) || StringUtils.equals(role.getStatus(), UserConstants.ROLE_DISABLE)) {
+                continue;
+            }
+            // 跳过不包含目标权限的角色
+            if (!StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) {
+                continue;
+            }
+
+            // 仅保留部门相关的权限逻辑(移除用户权限)
+            if (DATA_SCOPE_ALL.equals(dataScope)) {
+                // 全部权限:清空条件
+                sqlBuilder.setLength(0);
+                conditions.add(dataScope);
+                break;
+            } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
+                // 自定义部门权限:关联角色-部门表
+                if (customRoleIds.size() > 1) {
+                    sqlBuilder.append(StringUtils.format(
+                            " OR {}.dept_id IN (SELECT dept_id FROM sys_role_dept WHERE role_id IN ({}) )",
+                            deptAlias, String.join(",", customRoleIds)
+                    ));
+                } else {
+                    sqlBuilder.append(StringUtils.format(
+                            " OR {}.dept_id IN (SELECT dept_id FROM sys_role_dept WHERE role_id = {} )",
+                            deptAlias, role.getRoleId()
+                    ));
+                }
+            } else if (DATA_SCOPE_DEPT.equals(dataScope)) {
+                // 本部门权限:仅当前用户所属部门
+                sqlBuilder.append(StringUtils.format(
+                        " OR {}.dept_id = {}", deptAlias, user.getDeptId()
+                ));
+            } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
+                // 本部门及下属部门权限:通过部门表 ancestors 字段递归查询
+                sqlBuilder.append(StringUtils.format(
+                        " OR {}.dept_id IN (SELECT dept_id FROM sys_dept WHERE dept_id = {} OR find_in_set({}, ancestors) )",
+                        deptAlias, user.getDeptId(), user.getDeptId()
+                ));
+            } else if (DATA_SCOPE_SELF.equals(dataScope)) {
+                // 仅本人权限:关联用户表
+                if (StringUtils.isNotBlank(userAlias)) {
+                    sqlBuilder.append(StringUtils.format(
+                            " OR {}.user_id = {}", userAlias, user.getUserId()
+                    ));
+                } else {
+                    // 无用户别名时强制过滤(不查询任何数据)
+                    sqlBuilder.append(StringUtils.format(
+                            " OR {}.dept_id = 0", deptAlias
+                    ));
+                }
+            }
+            conditions.add(dataScope);
+        }
+
+        // 无匹配角色时,强制过滤(不查询任何数据)
+        if (conditions.isEmpty()) {
+            sqlBuilder.append(StringUtils.format(" OR {}.dept_id = 0", deptAlias));
+        }
+
+        // 注入部门权限条件
+        if (sqlBuilder.length() > 0) {
+            String dataScopeSql = sqlBuilder.substring(4); // 去除开头的 "OR "
+            wrapper.apply(dataScopeSql);
+        }
+    }
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/manage/mapper/IDeviceMapper.java b/ard-sample/src/main/java/com/ard/sample/manage/mapper/IDeviceMapper.java
index cffbb16..2fd4946 100644
--- a/ard-sample/src/main/java/com/ard/sample/manage/mapper/IDeviceMapper.java
+++ b/ard-sample/src/main/java/com/ard/sample/manage/mapper/IDeviceMapper.java
@@ -1,7 +1,14 @@
 package com.ard.sample.manage.mapper;
 
+import com.ard.common.annotation.DataScopePlus;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.ard.sample.manage.model.entity.DeviceEntity;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
 
 /**
  *
@@ -11,4 +18,17 @@
  */
 public interface IDeviceMapper extends BaseMapper<DeviceEntity> {
 
+    @Select("SELECT m.* " +
+            "FROM manage_device m " +
+            "left join sys_dept d on d.dept_id = m.dept_id " +
+            "${ew.customSqlSegment}")
+    @DataScopePlus(deptAlias = "d", permission = "device:dock:list")
+    List<DeviceEntity> selectListPer(@Param("ew") Wrapper<DeviceEntity> queryWrapper);
+
+    @Select("SELECT m.* " +
+            "FROM manage_device m " +
+            "left join sys_dept d on d.dept_id = m.dept_id " +
+            "${ew.customSqlSegment}")
+    @DataScopePlus(deptAlias = "d", permission = "device:dock:list")
+    <P extends IPage<DeviceEntity>> P selectPage(P page, @Param("ew") Wrapper<DeviceEntity> queryWrapper);
 }
\ No newline at end of file
diff --git a/ard-sample/src/main/java/com/ard/sample/manage/service/IDeviceService.java b/ard-sample/src/main/java/com/ard/sample/manage/service/IDeviceService.java
index 42d8f10..f8123a1 100644
--- a/ard-sample/src/main/java/com/ard/sample/manage/service/IDeviceService.java
+++ b/ard-sample/src/main/java/com/ard/sample/manage/service/IDeviceService.java
@@ -104,6 +104,16 @@
     void pushDeviceOnlineTopo(Collection<ConcurrentWebSocketSession> sessions, String sn, String gatewaySn);
 
     /**
+     * 根据部门权限获取机场列表
+     * */
+    List<DeviceDTO> getDockList();
+
+    void spliceDeviceTopoPer(DeviceDTO gateway);
+
+//    @DataScope(deptAlias = "d")
+    List<DeviceDTO> getDockListByParams(DeviceQueryParam param);
+
+    /**
      * Query the information of the device according to the sn of the device.
      * @param sn device sn
      * @return
@@ -232,5 +242,5 @@
     Boolean checkDeviceOnline(String sn);
 
 
-    public List<DeviceEntity> selectList(Wrapper<DeviceEntity> queryWrapper);
+//    public List<DeviceEntity> selectList(Wrapper<DeviceEntity> queryWrapper);
 }
\ No newline at end of file
diff --git a/ard-sample/src/main/java/com/ard/sample/manage/service/impl/DeviceServiceImpl.java b/ard-sample/src/main/java/com/ard/sample/manage/service/impl/DeviceServiceImpl.java
index fc5dd06..816c596 100644
--- a/ard-sample/src/main/java/com/ard/sample/manage/service/impl/DeviceServiceImpl.java
+++ b/ard-sample/src/main/java/com/ard/sample/manage/service/impl/DeviceServiceImpl.java
@@ -1,10 +1,12 @@
 package com.ard.sample.manage.service.impl;
 
 import com.alibaba.fastjson2.JSONObject;
+import com.ard.common.annotation.DataScope;
 import com.ard.common.annotation.DataScopePlus;
 import com.ard.system.authorize.service.AuthorizeService;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ard.common.core.domain.entity.SysDept;
@@ -347,6 +349,68 @@
 
         // payloads
         // subDevice.setPayloadsList(payloadService.getDevicePayloadEntitiesByDeviceSn(gateway.getChildDeviceSn()));
+    }
+
+    @Override
+    public List<DeviceDTO> getDockList() {
+        List<DeviceDTO> devicesList = this.getDockListByParams(
+                DeviceQueryParam.builder()
+                        .domains(List.of(DeviceDomainEnum.GATEWAY.getVal(), DeviceDomainEnum.DOCK.getVal()))
+                        .build());
+
+        devicesList.stream()
+                .filter(gateway -> DeviceDomainEnum.DOCK.getVal() == gateway.getDomain() ||
+                        RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + gateway.getDeviceSn()))
+                .forEach(this::spliceDeviceTopoPer);
+
+        return devicesList;
+    }
+
+    @Override
+    public void spliceDeviceTopoPer(DeviceDTO gateway) {
+
+        gateway.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + gateway.getDeviceSn()));
+
+        // sub device
+        if (!StringUtils.hasText(gateway.getChildDeviceSn())) {
+            return;
+        }
+
+        DeviceDTO subDevice =
+                getDevicesByParams(DeviceQueryParam.builder().deviceSn(gateway.getChildDeviceSn()).build()).get(0);
+        subDevice.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + subDevice.getDeviceSn()));
+        gateway.setChildren(subDevice);
+
+        // payloads
+        // subDevice.setPayloadsList(payloadService.getDevicePayloadEntitiesByDeviceSn(gateway.getChildDeviceSn()));
+    }
+
+    //@DataScope(deptAlias = "d")
+    @Override
+    public List<DeviceDTO> getDockListByParams(DeviceQueryParam param){
+
+        return mapper.selectListPer(
+                        new LambdaQueryWrapper<DeviceEntity>()
+                                .eq(StringUtils.hasText(param.getDeviceSn()),
+                                        DeviceEntity::getDeviceSn, param.getDeviceSn())
+                                .eq(param.getDeviceType() != null,
+                                        DeviceEntity::getDeviceType, param.getDeviceType())
+                                .eq(param.getSubType() != null,
+                                        DeviceEntity::getSubType, param.getSubType())
+                                .eq(StringUtils.hasText(param.getChildSn()),
+                                        DeviceEntity::getChildSn, param.getChildSn())
+                                .and(!CollectionUtils.isEmpty(param.getDomains()), wrapper -> {
+                                    for (Integer domain : param.getDomains()) {
+                                        wrapper.eq(DeviceEntity::getDomain, domain).or();
+                                    }
+                                })
+                                .eq(param.getBoundStatus() != null, DeviceEntity::getBoundStatus,
+                                        param.getBoundStatus())
+                                .orderBy(param.isOrderBy(),
+                                        param.isAsc(), DeviceEntity::getId))
+                .stream()
+                .map(this::deviceEntityConvertToDTO)
+                .collect(Collectors.toList());
     }
 
     @Override
@@ -874,11 +938,17 @@
                                                                Long pageSize, Integer domain, Long deptId) {
 
         Page<DeviceEntity> pagination = mapper.selectPage(new Page<>(page, pageSize),
-                new LambdaQueryWrapper<DeviceEntity>()
-                        .eq(DeviceEntity::getDomain, domain)
-                        .eq(deptId != null, DeviceEntity::getDeptId, deptId)
-                        .isNotNull(DeviceEntity::getDeptId));
-//                        .eq(DeviceEntity::getBoundStatus, true));
+                new QueryWrapper<DeviceEntity>()
+                        .eq("m.domain", domain)
+                        .eq(deptId != null, "m.dept_id", deptId)
+                        .isNotNull("m.dept_id"));
+
+        //以前的写法
+//        new LambdaQueryWrapper<DeviceEntity>()
+//                .eq(DeviceEntity::getDomain, domain)
+//                .eq(deptId != null, DeviceEntity::getDeptId, deptId)
+//                .isNotNull(DeviceEntity::getDeptId);
+
         List<DeviceDTO> devicesList = pagination.getRecords().stream().map(this::deviceEntityConvertToDTO)
                 .peek(device -> {
                     device.setStatus(RedisOpsUtils.checkExist(RedisConst.DEVICE_ONLINE_PREFIX + device.getDeviceSn()));
@@ -1221,9 +1291,9 @@
         messageSender.publish(topic, builder.build());
     }
 
-    @DataScopePlus(deptAlias="d")
-    @Override
-    public List<DeviceEntity> selectList(Wrapper<DeviceEntity> queryWrapper) {
-        return mapper.selectList(queryWrapper);
-    }
+//    @DataScopePlus(deptAlias="d")
+//    @Override
+//    public List<DeviceEntity> selectList(Wrapper<DeviceEntity> queryWrapper) {
+//        return mapper.selectList(queryWrapper);
+//    }
 }
\ No newline at end of file

--
Gitblit v1.9.3