From 5f19ddef7e25e41a292ad81c38c891536b8b6b65 Mon Sep 17 00:00:00 2001
From: vertoryao <673441990@qq.com>
Date: Tue, 21 Jan 2025 15:07:15 +0800
Subject: [PATCH] =?UTF-8?q?feat(dataScope):=20=E6=B7=BB=E5=8A=A0=E6=95=B0?=
 =?UTF-8?q?=E6=8D=AE=E6=9D=83=E9=99=90=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- 新增数据权限注解和切面
- 实现数据权限处理逻辑
- 更新用户实体和 mapper 文件以支持数据权限
- 添加数据权限测试用例
---
 pom.xml                                       |   5 +
 .../zsc/edu/gateway/common/util/TreeUtil.java |   7 +-
 .../framework/jackson/JacksonConfig.java      |  25 ----
 .../framework/mybatisplus/DataPermission.java |  22 +++
 .../mybatisplus/DataPermissionContext.java    |  18 +++
 .../mybatisplus/DataScopeAspect.java          |  44 ++++++
 .../mybatisplus/DataScopeHandler.java         | 130 ++++++++++++++++++
 .../framework/mybatisplus/DataScopeType.java  |  36 +++++
 .../mybatisplus/MybatisPlusConfig.java        |  15 +-
 .../security/JpaUserDetailsServiceImpl.java   |  10 +-
 .../framework/security/UserDetailsImpl.java   |   5 +-
 .../product/controller/ProductController.java |   4 +-
 .../iot/product/repo/ProductRepository.java   |   6 +
 .../gateway/modules/system/entity/Role.java   |   5 +
 .../gateway/modules/system/entity/User.java   |   7 +
 .../resources/mappers/system/UserMapper.xml   |  32 +++--
 .../service/system/UserServiceTest.java       |  34 +++--
 17 files changed, 337 insertions(+), 68 deletions(-)
 delete mode 100644 src/main/java/com/zsc/edu/gateway/framework/jackson/JacksonConfig.java
 create mode 100644 src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataPermission.java
 create mode 100644 src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataPermissionContext.java
 create mode 100644 src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataScopeAspect.java
 create mode 100644 src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataScopeHandler.java
 create mode 100644 src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataScopeType.java

diff --git a/pom.xml b/pom.xml
index c3929d0..454470f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,6 +36,11 @@
 <!--			<groupId>org.springframework.boot</groupId>-->
 <!--			<artifactId>spring-boot-starter-data-mongodb</artifactId>-->
 <!--		</dependency>-->
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-aop</artifactId>
+		</dependency>
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
 			<artifactId>spring-boot-starter-data-redis</artifactId>
