diff --git a/script/sql/zfjd-refactoring-v1/1001-修改用于测试的账号密码.sql b/script/sql/zfjd-refactoring-v1/1001-修改用于测试的账号密码.sql
deleted file mode 100644
index b440bfef..00000000
--- a/script/sql/zfjd-refactoring-v1/1001-修改用于测试的账号密码.sql
+++ /dev/null
@@ -1,89 +0,0 @@
---测试表
-create table test_enf_demo
-(
- id bigint not null comment '主键'
- primary key,
- enf_text varchar(255) null comment '文本',
- enf_content longtext null comment '长文本',
- tenant_id varchar(20) default '000000' null comment '租户编号',
- dept_id bigint null comment '部门id',
- user_id bigint null comment '用户id',
- order_num int default 0 null comment '排序号',
- test_key varchar(255) null comment 'key键',
- value varchar(255) null comment '值',
- version int default 0 null comment '版本',
- create_dept varchar(100) null comment '创建部门',
- create_time datetime null comment '创建时间',
- create_by bigint null comment '创建人',
- update_time datetime null comment '更新时间',
- update_by bigint null comment '更新人',
- del_flag int default 0 null comment '删除标志'
-)
- comment 'enf测试单表';
-
-
--- 为 test_enf_demo 表生成 10000 条测试数据
--- 使用存储过程批量插入
-
-DROP PROCEDURE IF EXISTS generate_test_enf_demo_data;
-
-DELIMITER $$
-
-CREATE PROCEDURE generate_test_enf_demo_data()
-BEGIN
- DECLARE i INT DEFAULT 1;
- DECLARE batch_size INT DEFAULT 500;
- DECLARE current_batch INT DEFAULT 0;
-
- -- 关闭自动提交,提升插入性能
- SET autocommit = 0;
-
- WHILE i <= 10000 DO
- INSERT INTO test_enf_demo (
- id, enf_text, enf_content, tenant_id, dept_id, user_id,
- order_num, test_key, value, version,
- create_dept, create_time, create_by, update_time, update_by, del_flag
- ) VALUES (
- i,
- CONCAT('文本_', i),
- CONCAT('这是第', i, '条测试长文本内容,用于测试数据填充。'),
- '000000',
- 100 + FLOOR(RAND() * 50),
- 1000 + FLOOR(RAND() * 500),
- i,
- CONCAT('KEY_', LPAD(i, 5, '0')),
- CONCAT('VAL_', LPAD(i, 5, '0')),
- 0,
- CONCAT('部门_', FLOOR(RAND() * 20)),
- DATE_ADD('2024-01-01', INTERVAL FLOOR(RAND() * 730) DAY),
- 1,
- NOW(),
- 1,
- 0
- );
-
- -- 每 500 条提交一次
- SET current_batch = current_batch + 1;
- IF current_batch >= batch_size THEN
- COMMIT;
- SET current_batch = 0;
-END IF;
-
- SET i = i + 1;
-END WHILE;
-
- -- 提交剩余数据
-COMMIT;
-SET autocommit = 1;
-END$$
-
-DELIMITER ;
-
--- 执行存储过程
-CALL generate_test_enf_demo_data();
-
--- 清理存储过程
-DROP PROCEDURE IF EXISTS generate_test_enf_demo_data;
-
--- 验证数据量
-SELECT COUNT(*) AS total_count FROM test_enf_demo;
diff --git a/script/sql/zfjd-refactoring-v1/1001-生成测试数据.sql b/script/sql/zfjd-refactoring-v1/1001-生成测试数据.sql
new file mode 100644
index 00000000..05755e80
--- /dev/null
+++ b/script/sql/zfjd-refactoring-v1/1001-生成测试数据.sql
@@ -0,0 +1,107 @@
+--测试表
+create table test_enf_demo
+(
+ id bigint not null comment '主键'
+ primary key,
+ enf_text varchar(255) null comment '文本',
+ enf_content longtext null comment '长文本',
+ tenant_id varchar(20) default '000000' null comment '租户编号',
+ dept_id bigint null comment '部门id',
+ user_id bigint null comment '用户id',
+ order_num int default 0 null comment '排序号',
+ test_key varchar(255) null comment 'key键',
+ value varchar(255) null comment '值',
+ version int default 0 null comment '版本',
+ create_dept varchar(100) null comment '创建部门',
+ bureau_dept_id varchar(100) null comment '执法部门ID',
+ create_time datetime null comment '创建时间',
+ create_by bigint null comment '创建人',
+ update_time datetime null comment '更新时间',
+ update_by bigint null comment '更新人',
+ del_flag int default 0 null comment '删除标志'
+) comment 'enf测试单表';
+
+
+-- 为 test_enf_demo 表生成 10000 条测试数据
+-- 使用存储过程批量插入
+-- 为 test_enf_demo 表生成 100000 条测试数据
+-- bureau_dept_id 从 efcode_virtual_unit 表的 sys_dept_id 中随机选取
+
+DROP PROCEDURE IF EXISTS generate_test_enf_demo_data;
+
+DELIMITER $$
+
+CREATE PROCEDURE generate_test_enf_demo_data()
+BEGIN
+ DECLARE i INT DEFAULT 1;
+ DECLARE batch_size INT DEFAULT 1000;
+ DECLARE current_batch INT DEFAULT 0;
+ DECLARE v_dept_count INT DEFAULT 0;
+
+ -- 创建临时表缓存 sys_dept_id 列表,避免每次子查询
+ DROP TEMPORARY TABLE IF EXISTS tmp_dept_ids;
+ CREATE TEMPORARY TABLE tmp_dept_ids (
+ row_num INT AUTO_INCREMENT PRIMARY KEY,
+ sys_dept_id VARCHAR(100)
+ );
+INSERT INTO tmp_dept_ids (sys_dept_id)
+SELECT sys_dept_id FROM efcode_virtual_unit WHERE sys_dept_id IS NOT NULL;
+
+SELECT COUNT(*) INTO v_dept_count FROM tmp_dept_ids;
+
+-- 关闭自动提交,提升插入性能
+SET autocommit = 0;
+
+ WHILE i <= 100000 DO
+ INSERT INTO test_enf_demo (
+ id, enf_text, enf_content, tenant_id, dept_id, user_id,
+ order_num, test_key, value, version,
+ create_dept, bureau_dept_id, create_time, create_by, update_time, update_by, del_flag
+ ) VALUES (
+ i,
+ CONCAT('文本_', i),
+ CONCAT('这是第', i, '条测试长文本内容,用于测试数据填充。'),
+ '000000',
+ 100 + FLOOR(RAND() * 50),
+ 1000 + FLOOR(RAND() * 500),
+ i,
+ CONCAT('KEY_', LPAD(i, 5, '0')),
+ CONCAT('VAL_', LPAD(i, 5, '0')),
+ 0,
+ CONCAT('部门_', FLOOR(RAND() * 20)),
+ (SELECT sys_dept_id FROM tmp_dept_ids WHERE row_num = 1 + FLOOR(RAND() * v_dept_count) LIMIT 1),
+ DATE_ADD('2024-01-01', INTERVAL FLOOR(RAND() * 730) DAY),
+ 1,
+ NOW(),
+ 1,
+ 0
+ );
+
+ -- 每 1000 条提交一次
+ SET current_batch = current_batch + 1;
+ IF current_batch >= batch_size THEN
+ COMMIT;
+ SET current_batch = 0;
+END IF;
+
+ SET i = i + 1;
+END WHILE;
+
+ -- 提交剩余数据
+COMMIT;
+SET autocommit = 1;
+
+ -- 清理临时表
+ DROP TEMPORARY TABLE IF EXISTS tmp_dept_ids;
+END$$
+
+DELIMITER ;
+
+-- 执行存储过程
+CALL generate_test_enf_demo_data();
+
+-- 清理存储过程
+DROP PROCEDURE IF EXISTS generate_test_enf_demo_data;
+
+-- 验证数据量
+SELECT COUNT(*) AS total_count FROM test_enf_demo;
diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilter.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilter.java
new file mode 100644
index 00000000..0f63bdde
--- /dev/null
+++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilter.java
@@ -0,0 +1,49 @@
+package com.zdxt.enforcementcode.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 执法码数据权限过滤注解。
+ *
+ * 标注在 Mapper 方法或 Mapper 类上,声明该查询需要按虚拟单位树进行数据过滤。
+ *
+ * 过滤规则:
+ *
+ * - 超级管理员 → 全部放行,不追加过滤条件
+ * - 拥有 ViewAllDataRole 角色 → 全部放行
+ * - 拥有 ViewFilterDataRole 角色 → 根据用户所在部门在 efcode_virtual_unit 中的位置,
+ * 查找该节点及其子孙的 sys_dept_id,生成 IN 条件注入 SQL
+ * - 其他用户 → 追加 1=0 条件,无权访问任何数据
+ *
+ *
+ * @author zrb
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface EfcodeDataFilter {
+
+ /**
+ * 是否包含统计节点(efcode_virtual_unit.vir_include_statistics = '是')
+ */
+ boolean includeStatistics() default true;
+
+ /**
+ * 是否包含查询节点(efcode_virtual_unit.vir_include_search = '是')
+ */
+ boolean includeSearch() default true;
+
+ /**
+ * 业务表的别名(如 SQL 中使用了别名,如 "t"、"e" 等)。
+ *
+ * 为空表示不使用别名,直接使用列名。
+ */
+ String tableAlias() default "";
+
+ /**
+ * 业务表中用于匹配 sys_dept_id 的列名。
+ *
+ * 默认为 "bureau_dept_id"。
+ */
+ String column() default "bureau_dept_id";
+}
diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterAdvice.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterAdvice.java
new file mode 100644
index 00000000..7e6a65d7
--- /dev/null
+++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterAdvice.java
@@ -0,0 +1,48 @@
+package com.zdxt.enforcementcode.common.annotation;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * EfcodeDataFilter 注解方法拦截器。
+ *
+ * 在 Mapper 方法执行前将注解信息存入 ThreadLocal,执行后清除。
+ *
+ * @author zrb
+ */
+@Slf4j
+public class EfcodeDataFilterAdvice implements MethodInterceptor {
+
+ @Override
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+ Object target = invocation.getThis();
+ Method method = invocation.getMethod();
+ // 设置过滤注解
+ EfcodeDataFilterHelper.setFilter(getAnnotation(target, method));
+ try {
+ return invocation.proceed();
+ } finally {
+ EfcodeDataFilterHelper.removeFilter();
+ }
+ }
+
+ /**
+ * 获取 EfcodeDataFilter 注解:优先方法级别,其次类级别
+ */
+ private EfcodeDataFilter getAnnotation(Object target, Method method) {
+ EfcodeDataFilter annotation = method.getAnnotation(EfcodeDataFilter.class);
+ if (annotation != null) {
+ return annotation;
+ }
+ Class> targetClass = target.getClass();
+ // JDK 动态代理(Mapper)需要获取接口上的注解
+ if (Proxy.isProxyClass(targetClass)) {
+ targetClass = targetClass.getInterfaces()[0];
+ }
+ return targetClass.getAnnotation(EfcodeDataFilter.class);
+ }
+}
diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterConfig.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterConfig.java
new file mode 100644
index 00000000..b125d3e5
--- /dev/null
+++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterConfig.java
@@ -0,0 +1,37 @@
+package com.zdxt.enforcementcode.common.annotation;
+
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Role;
+
+/**
+ * 执法码数据权限过滤配置。
+ *
+ * 注册 {@link EfcodeDataFilterInterceptor} 到 MyBatis-Plus 拦截器链,
+ * 并注册 {@link EfcodeDataFilterPointcutAdvisor} 用于 AOP 拦截。
+ *
+ * @author zrb
+ */
+@Configuration
+public class EfcodeDataFilterConfig {
+
+ /**
+ * 将执法码数据过滤拦截器追加到 MyBatis-Plus 拦截器链中
+ */
+ @Autowired
+ public void configureEfcodeDataFilter(MybatisPlusInterceptor interceptor) {
+ interceptor.addInnerInterceptor(new EfcodeDataFilterInterceptor());
+ }
+
+ /**
+ * 执法码数据过滤切面注册器
+ */
+ @Bean
+ @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ public EfcodeDataFilterPointcutAdvisor efcodeDataFilterPointcutAdvisor() {
+ return new EfcodeDataFilterPointcutAdvisor();
+ }
+}
diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterHelper.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterHelper.java
new file mode 100644
index 00000000..652d283c
--- /dev/null
+++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterHelper.java
@@ -0,0 +1,38 @@
+package com.zdxt.enforcementcode.common.annotation;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * EfcodeDataFilter 注解上下文管理器。
+ *
+ * 通过 ThreadLocal 在 AOP 切面设置注解信息,在 MyBatis 拦截器中读取并使用。
+ *
+ * @author zrb
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class EfcodeDataFilterHelper {
+
+ private static final ThreadLocal FILTER_CACHE = new ThreadLocal<>();
+
+ /**
+ * 设置当前线程的数据过滤注解
+ */
+ public static void setFilter(EfcodeDataFilter filter) {
+ FILTER_CACHE.set(filter);
+ }
+
+ /**
+ * 获取当前线程的数据过滤注解
+ */
+ public static EfcodeDataFilter getFilter() {
+ return FILTER_CACHE.get();
+ }
+
+ /**
+ * 清除当前线程的数据过滤注解
+ */
+ public static void removeFilter() {
+ FILTER_CACHE.remove();
+ }
+}
diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterInterceptor.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterInterceptor.java
new file mode 100644
index 00000000..9a8c758e
--- /dev/null
+++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterInterceptor.java
@@ -0,0 +1,161 @@
+package com.zdxt.enforcementcode.common.annotation;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
+import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
+import com.zdxt.enforcementcode.common.util.DataFilterUtil;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
+import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.statement.select.PlainSelect;
+import net.sf.jsqlparser.statement.select.Select;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+
+import java.sql.SQLException;
+
+/**
+ * 执法码数据权限 MyBatis 拦截器。
+ *
+ * 在 SQL 执行前,根据 {@link EfcodeDataFilter} 注解配置和用户角色,
+ * 自动向 SELECT 语句的 WHERE 子句追加基于 bureau_dept_id 的数据过滤条件。
+ *
+ * @author zrb
+ */
+@Slf4j
+public class EfcodeDataFilterInterceptor implements InnerInterceptor {
+
+ @Override
+ public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,
+ RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
+ // 从 ThreadLocal 获取注解
+ EfcodeDataFilter filter = EfcodeDataFilterHelper.getFilter();
+ if (filter == null) {
+ return;
+ }
+
+ // 构建过滤 SQL 片段
+ String filterSql = buildFilterSql(filter);
+ if (filterSql == null) {
+ // null 表示全部放行,不追加条件
+ return;
+ }
+
+ // 解析并修改 SQL
+ try {
+ PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
+ String originalSql = mpBs.sql();
+ String newSql = appendWhere(originalSql, filterSql);
+ mpBs.sql(newSql);
+ } catch (Exception e) {
+ log.error("EfcodeDataFilter SQL注入异常: {}", e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 根据注解配置和用户角色,构建过滤 SQL 片段。
+ *
+ * 使用 SQL 子查询替代 Java 端拼接 IN 列表,避免部门数量过多导致 SQL 超长。
+ *
+ * @param filter 注解
+ * @return SQL 片段;null 表示全部放行;非 null 字符串追加到 WHERE
+ */
+ private String buildFilterSql(EfcodeDataFilter filter) {
+ // 超管或 ViewAllDataRole → 不过滤
+ if (DataFilterUtil.isFullAccess()) {
+ return null;
+ }
+
+ // ViewFilterDataRole → 构建子查询条件
+ if (DataFilterUtil.needFilter()) {
+ String userDeptId = LoginHelper.getDeptId();
+ if (StrUtil.isBlank(userDeptId)) {
+ return "1 = 0";
+ }
+
+ // 构建列名(支持别名)
+ String columnRef = buildColumnRef(filter.tableAlias(), filter.column());
+ String escapedDeptId = escapeSql(userDeptId);
+
+ // 使用子查询:通过 userDeptId 找到 virId,再通过 FIND_IN_SET 查子孙节点的 sys_dept_id
+ StringBuilder sb = new StringBuilder();
+ sb.append(columnRef).append(" IN (");
+ sb.append("SELECT eu.sys_dept_id FROM efcode_virtual_unit eu");
+ sb.append(" WHERE FIND_IN_SET(");
+ sb.append("(SELECT eu2.vir_id FROM efcode_virtual_unit eu2 WHERE eu2.sys_dept_id = '").append(escapedDeptId).append("' LIMIT 1)");
+ sb.append(", eu.vir_ancestors)");
+ sb.append(" AND eu.sys_dept_id IS NOT NULL AND eu.sys_dept_id != ''");
+
+ // includeSearch / includeStatistics 过滤条件
+ if (filter.includeSearch() || filter.includeStatistics()) {
+ sb.append(" AND (1=0");
+ if (filter.includeSearch()) {
+ sb.append(" OR eu.vir_include_search = '是'");
+ }
+ if (filter.includeStatistics()) {
+ sb.append(" OR eu.vir_include_statistics = '是'");
+ }
+ sb.append(")");
+ }
+ sb.append(")");
+
+ // 补充自身部门(子查询通过 FIND_IN_SET 只返回子孙,不含自身节点)
+ sb.append(" OR ").append(columnRef).append(" = '").append(escapedDeptId).append("'");
+
+ return sb.toString();
+ }
+
+ // 无权访问
+ return "1 = 0";
+ }
+
+ /**
+ * 构建列引用(支持表别名)
+ */
+ private String buildColumnRef(String tableAlias, String column) {
+ if (StringUtils.isNotBlank(tableAlias)) {
+ return tableAlias + "." + column;
+ }
+ return column;
+ }
+
+ /**
+ * 将过滤条件追加到 SQL 的 WHERE 子句中
+ */
+ private String appendWhere(String originalSql, String filterSql) throws JSQLParserException {
+ Select select = (Select) CCJSqlParserUtil.parse(originalSql);
+ if (select instanceof PlainSelect plainSelect) {
+ Expression filterExpression = CCJSqlParserUtil.parseCondExpression(filterSql);
+ // 用括号包裹过滤条件
+ ParenthesedExpressionList parenthesis = new ParenthesedExpressionList<>(filterExpression);
+ Expression where = plainSelect.getWhere();
+ if (where != null) {
+ plainSelect.setWhere(new AndExpression(where, parenthesis));
+ } else {
+ plainSelect.setWhere(parenthesis);
+ }
+ return plainSelect.toString();
+ }
+ // 非简单 SELECT(如 UNION),尝试在末尾追加
+ log.warn("EfcodeDataFilter: 无法解析为PlainSelect,跳过SQL过滤: {}", originalSql);
+ return originalSql;
+ }
+
+ /**
+ * 简单的 SQL 注入防护(转义单引号)
+ */
+ private String escapeSql(String value) {
+ if (value == null) {
+ return "";
+ }
+ return value.replace("'", "''");
+ }
+}
diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterPointcut.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterPointcut.java
new file mode 100644
index 00000000..e9d2cbfe
--- /dev/null
+++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterPointcut.java
@@ -0,0 +1,31 @@
+package com.zdxt.enforcementcode.common.annotation;
+
+import org.springframework.aop.support.StaticMethodMatcherPointcut;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * EfcodeDataFilter 注解切入点匹配器。
+ *
+ * 匹配方法或类上标注了 {@link EfcodeDataFilter} 注解的目标。
+ *
+ * @author zrb
+ */
+@SuppressWarnings("all")
+public class EfcodeDataFilterPointcut extends StaticMethodMatcherPointcut {
+
+ @Override
+ public boolean matches(Method method, Class> targetClass) {
+ // 优先匹配方法上的注解
+ if (method.isAnnotationPresent(EfcodeDataFilter.class)) {
+ return true;
+ }
+ // MyBatis 的 Mapper 通过 JDK 动态代理实现,需要检查接口
+ Class> targetClassRef = targetClass;
+ if (Proxy.isProxyClass(targetClassRef)) {
+ targetClassRef = targetClass.getInterfaces()[0];
+ }
+ return targetClassRef.isAnnotationPresent(EfcodeDataFilter.class);
+ }
+}
diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterPointcutAdvisor.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterPointcutAdvisor.java
new file mode 100644
index 00000000..fe3e9e37
--- /dev/null
+++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/annotation/EfcodeDataFilterPointcutAdvisor.java
@@ -0,0 +1,33 @@
+package com.zdxt.enforcementcode.common.annotation;
+
+import org.aopalliance.aop.Advice;
+import org.springframework.aop.Pointcut;
+import org.springframework.aop.support.AbstractPointcutAdvisor;
+
+/**
+ * EfcodeDataFilter 切面注册器。
+ *
+ * 将 {@link EfcodeDataFilterPointcut} 和 {@link EfcodeDataFilterAdvice} 组合注册为 Spring AOP Advisor。
+ *
+ * @author zrb
+ */
+public class EfcodeDataFilterPointcutAdvisor extends AbstractPointcutAdvisor {
+
+ private final Advice advice;
+ private final Pointcut pointcut;
+
+ public EfcodeDataFilterPointcutAdvisor() {
+ this.advice = new EfcodeDataFilterAdvice();
+ this.pointcut = new EfcodeDataFilterPointcut();
+ }
+
+ @Override
+ public Pointcut getPointcut() {
+ return this.pointcut;
+ }
+
+ @Override
+ public Advice getAdvice() {
+ return this.advice;
+ }
+}
diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/DataFilterUtil.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/DataFilterUtil.java
index 5ea6723f..3c2e6e6b 100644
--- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/DataFilterUtil.java
+++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/DataFilterUtil.java
@@ -1,17 +1,31 @@
package com.zdxt.enforcementcode.common.util;
+
+import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.zdxt.enforcementcode.domain.vo.EfcodeVirtualUnitVo;
import com.zdxt.enforcementcode.service.IEfcodeVirtualUnitService;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
+import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
+
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class DataFilterUtil {
+ // ======================== 角色常量 ========================
+
+ /** 拥有此角色的用户可查看全部数据(不做过滤) */
+ public static final String ROLE_VIEW_ALL_DATA = "ViewAllDataRole";
+
+ /** 拥有此角色的用户需要按虚拟单位树做数据过滤 */
+ public static final String ROLE_VIEW_FILTER_DATA = "ViewFilterDataRole";
+
// ======================== 区(ShenZhen_Areas)(2) 的子节点常量 ========================
/** 区 */
@@ -53,6 +67,116 @@ public class DataFilterUtil {
/** 深圳市直 */
public static final String ShenZhen_ShiZhi = "ShenZhen_ShiZhi";
+ // ======================== 权限判断方法 ========================
+
+ /**
+ * 判断当前用户是否拥有全部数据访问权限(无需过滤)。
+ *
+ * 满足以下任一条件即为全部放行:
+ *
+ * - 超级管理员
+ * - 拥有 ViewAllDataRole 角色
+ *
+ *
+ * @return true=全部放行, false=需要进一步判断
+ */
+ public static boolean isFullAccess() {
+ if (LoginHelper.isSuperAdmin()) {
+ return true;
+ }
+ LoginUser user = LoginHelper.getLoginUser();
+ if (user == null) {
+ return false;
+ }
+ Set rolePermission = user.getRolePermission();
+ return CollUtil.isNotEmpty(rolePermission) && rolePermission.contains(ROLE_VIEW_ALL_DATA);
+ }
+
+ /**
+ * 判断当前用户是否需要进行数据过滤(拥有 ViewFilterDataRole 角色)
+ *
+ * @return true=需要过滤, false=不需要
+ */
+ public static boolean needFilter() {
+ LoginUser user = LoginHelper.getLoginUser();
+ if (user == null) {
+ return false;
+ }
+ Set rolePermission = user.getRolePermission();
+ return CollUtil.isNotEmpty(rolePermission) && rolePermission.contains(ROLE_VIEW_FILTER_DATA);
+ }
+
+ /**
+ * 判断当前用户是否无权访问数据。
+ *
+ * 既不是超管、也没有 ViewAllDataRole、也没有 ViewFilterDataRole
+ *
+ * @return true=无权访问, false=有权限
+ */
+ public static boolean hasNoAccess() {
+ return !isFullAccess() && !needFilter();
+ }
+
+ /**
+ * 获取当前用户可统计的全部单位的 sys_dept_id 列表。
+ *
+ * 逻辑:
+ *
+ * - 超管或 ViewAllDataRole → 返回 null,表示不限制(调用方应不追加过滤条件)
+ * - ViewFilterDataRole → 根据用户所在部门在虚拟单位树中的位置,
+ * 查询该节点下所有子孙 sys_dept_id(包含自身)
+ * - 其他 → 返回空列表(无权访问任何数据)
+ *
+ *
+ * @param includeStatistics 是否包含统计节点
+ * @param includeSearch 是否包含查询节点
+ * @return sys_dept_id 列表;null 表示全部放行;空列表表示无权
+ */
+ public static List getUserAccessibleDeptIds(boolean includeStatistics, boolean includeSearch) {
+ // 全部放行
+ if (isFullAccess()) {
+ return null;
+ }
+ // 需要过滤
+ if (needFilter()) {
+ return getFilteredDeptIdsForCurrentUser(includeStatistics, includeSearch);
+ }
+ // 无权访问
+ return Collections.emptyList();
+ }
+
+ /**
+ * 针对 ViewFilterDataRole 用户,根据其部门ID查找虚拟单位树中对应节点及其子孙的 sys_dept_id
+ *
+ * @param includeStatistics 是否包含统计节点
+ * @param includeSearch 是否包含查询节点
+ * @return 用户可访问的 sys_dept_id 列表(含自身节点)
+ */
+ public static List getFilteredDeptIdsForCurrentUser(boolean includeStatistics, boolean includeSearch) {
+ String userDeptId = LoginHelper.getDeptId();
+ if (StrUtil.isBlank(userDeptId)) {
+ return Collections.emptyList();
+ }
+ IEfcodeVirtualUnitService service = SpringUtils.getBean(IEfcodeVirtualUnitService.class);
+ // 先通过 sys_dept_id 找到用户在虚拟单位树中的节点 vir_id
+ String virId = service.findVirIdBySysDeptId(userDeptId);
+ if (StrUtil.isBlank(virId)) {
+ return Collections.emptyList();
+ }
+ // 查询该节点及其子孙(包含自身)的 sys_dept_id
+ List descendantDeptIds = service.queryDescendants(virId, includeSearch, includeStatistics, false)
+ .stream()
+ .map(EfcodeVirtualUnitVo::getSysDeptId)
+ .filter(StrUtil::isNotBlank)
+ .distinct()
+ .collect(Collectors.toList());
+ // 补充自身节点的 sys_dept_id
+ if (!descendantDeptIds.contains(userDeptId)) {
+ descendantDeptIds.add(userDeptId);
+ }
+ return descendantDeptIds;
+ }
+
// ======================== 公开方法 ========================
/**
diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/TestEnfDemo.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/TestEnfDemo.java
index 52ebfeba..a0236849 100644
--- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/TestEnfDemo.java
+++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/TestEnfDemo.java
@@ -42,6 +42,11 @@ public class TestEnfDemo extends TenantEntity {
*/
private Long deptId;
+ /**
+ * 执法部门ID
+ */
+ private String bureauDeptId;
+
/**
* 用户id
*/
diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/TestEnfDemoMapper.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/TestEnfDemoMapper.java
index 63190856..7bcada36 100644
--- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/TestEnfDemoMapper.java
+++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/TestEnfDemoMapper.java
@@ -2,6 +2,7 @@ package com.zdxt.enforcementcode.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zdxt.enforcementcode.common.annotation.EfcodeDataFilter;
import com.zdxt.enforcementcode.domain.TestEnfDemo;
import com.zdxt.enforcementcode.domain.vo.TestEnfDemoVo;
import org.apache.ibatis.annotations.Mapper;
@@ -22,16 +23,19 @@ public interface TestEnfDemoMapper extends BaseMapperPlus customPageList(@Param("page") Page page, @Param("ew") Wrapper wrapper);
/**
* 手动分页 - 查询总数(不经过拦截器)
*/
+ @EfcodeDataFilter(column = "bureau_dept_id")
Long manualCount(@Param("ew") Wrapper wrapper);
/**
* 手动分页 - 查询当前页数据(SQL中自带LIMIT,不经过拦截器)
*/
+ @EfcodeDataFilter(column = "bureau_dept_id")
List manualSelectList(@Param("ew") Wrapper wrapper,
@Param("offset") int offset,
@Param("pageSize") int pageSize);
diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeVirtualUnitService.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeVirtualUnitService.java
index 3f13fc64..4e180438 100644
--- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeVirtualUnitService.java
+++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeVirtualUnitService.java
@@ -126,6 +126,14 @@ public interface IEfcodeVirtualUnitService {
*/
void updateUser(UnitUserBo bo);
+ /**
+ * 根据 sys_dept_id 查找对应的虚拟单位 vir_id
+ *
+ * @param sysDeptId 部门ID
+ * @return 对应的 vir_id,不存在则返回 null
+ */
+ String findVirIdBySysDeptId(String sysDeptId);
+
/**
* 删除执法人员
*
diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeVirtualUnitServiceImpl.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeVirtualUnitServiceImpl.java
index 77adcb97..6957ec8b 100644
--- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeVirtualUnitServiceImpl.java
+++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeVirtualUnitServiceImpl.java
@@ -343,6 +343,22 @@ public class EfcodeVirtualUnitServiceImpl implements IEfcodeVirtualUnitService {
sysUserMapper.update(null, uw);
}
+ /**
+ * 根据 sys_dept_id 查找对应的虚拟单位 vir_id
+ */
+ @Override
+ public String findVirIdBySysDeptId(String sysDeptId) {
+ if (StringUtils.isBlank(sysDeptId)) {
+ return null;
+ }
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(EfcodeVirtualUnit::getSysDeptId, sysDeptId);
+ lqw.select(EfcodeVirtualUnit::getVirId);
+ lqw.last("LIMIT 1");
+ EfcodeVirtualUnitVo vo = baseMapper.selectVoOne(lqw);
+ return vo != null ? vo.getVirId() : null;
+ }
+
/**
* 删除执法人员(逻辑删除)
*/
diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/TestEnfDemoServiceImpl.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/TestEnfDemoServiceImpl.java
index 90313f11..3f4889b1 100644
--- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/TestEnfDemoServiceImpl.java
+++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/TestEnfDemoServiceImpl.java
@@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
+import com.zdxt.enforcementcode.common.annotation.EfcodeDataFilter;
import com.zdxt.enforcementcode.domain.bo.TestEnfDemoBo;
import com.zdxt.enforcementcode.domain.vo.TestEnfDemoVo;
import com.zdxt.enforcementcode.domain.TestEnfDemo;
@@ -45,12 +46,13 @@ public class TestEnfDemoServiceImpl implements ITestEnfDemoService {
}
/**
- * 分页查询enf测试单列表
+ * 分页查询enf测试单列表(Service层注解示例:覆盖继承方法selectVoPage)
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return enf测试单分页列表
*/
+ @EfcodeDataFilter
@Override
public TableDataInfo queryPageList(TestEnfDemoBo bo, PageQuery pageQuery) {
LambdaQueryWrapper lqw = buildQueryWrapper(bo);
@@ -71,7 +73,9 @@ public class TestEnfDemoServiceImpl implements ITestEnfDemoService {
/**
* 全手动分页(方案A demo)
* 完全绕过PaginationInnerInterceptor拦截器,自己控制COUNT和LIMIT
+ * Service层注解示例:一个注解覆盖方法内多次Mapper调用(manualCount + manualSelectList)
*/
+ @EfcodeDataFilter
@Override
public TableDataInfo manualPageList(TestEnfDemoBo bo, PageQuery pageQuery) {
LambdaQueryWrapper lqw = buildQueryWrapper(bo);
@@ -91,11 +95,12 @@ public class TestEnfDemoServiceImpl implements ITestEnfDemoService {
}
/**
- * 查询符合条件的enf测试单列表
+ * 查询符合条件的enf测试单列表(Service层注解示例:覆盖继承方法selectVoList)
*
* @param bo 查询条件
* @return enf测试单列表
*/
+ @EfcodeDataFilter
@Override
public List queryList(TestEnfDemoBo bo) {
LambdaQueryWrapper lqw = buildQueryWrapper(bo);
diff --git a/zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/service/test/TestEnfDemoServiceTest.java b/zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/service/test/TestEnfDemoServiceTest.java
new file mode 100644
index 00000000..8d3011e2
--- /dev/null
+++ b/zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/service/test/TestEnfDemoServiceTest.java
@@ -0,0 +1,316 @@
+package com.zdxt.service.test;
+
+import cn.dev33.satoken.context.mock.SaTokenContextMockUtil;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import com.zdxt.enforcementcode.domain.bo.TestEnfDemoBo;
+import com.zdxt.enforcementcode.domain.vo.TestEnfDemoVo;
+import com.zdxt.enforcementcode.service.ITestEnfDemoService;
+import org.dromara.ZdxtAdminApplication;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.junit.jupiter.api.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * TestEnfDemoServiceImpl 单元测试
+ *
+ * 验证 @EfcodeDataFilter 注解在 Service 层和 Mapper 层的数据过滤效果。
+ *
+ *
+ * @author zrb
+ */
+@SpringBootTest(classes = ZdxtAdminApplication.class)
+@ActiveProfiles({"dev", "dev-cust-zrb"})
+@AutoConfigureMockMvc
+@DisplayName("TestEnfDemoService 数据过滤注解测试")
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class TestEnfDemoServiceTest {
+
+ @Autowired
+ private ITestEnfDemoService testEnfDemoService;
+
+ private static final String TEST_CLIENT_ID = "e5cd7e4891bf95d1d19206ce24a7b32e";
+ private static final String TENANT_ID = "000000";
+
+ @BeforeEach
+ public void setUp() {
+ SaTokenContextMockUtil.setMockContext();
+ MockHttpServletRequest mockRequest = new MockHttpServletRequest();
+ mockRequest.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
+ RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(mockRequest));
+ }
+
+ @AfterEach
+ public void tearDown() {
+ try {
+ StpUtil.logout();
+ } catch (Exception ignored) {
+ }
+ RequestContextHolder.resetRequestAttributes();
+ SaTokenContextMockUtil.clearContext();
+ }
+
+ /**
+ * 模拟超级管理员登录(全部放行)
+ */
+ private void loginAsSuperAdmin() {
+ LoginUser loginUser = new LoginUser();
+ loginUser.setUserId(1L);
+ loginUser.setUserType("sys_user");
+ loginUser.setTenantId(TENANT_ID);
+ loginUser.setUsername("admin");
+ loginUser.setNickname("超级管理员");
+ loginUser.setDeptId("103");
+ loginUser.setDeptName("研发部门");
+ loginUser.setRolePermission(new HashSet<>(Set.of("admin")));
+ loginUser.setMenuPermission(new HashSet<>(Set.of("*:*:*")));
+
+ SaLoginParameter param = new SaLoginParameter();
+ param.setExtra(LoginHelper.CLIENT_KEY, TEST_CLIENT_ID);
+ LoginHelper.login(loginUser, param);
+ }
+
+ /**
+ * 模拟 ViewAllDataRole 用户登录(全部放行)
+ */
+ private void loginAsViewAllDataUser() {
+ LoginUser loginUser = new LoginUser();
+ loginUser.setUserId(2L);
+ loginUser.setUserType("sys_user");
+ loginUser.setTenantId(TENANT_ID);
+ loginUser.setUsername("view_all_user");
+ loginUser.setNickname("全数据查看用户");
+ loginUser.setDeptId("103");
+ loginUser.setDeptName("研发部门");
+ loginUser.setRolePermission(new HashSet<>(Set.of("ViewAllDataRole")));
+ loginUser.setMenuPermission(new HashSet<>());
+
+ SaLoginParameter param = new SaLoginParameter();
+ param.setExtra(LoginHelper.CLIENT_KEY, TEST_CLIENT_ID);
+ LoginHelper.login(loginUser, param);
+ }
+
+ /**
+ * 模拟 ViewFilterDataRole 用户登录(需要数据过滤)
+ */
+ private void loginAsFilterDataUser(String deptId) {
+ LoginUser loginUser = new LoginUser();
+ loginUser.setUserId(3L);
+ loginUser.setUserType("sys_user");
+ loginUser.setTenantId(TENANT_ID);
+ loginUser.setUsername("filter_user");
+ loginUser.setNickname("过滤数据用户");
+ loginUser.setDeptId(deptId);
+ loginUser.setDeptName("过滤部门");
+ loginUser.setRolePermission(new HashSet<>(Set.of("ViewFilterDataRole")));
+ loginUser.setMenuPermission(new HashSet<>());
+
+ SaLoginParameter param = new SaLoginParameter();
+ param.setExtra(LoginHelper.CLIENT_KEY, TEST_CLIENT_ID);
+ LoginHelper.login(loginUser, param);
+ }
+
+ /**
+ * 模拟无任何数据权限角色的用户登录(无权访问)
+ */
+ private void loginAsNoAccessUser() {
+ LoginUser loginUser = new LoginUser();
+ loginUser.setUserId(4L);
+ loginUser.setUserType("sys_user");
+ loginUser.setTenantId(TENANT_ID);
+ loginUser.setUsername("no_access_user");
+ loginUser.setNickname("无权用户");
+ loginUser.setDeptId("103");
+ loginUser.setDeptName("研发部门");
+ loginUser.setRolePermission(new HashSet<>(Set.of("common")));
+ loginUser.setMenuPermission(new HashSet<>());
+
+ SaLoginParameter param = new SaLoginParameter();
+ param.setExtra(LoginHelper.CLIENT_KEY, TEST_CLIENT_ID);
+ LoginHelper.login(loginUser, param);
+ }
+
+ // ======================== queryPageList 测试(Service层注解,覆盖selectVoPage) ========================
+
+ @Test
+ @Order(1)
+ @DisplayName("超级管理员 - queryPageList - 全部放行,可查询到数据")
+ public void testQueryPageList_SuperAdmin() {
+ loginAsSuperAdmin();
+ TestEnfDemoBo bo = new TestEnfDemoBo();
+ PageQuery pageQuery = new PageQuery(10, 1);
+
+ TableDataInfo result = testEnfDemoService.queryPageList(bo, pageQuery);
+ assertNotNull(result);
+ System.out.println("[超管] queryPageList 总数: " + result.getTotal());
+ assertTrue(result.getTotal() > 0, "超级管理员应能查询到数据");
+ }
+
+ @Test
+ @Order(2)
+ @DisplayName("ViewAllDataRole用户 - queryPageList - 全部放行")
+ public void testQueryPageList_ViewAllData() {
+ loginAsViewAllDataUser();
+ TestEnfDemoBo bo = new TestEnfDemoBo();
+ PageQuery pageQuery = new PageQuery(10, 1);
+
+ TableDataInfo result = testEnfDemoService.queryPageList(bo, pageQuery);
+ assertNotNull(result);
+ System.out.println("[ViewAllData] queryPageList 总数: " + result.getTotal());
+ assertTrue(result.getTotal() > 0, "ViewAllDataRole用户应能查询到数据");
+ }
+
+ @Test
+ @Order(3)
+ @DisplayName("ViewFilterDataRole用户 - queryPageList - 数据过滤生效")
+ public void testQueryPageList_FilterData() {
+ // 使用一个在虚拟单位树中存在的部门ID
+ loginAsFilterDataUser("20210922211852148-9BC0-86CB268FF");
+ TestEnfDemoBo bo = new TestEnfDemoBo();
+ PageQuery pageQuery = new PageQuery(10, 1);
+
+ TableDataInfo result = testEnfDemoService.queryPageList(bo, pageQuery);
+ assertNotNull(result);
+ System.out.println("[ViewFilterData] queryPageList 总数: " + result.getTotal());
+ // 过滤后数据量应小于等于全量
+ }
+
+ @Test
+ @Order(4)
+ @DisplayName("无权用户 - queryPageList - 无法查询到任何数据")
+ public void testQueryPageList_NoAccess() {
+ loginAsNoAccessUser();
+ TestEnfDemoBo bo = new TestEnfDemoBo();
+ PageQuery pageQuery = new PageQuery(10, 1);
+
+ TableDataInfo result = testEnfDemoService.queryPageList(bo, pageQuery);
+ assertNotNull(result);
+ System.out.println("[无权用户] queryPageList 总数: " + result.getTotal());
+ assertEquals(0, result.getTotal(), "无权用户不应查询到任何数据");
+ }
+
+ // ======================== queryList 测试(Service层注解,覆盖selectVoList) ========================
+
+ @Test
+ @Order(5)
+ @DisplayName("超级管理员 - queryList - 全部放行")
+ public void testQueryList_SuperAdmin() {
+ loginAsSuperAdmin();
+ TestEnfDemoBo bo = new TestEnfDemoBo();
+
+ List result = testEnfDemoService.queryList(bo);
+ assertNotNull(result);
+ System.out.println("[超管] queryList 数量: " + result.size());
+ assertFalse(result.isEmpty(), "超级管理员应能查询到数据");
+ }
+
+ @Test
+ @Order(6)
+ @DisplayName("无权用户 - queryList - 查询结果为空")
+ public void testQueryList_NoAccess() {
+ loginAsNoAccessUser();
+ TestEnfDemoBo bo = new TestEnfDemoBo();
+
+ List result = testEnfDemoService.queryList(bo);
+ assertNotNull(result);
+ System.out.println("[无权用户] queryList 数量: " + result.size());
+ assertTrue(result.isEmpty(), "无权用户不应查询到任何数据");
+ }
+
+ // ======================== manualPageList 测试(Service层注解,覆盖多次Mapper调用) ========================
+
+ @Test
+ @Order(7)
+ @DisplayName("超级管理员 - manualPageList - 全部放行")
+ public void testManualPageList_SuperAdmin() {
+ loginAsSuperAdmin();
+ TestEnfDemoBo bo = new TestEnfDemoBo();
+ PageQuery pageQuery = new PageQuery(10, 1);
+
+ TableDataInfo result = testEnfDemoService.manualPageList(bo, pageQuery);
+ assertNotNull(result);
+ System.out.println("[超管] manualPageList 总数: " + result.getTotal());
+ assertTrue(result.getTotal() > 0, "超级管理员应能查询到数据");
+ }
+
+ @Test
+ @Order(8)
+ @DisplayName("无权用户 - manualPageList - 无法查询到任何数据")
+ public void testManualPageList_NoAccess() {
+ loginAsNoAccessUser();
+ TestEnfDemoBo bo = new TestEnfDemoBo();
+ PageQuery pageQuery = new PageQuery(10, 1);
+
+ TableDataInfo result = testEnfDemoService.manualPageList(bo, pageQuery);
+ assertNotNull(result);
+ System.out.println("[无权用户] manualPageList 总数: " + result.getTotal());
+ assertEquals(0, result.getTotal(), "无权用户不应查询到任何数据");
+ }
+
+ // ======================== customPageList 测试(Mapper层注解) ========================
+
+ @Test
+ @Order(9)
+ @DisplayName("超级管理员 - customPageList(Mapper注解) - 全部放行")
+ public void testCustomPageList_SuperAdmin() {
+ loginAsSuperAdmin();
+ TestEnfDemoBo bo = new TestEnfDemoBo();
+ PageQuery pageQuery = new PageQuery(10, 1);
+
+ TableDataInfo result = testEnfDemoService.customPageList(bo, pageQuery);
+ assertNotNull(result);
+ System.out.println("[超管] customPageList 总数: " + result.getTotal());
+ assertTrue(result.getTotal() > 0, "超级管理员应能查询到数据");
+ }
+
+ @Test
+ @Order(10)
+ @DisplayName("无权用户 - customPageList(Mapper注解) - 无法查询到任何数据")
+ public void testCustomPageList_NoAccess() {
+ loginAsNoAccessUser();
+ TestEnfDemoBo bo = new TestEnfDemoBo();
+ PageQuery pageQuery = new PageQuery(10, 1);
+
+ TableDataInfo result = testEnfDemoService.customPageList(bo, pageQuery);
+ assertNotNull(result);
+ System.out.println("[无权用户] customPageList 总数: " + result.getTotal());
+ assertEquals(0, result.getTotal(), "无权用户不应查询到任何数据");
+ }
+
+ // ======================== 对比验证:超管 vs 过滤用户数据量 ========================
+
+ @Test
+ @Order(11)
+ @DisplayName("数据量对比 - 超管全量 vs ViewFilterDataRole过滤量")
+ public void testDataComparison() {
+ // 先以超管身份查全量
+ loginAsSuperAdmin();
+ TestEnfDemoBo bo = new TestEnfDemoBo();
+ List allData = testEnfDemoService.queryList(bo);
+ long allCount = allData.size();
+ StpUtil.logout();
+
+ // 再以过滤用户身份查
+ loginAsFilterDataUser("20210922211852148-9BC0-86CB268FF");
+ List filteredData = testEnfDemoService.queryList(bo);
+ long filteredCount = filteredData.size();
+
+ System.out.println("[对比] 超管全量: " + allCount + ", 过滤用户: " + filteredCount);
+ assertTrue(filteredCount <= allCount, "过滤后数据量应小于等于全量数据");
+ }
+}