diff --git a/src/main/java/com/zsc/edu/gateway/common/util/TreeUtil.java b/src/main/java/com/zsc/edu/gateway/common/util/TreeUtil.java
index 312f984..7bb1ba1 100644
--- a/src/main/java/com/zsc/edu/gateway/common/util/TreeUtil.java
+++ b/src/main/java/com/zsc/edu/gateway/common/util/TreeUtil.java
@@ -31,7 +31,7 @@ public class TreeUtil {
     }
 
     /**
-     * 将树打平成tree
+     * 将树打平成list
      *
      * @param tree           需要打平的树
      * @param getSubChildren 设置下级数据方法,如:Menu::getSubMenus,x->x.setSubMenus(null)
@@ -125,6 +125,9 @@ public class TreeUtil {
     }
 
     private static <E> List<E> makeChildren(E parent, List<E> allData, BiFunction<E, E, Boolean> parentCheck, BiConsumer<E, List<E>> children) {
-        return allData.stream().filter(x -> parentCheck.apply(parent, x)).peek(x -> children.accept(x, makeChildren(x, allData, parentCheck, children))).collect(Collectors.toList());
+        return allData.stream()
+                .filter(x -> parentCheck.apply(parent, x))
+                .peek(x -> children.accept(x, makeChildren(x, allData, parentCheck, children)))
+                .collect(Collectors.toList());
     }
 }
diff --git a/src/main/java/com/zsc/edu/gateway/framework/jackson/JacksonConfig.java b/src/main/java/com/zsc/edu/gateway/framework/jackson/JacksonConfig.java
deleted file mode 100644
index e216f8b..0000000
--- a/src/main/java/com/zsc/edu/gateway/framework/jackson/JacksonConfig.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.zsc.edu.gateway.framework.jackson;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import com.fasterxml.jackson.databind.module.SimpleModule;
-import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
-import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
-
-import java.text.SimpleDateFormat;
-
-@Configuration
-public class JacksonConfig {
-
-
-    @Bean
-    public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
-        return builder -> builder.serializationInclusion(JsonInclude.Include.NON_NULL)
-                .serializationInclusion(JsonInclude.Include.NON_EMPTY);
-    }
-}
diff --git a/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataPermission.java b/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataPermission.java
new file mode 100644
index 0000000..f5b42cb
--- /dev/null
+++ b/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataPermission.java
@@ -0,0 +1,22 @@
+package com.zsc.edu.gateway.framework.mybatisplus;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataPermission {
+    /**
+     * 目标表的别名
+     */
+    String tableAlias() default "";
+    /**
+     * 部门表的别名
+     */
+    String deptAlias() default "dept_id";
+
+    /**
+     * 用户表的别名
+     */
+    String userAlias() default "create_id";
+}
diff --git a/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataPermissionContext.java b/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataPermissionContext.java
new file mode 100644
index 0000000..9531671
--- /dev/null
+++ b/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataPermissionContext.java
@@ -0,0 +1,18 @@
+package com.zsc.edu.gateway.framework.mybatisplus;
+
+public class DataPermissionContext {
+
+    private static final ThreadLocal<DataPermission> contextHolder = new ThreadLocal<>();
+
+    public static void set(DataPermission dataPermission) {
+        contextHolder.set(dataPermission);
+    }
+
+    public static DataPermission get() {
+        return contextHolder.get();
+    }
+
+    public static void clear() {
+        contextHolder.remove();
+    }
+}
diff --git a/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataScopeAspect.java b/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataScopeAspect.java
new file mode 100644
index 0000000..275f43a
--- /dev/null
+++ b/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataScopeAspect.java
@@ -0,0 +1,44 @@
+package com.zsc.edu.gateway.framework.mybatisplus;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+
+/**
+ * 数据权限注解切面
+ */
+@Aspect
+@Component
+public class DataScopeAspect {
+
+    /**
+     * 环绕通知,拦截带有 @DataPermission 注解的方法
+     * @param joinPoint
+     * @throws Throwable
+     */
+    @Around("@annotation(DataPermission)()")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+
+        // 获取方法上的 @DataPermission 注解
+        DataPermission dataPermission = method.getAnnotation(DataPermission.class);
+
+        if (dataPermission != null) {
+            // 将注解信息存储在上下文中,供 MyBatis 拦截器使用
+            DataPermissionContext.set(dataPermission);
+        }
+
+        try {
+            // 执行目标方法
+            return joinPoint.proceed();
+        } finally {
+            // 方法执行完毕,清除数据权限上下文,避免内存泄露
+            DataPermissionContext.clear();
+        }
+    }
+}
diff --git a/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataScopeHandler.java b/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataScopeHandler.java
new file mode 100644
index 0000000..67adb71
--- /dev/null
+++ b/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataScopeHandler.java
@@ -0,0 +1,130 @@
+package com.zsc.edu.gateway.framework.mybatisplus;
+
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
+import com.zsc.edu.gateway.framework.security.SecurityUtil;
+import com.zsc.edu.gateway.framework.security.UserDetailsImpl;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.LongValue;
+import net.sf.jsqlparser.expression.Parenthesis;
+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 net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
+import net.sf.jsqlparser.schema.Column;
+import net.sf.jsqlparser.schema.Table;
+
+import java.lang.reflect.Method;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 数据权限拼装逻辑处理
+ *
+ */
+@Slf4j
+public class DataScopeHandler implements MultiDataPermissionHandler {
+
+    /**
+     * 获取数据权限 SQL 片段。
+     * <p> {@link MultiDataPermissionHandler#getSqlSegment(Table, Expression, String)} 方法不能覆盖原有的 where 数据,如果 return 了 null 则表示不追加任何 where 条件</p>
+     *
+     * @param table             所执行的数据库表信息,可以通过此参数获取表名和表别名
+     * @param where             原有的 where 条件信息
+     * @param mappedStatementId Mybatis MappedStatementId 根据该参数可以判断具体执行方法
+     * @return JSqlParser       条件表达式,返回的条件表达式会拼接在原有的表达式后面(不会覆盖原有的表达式)
+     */
+    @Override
+    public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
+//        try {
+            // 获取当前线程中的数据权限信息
+            DataPermission dataPermission = DataPermissionContext.get();
+            if (Objects.isNull(dataPermission)) {
+                return null;
+            }
+            return buildDataScopeByAnnotation(dataPermission);
+
+    }
+    /**
+     * DataScope注解方式,拼装数据权限
+     * @param dataScope
+     * @return
+     */
+    private Expression buildDataScopeByAnnotation(DataPermission dataScope) {
+        UserDetailsImpl userInfo = SecurityUtil.getUserInfo();
+        if (userInfo == null) {
+            return null;
+        }
+        Set<Long> dataScopeDeptIds = userInfo.getDataScopeDeptIds();
+        DataScopeType dataScopeType = userInfo.getRole().getDataScope();
+        // 本人ID
+        Long dataScopeCreateId = userInfo.getId();
+        // 本人部门ID
+        Long deptId = userInfo.getDept().getId();
+        // 表别名
+        String tableAlias = dataScope.tableAlias();
+        // 部门限制范围的字段名称
+        String deptAlias = dataScope.deptAlias();
+        // 本人限制范围的字段名称
+        String userAlias = dataScope.userAlias();
+
+        // 拼装数据权限
+        return switch (dataScopeType) {
+            // 全部数据权限
+            case DATA_SCOPE_ALL -> null;
+            // 本部门数据权限, 构建部门eq表达式
+            case DATA_SCOPE_DEPT -> equalsTo(tableAlias, deptAlias, deptId);
+            // 本部门及以下数据权限,构建部门in表达式
+            case DATA_SCOPE_DEPT_AND_CHILD -> inExpression(tableAlias, deptAlias, dataScopeDeptIds);
+            // 本人数据权限,构建本人eq表达式
+            case DATA_SCOPE_SELF -> equalsTo(tableAlias, userAlias, dataScopeCreateId);
+        };
+    }
+
+    /**
+     * 构建eq表达式
+     * @param tableAlias 表别名
+     * @param itemAlias 字段别名
+     * @param itemId id
+     * @return
+     */
+    private EqualsTo equalsTo(String tableAlias, String itemAlias, Long itemId) {
+        if (Objects.nonNull(itemId)) {
+            EqualsTo equalsTo = new EqualsTo();
+            equalsTo.withLeftExpression(buildColumn(tableAlias, itemAlias));
+            equalsTo.setRightExpression(new LongValue(itemId));
+            return equalsTo;
+        } else {
+            return null;
+        }
+    }
+
+    private InExpression inExpression(String tableAlias, String itemAlias, Set<Long> itemIds) {
+        if (!itemIds.isEmpty()) {
+            InExpression deptIdInExpression = new InExpression();
+            ParenthesedExpressionList<LongValue> deptIds = new ParenthesedExpressionList<>(itemIds.stream().map(LongValue::new).collect(Collectors.toList()));
+            deptIdInExpression.withLeftExpression(buildColumn(tableAlias, itemAlias));
+            deptIdInExpression.setRightExpression(deptIds);
+            return deptIdInExpression;
+        } else {
+            return null;
+        }
+    }
+
+
+    /**
+     * 构建Column
+     *
+     * @param tableAlias 表别名
+     * @param columnName 字段名称
+     * @return 带表别名字段
+     */
+    public static Column buildColumn(String tableAlias, String columnName) {
+        if (StringUtils.isNotEmpty(tableAlias)) {
+            columnName = tableAlias + "." + columnName;
+        }
+        return new Column(columnName);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataScopeType.java b/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataScopeType.java
new file mode 100644
index 0000000..6673eed
--- /dev/null
+++ b/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/DataScopeType.java
@@ -0,0 +1,36 @@
+package com.zsc.edu.gateway.framework.mybatisplus;
+
+import com.baomidou.mybatisplus.annotation.IEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+public enum DataScopeType implements IEnum<Integer> {
+    /**
+     * 全部数据权限
+     */
+    DATA_SCOPE_ALL(1),
+    /**
+     * 部门数据权限
+     */
+    DATA_SCOPE_DEPT(2),
+    /**
+     * 部门及以下数据权限
+     */
+    DATA_SCOPE_DEPT_AND_CHILD(3),
+    /**
+     * 仅个人数据权限
+     */
+    DATA_SCOPE_SELF(4);
+    /**
+     * 自定义数据权限
+     */
+//    DATA_SCOPE_CUSTOM(5);
+
+    private final int value;
+
+    @Override
+    public Integer getValue() {
+        return this.value;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/MybatisPlusConfig.java b/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/MybatisPlusConfig.java
index 42fc7db..2d9898d 100644
--- a/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/MybatisPlusConfig.java
+++ b/src/main/java/com/zsc/edu/gateway/framework/mybatisplus/MybatisPlusConfig.java
@@ -2,7 +2,9 @@ package com.zsc.edu.gateway.framework.mybatisplus;
 
 import com.baomidou.mybatisplus.annotation.DbType;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.apache.ibatis.plugin.Interceptor;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -13,18 +15,15 @@ import org.springframework.context.annotation.Configuration;
 @MapperScan(basePackages = "com.zsc.edu.gateway.modules.**.repo")
 @Configuration
 public class MybatisPlusConfig {
-
     @Bean
     public MybatisPlusInterceptor mybatisPlusInterceptor() {
         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        // 添加数据权限插件
+        DataPermissionInterceptor dataPermissionInterceptor = new DataPermissionInterceptor(new DataScopeHandler());
+        // 添加自定义的数据权限处理器
+        interceptor.addInnerInterceptor(dataPermissionInterceptor);
+        // 添加分页插件
         interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
-//        // 添加数据权限插件
-//       MyDataPermissionInterceptor dataPermissionInterceptor = new MyDataPermissionInterceptor();
-//        // 添加自定义的数据权限处理器
-//        dataPermissionInterceptor.setDataPermissionHandler(new MyDataPermissionHandler());
-//        interceptor.addInnerInterceptor(dataPermissionInterceptor);
         return interceptor;
     }
-
-
 }
diff --git a/src/main/java/com/zsc/edu/gateway/framework/security/JpaUserDetailsServiceImpl.java b/src/main/java/com/zsc/edu/gateway/framework/security/JpaUserDetailsServiceImpl.java
index 6331ec7..6294330 100644
--- a/src/main/java/com/zsc/edu/gateway/framework/security/JpaUserDetailsServiceImpl.java
+++ b/src/main/java/com/zsc/edu/gateway/framework/security/JpaUserDetailsServiceImpl.java
@@ -1,6 +1,8 @@
 package com.zsc.edu.gateway.framework.security;
 
+import com.zsc.edu.gateway.common.util.TreeUtil;
 import com.zsc.edu.gateway.exception.StateException;
+import com.zsc.edu.gateway.modules.system.entity.Dept;
 import com.zsc.edu.gateway.modules.system.entity.Menu;
 import com.zsc.edu.gateway.modules.system.entity.RoleAuthority;
 import com.zsc.edu.gateway.modules.system.entity.User;
@@ -8,6 +10,7 @@ import com.zsc.edu.gateway.modules.system.repo.AuthorityRepository;
 import com.zsc.edu.gateway.modules.system.repo.MenuRepository;
 import com.zsc.edu.gateway.modules.system.repo.RoleAuthoritiesRepository;
 import com.zsc.edu.gateway.modules.system.repo.UserRepository;
+import com.zsc.edu.gateway.modules.system.service.DeptService;
 import lombok.AllArgsConstructor;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
@@ -27,9 +30,9 @@ import java.util.stream.Collectors;
 public class JpaUserDetailsServiceImpl implements UserDetailsService {
 
     private final UserRepository userRepo;
-//    private  final RoleAuthoritiesRepository roleAuthoritiesRepository;
     private  final AuthorityRepository authorityRepository;
     private final MenuRepository menuRepository;
+    private final DeptService deptService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -38,7 +41,10 @@ public class JpaUserDetailsServiceImpl implements UserDetailsService {
         if (!user.getEnableState()) {
             throw new StateException("用户 '" + username + "' 已被禁用!请联系管理员");
         }
-
+        List<Dept> depts = deptService.listTree(user.deptId);
+        List<Dept> flat = TreeUtil.flat(depts, Dept::getChildren, d -> d.setChildren(null));
+        Set<Long> dataScopeDeptIds = flat.stream().map(Dept::getId).collect(Collectors.toSet());
+        user.setDataScopeDeptIds(dataScopeDeptIds);
 //        List<RoleAuthority> roleAuthorities= roleAuthoritiesRepository.selectByRoleId(user.getRoleId());
 //        user.role.authorities = authorityRepository.selectAuthoritiesByRoleId(user.getRoleId());
         List<Menu> menus = menuRepository.selectByRoleId(user.getRoleId());
diff --git a/src/main/java/com/zsc/edu/gateway/framework/security/UserDetailsImpl.java b/src/main/java/com/zsc/edu/gateway/framework/security/UserDetailsImpl.java
index 2d0be83..de286aa 100644
--- a/src/main/java/com/zsc/edu/gateway/framework/security/UserDetailsImpl.java
+++ b/src/main/java/com/zsc/edu/gateway/framework/security/UserDetailsImpl.java
@@ -34,14 +34,16 @@ public class UserDetailsImpl implements UserDetails {
     public Role role;
     public Set<Authority> authorities;
     public Set<String> permissions;
+    public Set<Long> dataScopeDeptIds;
 
-    public UserDetailsImpl(Long id, String username, String password, String name, Boolean enableState, Dept dept, Role role, Set<Authority> authorities, Set<String> permissions) {
+    public UserDetailsImpl(Long id, String username, String password, String name, Boolean enableState, Dept dept, Set<Long> dataScopeDeptIds, Role role, Set<Authority> authorities, Set<String> permissions) {
         this.id = id;
         this.username = username;
         this.password = password;
         this.enableState = enableState;
         this.name = name;
         this.dept = dept;
+        this.dataScopeDeptIds = dataScopeDeptIds;
         this.role = role;
         this.authorities = authorities;
         this.permissions = permissions;
@@ -55,6 +57,7 @@ public class UserDetailsImpl implements UserDetails {
                 user.name,
                 user.enableState,
                 user.dept,
+                user.dataScopeDeptIds,
                 user.role,
                 user.role.authorities,
                 permissions
diff --git a/src/main/java/com/zsc/edu/gateway/modules/iot/product/controller/ProductController.java b/src/main/java/com/zsc/edu/gateway/modules/iot/product/controller/ProductController.java
index 1321243..6e1b098 100644
--- a/src/main/java/com/zsc/edu/gateway/modules/iot/product/controller/ProductController.java
+++ b/src/main/java/com/zsc/edu/gateway/modules/iot/product/controller/ProductController.java
@@ -2,6 +2,7 @@ package com.zsc.edu.gateway.modules.iot.product.controller;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zsc.edu.gateway.framework.mybatisplus.DataPermission;
 import com.zsc.edu.gateway.modules.iot.product.dto.ProductDto;
 import com.zsc.edu.gateway.modules.iot.product.entity.Product;
 import com.zsc.edu.gateway.modules.iot.product.query.ProductQuery;
@@ -26,8 +27,6 @@ public class ProductController {
 
     private final ProductService service;
 
-
-
     /**
      * 创建产品
      *
@@ -60,6 +59,7 @@ public class ProductController {
      * @param page  分页参数
      * @return Page<Device> 产品分页数据
      */
+    @DataPermission
     @GetMapping
     @PreAuthorize("hasAuthority('iot:product:query')")
     public Page<Product> page(Page<Product> page, ProductQuery query) {
diff --git a/src/main/java/com/zsc/edu/gateway/modules/iot/product/repo/ProductRepository.java b/src/main/java/com/zsc/edu/gateway/modules/iot/product/repo/ProductRepository.java
index efb4d2c..1f02143 100644
--- a/src/main/java/com/zsc/edu/gateway/modules/iot/product/repo/ProductRepository.java
+++ b/src/main/java/com/zsc/edu/gateway/modules/iot/product/repo/ProductRepository.java
@@ -1,10 +1,16 @@
 package com.zsc.edu.gateway.modules.iot.product.repo;
 
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.zsc.edu.gateway.framework.mybatisplus.DataPermission;
 import com.zsc.edu.gateway.modules.iot.product.entity.Product;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
+import java.util.List;
+
 
 /**
  * @author Yao
diff --git a/src/main/java/com/zsc/edu/gateway/modules/system/entity/Role.java b/src/main/java/com/zsc/edu/gateway/modules/system/entity/Role.java
index 40fc44c..7107a36 100644
--- a/src/main/java/com/zsc/edu/gateway/modules/system/entity/Role.java
+++ b/src/main/java/com/zsc/edu/gateway/modules/system/entity/Role.java
@@ -2,6 +2,7 @@ package com.zsc.edu.gateway.modules.system.entity;
 
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.zsc.edu.gateway.framework.mybatisplus.DataScopeType;
 import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.Setter;
@@ -23,6 +24,10 @@ public class Role extends BaseEntity {
      * 名称,唯一
      */
     public String name;
+    /**
+     * 数据权限
+     */
+    public DataScopeType dataScope;
 
     /**
      * 启用状态
diff --git a/src/main/java/com/zsc/edu/gateway/modules/system/entity/User.java b/src/main/java/com/zsc/edu/gateway/modules/system/entity/User.java
index bda4d53..27f0485 100644
--- a/src/main/java/com/zsc/edu/gateway/modules/system/entity/User.java
+++ b/src/main/java/com/zsc/edu/gateway/modules/system/entity/User.java
@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import lombok.*;
 
+import java.util.Set;
+
 /**
  * 用户
  *
@@ -59,6 +61,11 @@ public class User extends BaseEntity {
      */
     @TableField(exist = false)
     public Dept dept;
+    /**
+     * 所属部门及子部门ID
+     */
+    @TableField(exist = false)
+    public Set<Long> dataScopeDeptIds;
 
     /**
      * 角色ID
diff --git a/src/main/resources/mappers/system/UserMapper.xml b/src/main/resources/mappers/system/UserMapper.xml
index dbdb833..546ffe3 100644
--- a/src/main/resources/mappers/system/UserMapper.xml
+++ b/src/main/resources/mappers/system/UserMapper.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.zsc.edu.gateway.modules.system.repo.UserRepository">
-    <resultMap id="BaseResultMap" type="com.zsc.edu.gateway.modules.system.entity.User">
+    <resultMap id="UserMap" type="com.zsc.edu.gateway.modules.system.entity.User">
         <id column="id" jdbcType="BIGINT" property="id"/>
         <result column="username" jdbcType="VARCHAR" property="username"/>
         <result column="password" jdbcType="VARCHAR" property="password"/>
@@ -14,14 +14,19 @@
         <result column="address" jdbcType="VARCHAR" property="address"/>
         <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
         <result column="enable_state" jdbcType="BIT" property="enableState"/>
-        <collection property="role" ofType="com.zsc.edu.gateway.modules.system.entity.Role">
-            <id column="id" jdbcType="BIGINT" property="id"/>
-            <result column="name" jdbcType="VARCHAR" property="name"/>
-        </collection>
+        <association property="role" javaType="com.zsc.edu.gateway.modules.system.entity.Role">
+            <id column="role_id" jdbcType="BIGINT" property="id"/>
+            <result column="role_name" jdbcType="VARCHAR" property="name"/>
+            <result column="data_scope" jdbcType="INTEGER" property="dataScope" />
+        </association>
+        <association property="dept" javaType="com.zsc.edu.gateway.modules.system.entity.Dept">
+            <id column="dept_id" jdbcType="BIGINT" property="id"/>
+            <result column="dept_name" jdbcType="VARCHAR" property="name"/>
+        </association>
     </resultMap>
 
-    <resultMap id="BaseResultMap1" type="com.zsc.edu.gateway.modules.system.vo.UserVo">
-        <id column="id" jdbcType="BIGINT" property="id"/>
+    <resultMap id="UserVoMap" type="com.zsc.edu.gateway.modules.system.vo.UserVo">
+        <id column="id" jdbcType="BIGINT" property="userId"/>
         <result column="username" jdbcType="VARCHAR" property="username"/>
         <result column="role_id" jdbcType="BIGINT" property="roleId"/>
         <result column="dept_id" jdbcType="BIGINT" property="deptId"/>
@@ -36,21 +41,20 @@
         id, username, password, email, phone, create_time
     </sql>
 
-    <select id="selectByUsername" parameterType="java.lang.String" resultMap="BaseResultMap">
-        select
-            *
-        from sys_user su
-                 left join sys_role sr on su.role_id = sr.id
+    <select id="selectByUsername" parameterType="java.lang.String" resultMap="UserMap">
+        select su.*, sr.name as role_name, sr.data_scope, sd.name as dept_name from sys_user su
+        left join sys_role sr on su.role_id = sr.id
+        left join sys_dept sd on su.dept_id = sd.id
         where su.username = #{username,jdbcType=VARCHAR}
     </select>
 
-    <select id="page" resultType="com.zsc.edu.gateway.modules.system.vo.UserVo" resultMap="BaseResultMap1">
+    <select id="page" resultType="com.zsc.edu.gateway.modules.system.vo.UserVo" resultMap="UserVoMap">
         select
             *
         from sys_user
         ${ew.customSqlSegment}
     </select>
-    <select id="detail" resultType="com.zsc.edu.gateway.modules.system.vo.UserVo" resultMap="BaseResultMap1">
+    <select id="detail" resultType="com.zsc.edu.gateway.modules.system.vo.UserVo" resultMap="UserVoMap">
         select
             *
         from sys_user
diff --git a/src/test/java/com/zsc/edu/gateway/service/system/UserServiceTest.java b/src/test/java/com/zsc/edu/gateway/service/system/UserServiceTest.java
index b740c71..d8fe0a6 100644
--- a/src/test/java/com/zsc/edu/gateway/service/system/UserServiceTest.java
+++ b/src/test/java/com/zsc/edu/gateway/service/system/UserServiceTest.java
@@ -49,20 +49,20 @@ public class UserServiceTest {
     private User user2;
     private User user3;
 
-    @BeforeEach
-    void setUp() {
-        user1 = UserBuilder.anUser().username("A张三").password("123456").enable(true).build();
-        repo.insert(user1);
-        user2 = UserBuilder.anUser().username("A李四").password("123456").enable(true).build();
-        repo.insert(user2);
-        user3 = UserBuilder.anUser().username("A王五").password("123456").enable(true).build();
-        repo.insert(user3);
-    }
-
-    @AfterEach
-    void tearDown() {
-        repo.delete(new LambdaQueryWrapper<User>().in(User::getUsername, "A张三", "A李四", "A王五", "A赵六", "A陈七", "A刘八"));
-    }
+//    @BeforeEach
+//    void setUp() {
+//        user1 = UserBuilder.anUser().username("A张三").password("123456").enable(true).build();
+//        repo.insert(user1);
+//        user2 = UserBuilder.anUser().username("A李四").password("123456").enable(true).build();
+//        repo.insert(user2);
+//        user3 = UserBuilder.anUser().username("A王五").password("123456").enable(true).build();
+//        repo.insert(user3);
+//    }
+//
+//    @AfterEach
+//    void tearDown() {
+//        repo.delete(new LambdaQueryWrapper<User>().in(User::getId, user1.getId(), user2.getId(), user3.getId()));
+//    }
 
     @Test
     void list() {
@@ -107,6 +107,12 @@ public class UserServiceTest {
     void updatePassword() {
         assertTrue(service.updatePassword("777777", user3.id));
     }
+    
+    @Test
+    void getOne() {
+        User byId = repo.selectByUsername("admin");
+        assertNotNull(byId.role.dataScope);
+    }
 
 
 }