SpringBoot结合MyBatis-Plus实现注解驱动的行级数据过滤,实现真正的数据隔离

引言

在企业级应用开发中,数据权限控制一直是个让人头疼的问题。你有没有遇到过这种情况:销售员能看到所有客户的资料,财务人员只能查看自己部门的数据,而管理员却要看到全部信息?这些问题的根源就是缺乏灵活的数据权限控制机制。

今天就来聊聊如何用SpringBoot结合MyBatis-Plus实现注解驱动的行级数据过滤,让不同角色的用户只能看到属于自己的数据,实现真正的数据隔离。

为什么需要数据权限控制?

数据安全的痛点

让我们先看看没有数据权限控制的系统存在什么问题:

数据泄露风险

  • 销售员可以查看所有客户的联系方式
  • 普通员工能看到高管的敏感数据
  • 不同部门间的数据没有有效隔离

业务合规问题

  • 违反数据隐私保护法规
  • 不符合企业内部数据管理规范
  • 无法满足审计要求

用户体验不佳

  • 用户看到大量无关数据
  • 数据查询性能下降
  • 界面信息过于复杂

维护成本高昂

  • 每个查询都需要手动添加权限过滤
  • 代码重复度高,难以维护
  • 权限逻辑与业务逻辑耦合

注解驱动的优势

开发效率高

  • 声明式编程,一行注解搞定权限
  • 无需修改现有业务代码
  • 权限控制与业务逻辑分离

灵活性强

  • 支持多种权限控制策略
  • 可以动态调整权限规则
  • 适应复杂的业务场景

维护性好

  • 权限逻辑集中管理
  • 统一的权限控制机制
  • 便于扩展和修改

核心架构设计

我们的数据权限行级过滤架构:

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   用户请求      │───▶│   注解拦截器     │───▶│   SQL重写器     │
│  (Controller)   │    │ (AOP Aspect)    │    │ (Interceptor)   │
└─────────────────┘    └──────────────────┘    └─────────────────┘
        │                        │                       │
        │ 发起查询               │                       │
        │───────────────────────▶│                       │
        │                        │ 检查数据权限注解      │
        │                        │──────────────────────▶│
        │                        │                       │
        │                        │ 获取用户权限信息      │
        │                        │──────────────────────▶│
        │                        │                       │
        │                        │ 动态生成SQL条件       │
        │                        │──────────────────────▶│
        │                        │                       │
        │                        │ 重写原始SQL           │
        │                        │──────────────────────▶│
        │                        │                       │
        │ 执行带权限的查询       │                       │
        │◀───────────────────────│                       │
        │                        │                       │

核心设计要点

1. 数据权限模型设计

// 数据权限实体类
@Data
@TableName("sys_data_permission")
public class DataPermission {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String permissionCode;    // 权限编码
    private String permissionName;    // 权限名称
    private String resourceType;      // 资源类型(用户、部门、角色等)
    private String resourceField;     // 资源字段
    private String resourceTable;     // 资源表名
    private String conditionExpression; // 权限表达式
    private Integer sort;             // 排序
    private Integer status;           // 状态(0-禁用 1-启用)
    private LocalDateTime createTime; // 创建时间
    private LocalDateTime updateTime; // 更新时间
}

// 用户数据权限关系
@Data
@TableName("sys_user_data_permission")
public class UserDataPermission {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long userId;              // 用户ID
    private Long permissionId;        // 权限ID
    private String customCondition;   // 自定义条件
    private LocalDateTime createTime; // 创建时间
}

// 数据权限注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataPermission {
    
    /**
     * 权限类型
     */
    PermissionType type() default PermissionType.CUSTOM;
    
    /**
     * 自定义权限条件
     * 支持SpEL表达式
     */
    String condition() default "";
    
    /**
     * 需要过滤的表名
     */
    String[] tables() default {};
    
    /**
     * 用户字段映射
     * key: 表字段名, value: 用户信息字段名
     */
    String[] fieldMapping() default {};
    
    enum PermissionType {
        /**
         * 自定义权限
         */
        CUSTOM,
        
        /**
         * 只能查看自己的数据
         */
        OWN,
        
        /**
         * 可以查看部门内数据
         */
        DEPT,
        
        /**
         * 可以查看所有数据
         */
        ALL
    }
}

2. 数据权限处理核心

// 数据权限上下文管理
@Component
public class DataPermissionContext {
    
    private static final ThreadLocal<UserPermissionInfo> context = new ThreadLocal<>();
    
    /**
     * 设置当前用户权限信息
     */
    public static void setCurrentUser(UserPermissionInfo permissionInfo) {
        context.set(permissionInfo);
    }
    
    /**
     * 获取当前用户权限信息
     */
    public static UserPermissionInfo getCurrentUser() {
        return context.get();
    }
    
    /**
     * 清理上下文
     */
    public static void clear() {
        context.remove();
    }
}

// 用户权限信息
@Data
@Builder
public class UserPermissionInfo {
    private Long userId;              // 用户ID
    private String username;          // 用户名
    private Long departmentId;        // 部门ID
    private List<String> roleCodes;   // 角色编码列表
    private List<Long> dataPermissions; // 数据权限ID列表
    private PermissionLevel permissionLevel; // 权限级别
    
    public enum PermissionLevel {
        ALL,      // 所有数据
        DEPT,     // 部门数据
        OWN       // 自己的数据
    }
}

// 数据权限服务
@Service
public class DataPermissionService {
    
    @Autowired
    private DataPermissionMapper dataPermissionMapper;
    
    @Autowired
    private UserDataPermissionMapper userDataPermissionMapper;
    
    /**
     * 根据用户ID获取数据权限SQL片段
     */
    public String getPermissionSqlSegment(Long userId, String tableName) {
        try {
            // 获取用户权限信息
            UserPermissionInfo userPermission = getCurrentUserPermission(userId);
            
            // 根据权限级别生成SQL条件
            return generatePermissionSql(userPermission, tableName);
            
        } catch (Exception e) {
            // 发生异常时返回允许所有数据的条件
            return "1 = 1";
        }
    }
    
    /**
     * 获取用户权限信息
     */
    private UserPermissionInfo getCurrentUserPermission(Long userId) {
        // 从数据库或缓存中获取用户权限信息
        // 这里简化处理,实际项目中应该缓存权限信息
        
        UserPermissionInfo.UserPermissionInfoBuilder builder = UserPermissionInfo.builder()
            .userId(userId);
        
        // 获取用户数据权限
        List<UserDataPermission> userPermissions = userDataPermissionMapper
            .selectByUserId(userId);
        
        List<Long> permissionIds = userPermissions.stream()
            .map(UserDataPermission::getPermissionId)
            .collect(Collectors.toList());
        
        builder.dataPermissions(permissionIds);
        
        // 确定权限级别
        PermissionLevel level = determinePermissionLevel(permissionIds);
        builder.permissionLevel(level);
        
        return builder.build();
    }
    
    /**
     * 根据权限ID列表确定权限级别
     */
    private PermissionLevel determinePermissionLevel(List<Long> permissionIds) {
        if (permissionIds.contains(getAllPermissionId())) {
            return PermissionLevel.ALL;
        } else if (permissionIds.contains(getDeptPermissionId())) {
            return PermissionLevel.DEPT;
        } else {
            return PermissionLevel.OWN;
        }
    }
    
    /**
     * 生成权限SQL片段
     */
    private String generatePermissionSql(UserPermissionInfo permissionInfo, String tableName) {
        switch (permissionInfo.getPermissionLevel()) {
            case ALL:
                return "1 = 1";
            case DEPT:
                return tableName + ".department_id = " + permissionInfo.getDepartmentId();
            case OWN:
                return tableName + ".create_user = " + permissionInfo.getUserId();
            default:
                return "1 = 1";
        }
    }
    
    private Long getAllPermissionId() {
        return 1L; // 全部权限ID
    }
    
    private Long getDeptPermissionId() {
        return 2L; // 部门权限ID
    }
}

3. SQL拦截器实现

// 数据权限拦截器
@Intercepts({
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Component
@Slf4j
public class DataPermissionInterceptor implements Interceptor {

    @Autowired
    private DataPermissionService dataPermissionService;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取当前用户信息
        UserPermissionInfo currentUser = DataPermissionContext.getCurrentUser();
        if (currentUser == null) {
            // 如果没有用户信息,放行(可能是在初始化或后台任务)
            return invocation.proceed();
        }

        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        Object parameter = args[1];

        // 获取SQL语句
        String originalSql = getOriginalSql(mappedStatement, parameter);
        if (StringUtils.isEmpty(originalSql)) {
            return invocation.proceed();
        }

        // 分析SQL中的表名
        List<String> tableNames = extractTableNames(originalSql);
        
        // 为每个涉及的表生成权限条件
        Map<String, String> tablePermissionMap = new HashMap<>();
        for (String tableName : tableNames) {
            String permissionSql = dataPermissionService.getPermissionSqlSegment(
                currentUser.getUserId(), tableName);
            if (StringUtils.hasText(permissionSql)) {
                tablePermissionMap.put(tableName, permissionSql);
            }
        }

        // 如果没有权限限制,直接放行
        if (tablePermissionMap.isEmpty()) {
            return invocation.proceed();
        }

        // 重写SQL
        String rewroteSql = rewriteSqlWithPermission(originalSql, tablePermissionMap);
        
        log.debug("原SQL: {}", originalSql);
        log.debug("重写后SQL: {}", rewroteSql);

        // 创建新的MappedStatement
        MappedStatement newMappedStatement = copyMappedStatement(mappedStatement, rewroteSql);
        args[0] = newMappedStatement;

        // 继续执行
        return invocation.proceed();
    }

    /**
     * 获取原始SQL语句
     */
    private String getOriginalSql(MappedStatement mappedStatement, Object parameter) {
        // 简化的SQL获取逻辑
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        return boundSql.getSql();
    }

    /**
     * 从SQL中提取表名
     */
    private List<String> extractTableNames(String sql) {
        // 这里应该使用JSQLParser或自定义SQL解析器来准确提取表名
        // 为简化,使用简单的字符串分析(不推荐用于生产环境)
        List<String> tables = new ArrayList<>();
        
        // 示例逻辑:简单的字符串匹配
        // 实际应用中应该使用专门的SQL解析器
        String[] parts = sql.toUpperCase().split("\\s+");
        for (int i = 0; i < parts.length - 1; i++) {
            if ("FROM".equals(parts[i])) {
                String tablePart = parts[i + 1].split("JOIN\\s+")[0];
                tables.add(tablePart.replace("`", "").toLowerCase());
            }
        }
        
        return tables.stream().distinct().collect(Collectors.toList());
    }

    /**
     * 重写SQL添加权限条件
     */
    private String rewriteSqlWithPermission(String originalSql, Map<String, String> permissionMap) {
        // 这里应该使用JSQLParser来安全地修改SQL
        // 为简化,使用字符串替换(不推荐用于生产环境)
        
        String resultSql = originalSql;
        
        // 为每个表添加权限条件
        for (Map.Entry<String, String> entry : permissionMap.entrySet()) {
            String tableName = entry.getKey();
            String permissionCondition = entry.getValue();
            
            // 在WHERE子句中添加权限条件
            if (resultSql.toUpperCase().contains("WHERE")) {
                resultSql = resultSql.replaceAll(
                    "(?i)(WHERE\\s+)([^\\s]+)", 
                    "$1($2) AND " + permissionCondition
                );
            } else {
                // 如果没有WHERE子句,添加WHERE
                resultSql = resultSql + " WHERE " + permissionCondition;
            }
        }
        
        return resultSql;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以设置一些配置属性
    }

    // 其他辅助方法...
}

4. 注解驱动实现

// 数据权限切面
@Aspect
@Component
@Slf4j
public class DataPermissionAspect {

    @Autowired
    private UserService userService;

    /**
     * 在方法执行前设置数据权限上下文
     */
    @Before("@annotation(dataPermission) || @within(dataPermission)")
    public void setDataPermissionContext(JoinPoint joinPoint, DataPermission dataPermission) {
        try {
            // 获取当前用户信息
            UserPermissionInfo permissionInfo = getCurrentUserPermission();
            
            if (permissionInfo != null) {
                // 设置权限上下文
                DataPermissionContext.setCurrentUser(permissionInfo);
                log.debug("设置数据权限上下文: userId={}", permissionInfo.getUserId());
            }
        } catch (Exception e) {
            log.error("设置数据权限上下文失败", e);
        }
    }

    /**
     * 在方法执行后清理数据权限上下文
     */
    @After("@annotation(dataPermission) || @within(dataPermission)")
    public void clearDataPermissionContext(JoinPoint joinPoint, DataPermission dataPermission) {
        try {
            DataPermissionContext.clear();
            log.debug("清理数据权限上下文");
        } catch (Exception e) {
            log.error("清理数据权限上下文失败", e);
        }
    }

    /**
     * 获取当前用户权限信息
     */
    private UserPermissionInfo getCurrentUserPermission() {
        try {
            // 从SecurityContext或请求中获取当前用户
            // 这里简化处理,实际项目中应该从认证信息中获取
            String username = getCurrentUsername();
            if (StringUtils.isEmpty(username)) {
                return null;
            }

            // 获取用户详细信息
            User user = userService.getByUsername(username);
            if (user == null) {
                return null;
            }

            return UserPermissionInfo.builder()
                .userId(user.getId())
                .username(user.getUsername())
                .departmentId(user.getDepartmentId())
                .roleCodes(getUserRoles(user.getId()))
                .build();

        } catch (Exception e) {
            log.error("获取用户权限信息失败", e);
            return null;
        }
    }

    private String getCurrentUsername() {
        // 从Spring Security获取当前用户名
        // 这里简化处理
        return "test_user";
    }

    private List<String> getUserRoles(Long userId) {
        // 获取用户角色信息
        // 这里简化处理
        return Arrays.asList("SALES", "USER");
    }
}

// 业务服务层应用示例
@Service
public class CustomerService {

    @Autowired
    private CustomerMapper customerMapper;

    /**
     * 销售员只能查看自己的客户
     */
    @DataPermission(type = DataPermission.PermissionType.OWN)
    public List<Customer> getMyCustomers() {
        return customerMapper.selectList(null);
    }

    /**
     * 部门经理可以查看部门内所有客户
     */
    @DataPermission(type = DataPermission.PermissionType.DEPT)
    public List<Customer> getDeptCustomers() {
        return customerMapper.selectList(null);
    }

    /**
     * 财务可以查看所有客户
     */
    @DataPermission(type = DataPermission.PermissionType.ALL)
    public List<Customer> getAllCustomers() {
        return customerMapper.selectList(null);
    }

    /**
     * 自定义权限条件
     */
    @DataPermission(
        type = DataPermission.PermissionType.CUSTOM,
        condition = "#user.departmentId == #customer.departmentId && #customer.status == 'ACTIVE'"
    )
    public List<Customer> getActiveCustomersInDept() {
        return customerMapper.selectList(null);
    }
}

关键实现细节

1. 权限配置管理

# application.yml
data-permission:
  # 是否启用数据权限
  enabled: true
  # 默认权限类型
  default-type: OWN
  # 权限表配置
  tables:
    - name: customer
      user-field: create_user
      dept-field: department_id
    - name: order
      user-field: sales_id
      dept-field: department_id
    - name: product
      user-field: create_user
      dept-field: department_id
  # 权限级别配置
  levels:
    ALL:
      condition: "1 = 1"
    DEPT:
      condition: "{table}.department_id = {user.departmentId}"
    OWN:
      condition: "{table}.create_user = {user.userId}"
// 数据权限配置类
@Configuration
@ConfigurationProperties(prefix = "data-permission")
@Data
public class DataPermissionProperties {
    
    private boolean enabled = true;
    private PermissionType defaultType = PermissionType.OWN;
    private List<TableConfig> tables = new ArrayList<>();
    private Map<String, String> levelConditions = new HashMap<>();
    
    @Data
    public static class TableConfig {
        private String name;
        private String userField;
        private String deptField;
        private String allField;
    }
    
    public enum PermissionType {
        ALL, DEPT, OWN, CUSTOM
    }
}

2. 权限缓存优化

// 权限缓存服务
@Service
@Slf4j
public class PermissionCacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String PERMISSION_CACHE_PREFIX = "data_permission:";
    private static final long CACHE_EXPIRE_TIME = 300; // 5分钟
    
    /**
     * 获取用户权限缓存
     */
    public UserPermissionInfo getUserPermission(Long userId) {
        String cacheKey = PERMISSION_CACHE_PREFIX + userId;
        UserPermissionInfo permissionInfo = (UserPermissionInfo) redisTemplate.opsForValue().get(cacheKey);
        
        if (permissionInfo == null) {
            // 缓存未命中,从数据库加载
            permissionInfo = loadUserPermissionFromDb(userId);
            if (permissionInfo != null) {
                // 设置缓存
                redisTemplate.opsForValue().set(cacheKey, permissionInfo, CACHE_EXPIRE_TIME, TimeUnit.SECONDS);
            }
        }
        
        return permissionInfo;
    }
    
    /**
     * 清除用户权限缓存
     */
    public void clearUserPermissionCache(Long userId) {
        String cacheKey = PERMISSION_CACHE_PREFIX + userId;
        redisTemplate.delete(cacheKey);
        log.info("清除用户权限缓存: userId={}", userId);
    }
    
    /**
     * 批量清除权限缓存
     */
    public void clearAllPermissionCache() {
        Set<String> keys = redisTemplate.keys(PERMISSION_CACHE_PREFIX + "*");
        if (keys != null && !keys.isEmpty()) {
            redisTemplate.delete(keys);
            log.info("批量清除权限缓存,数量: {}", keys.size());
        }
    }
    
    private UserPermissionInfo loadUserPermissionFromDb(Long userId) {
        // 从数据库加载用户权限信息
        // 实际实现应该调用相应的Mapper
        return UserPermissionInfo.builder()
            .userId(userId)
            .permissionLevel(UserPermissionInfo.PermissionLevel.OWN)
            .build();
    }
}

3. 监控和统计

// 数据权限监控服务
@Service
@Slf4j
public class DataPermissionMonitor {
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    private final Counter permissionCheckCounter;
    private final Counter permissionDeniedCounter;
    private final Timer permissionCheckTimer;
    
    public DataPermissionMonitor(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        this.permissionCheckCounter = Counter.builder("data_permission_checks_total")
            .description("数据权限检查总次数")
            .register(meterRegistry);
        this.permissionDeniedCounter = Counter.builder("data_permission_denied_total")
            .description("数据权限拒绝次数")
            .register(meterRegistry);
        this.permissionCheckTimer = Timer.builder("data_permission_check_duration")
            .description("数据权限检查耗时")
            .register(meterRegistry);
    }
    
    /**
     * 记录权限检查
     */
    public void recordPermissionCheck(String tableName, boolean allowed) {
        permissionCheckCounter.increment();
        if (!allowed) {
            permissionDeniedCounter.increment();
        }
        log.info("数据权限检查: table={}, allowed={}", tableName, allowed);
    }
    
    /**
     * 记录权限检查耗时
     */
    public <T> T recordPermissionCheckDuration(Supplier<T> operation) {
        return permissionCheckTimer.record(operation);
    }
    
    /**
     * 获取权限统计信息
     */
    public PermissionStats getPermissionStats() {
        return PermissionStats.builder()
            .totalChecks((long) permissionCheckCounter.count())
            .deniedChecks((long) permissionDeniedCounter.count())
            .averageCheckTime(permissionCheckTimer.mean(TimeUnit.MILLISECONDS))
            .build();
    }
}

// 权限统计信息
@Data
@Builder
public class PermissionStats {
    private Long totalChecks;
    private Long deniedChecks;
    private Double averageCheckTime;
    private Double denyRate;
}

业务场景应用

1. 客户管理系统

// 客户实体类
@Data
@TableName("customer")
public class Customer {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String customerName;
    private String contactPhone;
    private String contactEmail;
    private Long salesId;           // 销售员ID
    private Long departmentId;      // 部门ID
    private String status;          // 状态
    private Long createUserId;      // 创建用户ID
    private LocalDateTime createTime;
}

// 客户Mapper
@Mapper
public interface CustomerMapper extends BaseMapper<Customer> {
    
    /**
     * 销售员查看自己的客户
     */
    @DataPermission(type = DataPermission.PermissionType.OWN, tables = {"customer"})
    List<Customer> selectMyCustomers();
    
    /**
     * 部门经理查看部门内客户
     */
    @DataPermission(type = DataPermission.PermissionType.DEPT, tables = {"customer"})
    List<Customer> selectDeptCustomers();
    
    /**
     * 财务查看所有客户
     */
    @DataPermission(type = DataPermission.PermissionType.ALL, tables = {"customer"})
    List<Customer> selectAllCustomers();
    
    /**
     * 自定义查询:查看活跃客户
     */
    @DataPermission(
        type = DataPermission.PermissionType.CUSTOM,
        condition = "customer.status = 'ACTIVE' and customer.department_id = #user.departmentId"
    )
    List<Customer> selectActiveCustomers();
}

2. 订单管理系统

// 订单实体类
@Data
@TableName("order")
public class Order {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String orderNo;
    private Long customerId;
    private Long salesId;           // 销售员ID
    private BigDecimal amount;
    private String status;
    private Long departmentId;      // 部门ID
    private Long createUserId;      // 创建用户ID
    private LocalDateTime createTime;
}

// 订单服务
@Service
public class OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    /**
     * 销售员查看自己的订单
     */
    @DataPermission(type = DataPermission.PermissionType.OWN)
    public List<Order> getMyOrders() {
        return orderMapper.selectList(null);
    }
    
    /**
     * 部门经理查看部门订单统计
     */
    @DataPermission(type = DataPermission.PermissionType.DEPT)
    public OrderStatistics getDeptOrderStats() {
        // 获取部门订单统计数据
        List<Order> orders = orderMapper.selectList(null);
        return calculateOrderStatistics(orders);
    }
    
    /**
     * 财务查看所有订单
     */
    @DataPermission(type = DataPermission.PermissionType.ALL)
    public List<Order> getAllOrders() {
        return orderMapper.selectList(null);
    }
    
    private OrderStatistics calculateOrderStatistics(List<Order> orders) {
        // 计算订单统计信息
        return OrderStatistics.builder()
            .totalOrders(orders.size())
            .totalAmount(orders.stream().mapToDouble(o -> o.getAmount().doubleValue()).sum())
            .build();
    }
}

3. 权限管理界面

// 权限管理控制器
@RestController
@RequestMapping("/api/permissions")
public class PermissionController {
    
    @Autowired
    private DataPermissionService dataPermissionService;
    
    @Autowired
    private PermissionCacheService cacheService;
    
    @Autowired
    private DataPermissionMonitor monitorService;
    
    /**
     * 获取用户数据权限
     */
    @GetMapping("/user/{userId}")
    public ResponseEntity<ApiResponse<UserPermissionInfo>> getUserPermissions(@PathVariable Long userId) {
        UserPermissionInfo permissions = dataPermissionService.getUserPermissions(userId);
        return ResponseEntity.ok(ApiResponse.success(permissions, "获取成功"));
    }
    
    /**
     * 清除用户权限缓存
     */
    @DeleteMapping("/cache/user/{userId}")
    public ResponseEntity<ApiResponse<Void>> clearUserCache(@PathVariable Long userId) {
        cacheService.clearUserPermissionCache(userId);
        return ResponseEntity.ok(ApiResponse.success(null, "缓存清除成功"));
    }
    
    /**
     * 获取权限统计信息
     */
    @GetMapping("/stats")
    public ResponseEntity<ApiResponse<PermissionStats>> getPermissionStats() {
        PermissionStats stats = monitorService.getPermissionStats();
        return ResponseEntity.ok(ApiResponse.success(stats, "获取成功"));
    }
    
    /**
     * 测试权限检查
     */
    @PostMapping("/test")
    public ResponseEntity<ApiResponse<Boolean>> testPermission(@RequestBody PermissionTestRequest request) {
        boolean allowed = dataPermissionService.testPermission(
            request.getUserId(), 
            request.getTableName(), 
            request.getOperation()
        );
        return ResponseEntity.ok(ApiResponse.success(allowed, "测试完成"));
    }
}

// 权限测试请求
@Data
public class PermissionTestRequest {
    private Long userId;
    private String tableName;
    private String operation;
}

最佳实践建议

1. 性能优化

// 优化的数据权限服务
@Service
public class OptimizedDataPermissionService {
    
    @Autowired
    private PermissionCacheService cacheService;
    
    @Autowired
    private DataPermissionProperties properties;
    
    // 本地缓存,减少Redis访问
    private final LoadingCache<String, String> localPermissionCache = Caffeine.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(Duration.ofMinutes(1))
        .build(this::loadPermissionFromCache);
    
    /**
     * 获取权限SQL片段(带缓存优化)
     */
    public String getPermissionSqlSegmentOptimized(Long userId, String tableName) {
        try {
            String cacheKey = userId + ":" + tableName;
            return localPermissionCache.get(cacheKey);
        } catch (Exception e) {
            log.error("获取权限SQL片段失败", e);
            return "1 = 1"; // 失败时允许所有数据
        }
    }
    
    private String loadPermissionFromCache(String cacheKey) {
        String[] parts = cacheKey.split(":");
        Long userId = Long.parseLong(parts[0]);
        String tableName = parts[1];
        
        // 先从Redis缓存获取
        UserPermissionInfo permissionInfo = cacheService.getUserPermission(userId);
        if (permissionInfo != null) {
            return generatePermissionSql(permissionInfo, tableName);
        }
        
        // 缓存未命中,从数据库获取
        return generatePermissionSqlFromDb(userId, tableName);
    }
    
    private String generatePermissionSqlFromDb(Long userId, String tableName) {
        // 直接从数据库生成权限SQL,避免缓存层
        // 这里应该调用实际的数据权限逻辑
        return "1 = 1";
    }
}

2. 异常处理

// 数据权限异常处理
@ControllerAdvice
@Slf4j
public class DataPermissionExceptionHandler {
    
    @ExceptionHandler(DataPermissionException.class)
    public ResponseEntity<ErrorResponse> handleDataPermissionException(DataPermissionException ex) {
        log.warn("数据权限异常: {}", ex.getMessage());
        
        ErrorResponse error = ErrorResponse.builder()
            .code("DATA_PERMISSION_DENIED")
            .message(ex.getMessage())
            .timestamp(System.currentTimeMillis())
            .build();
            
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex) {
        log.error("数据权限处理异常", ex);
        
        ErrorResponse error = ErrorResponse.builder()
            .code("INTERNAL_ERROR")
            .message("数据权限处理失败")
            .timestamp(System.currentTimeMillis())
            .build();
            
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

// 数据权限异常
public class DataPermissionException extends RuntimeException {
    public DataPermissionException(String message) {
        super(message);
    }
    
    public DataPermissionException(String message, Throwable cause) {
        super(message, cause);
    }
}

3. 测试支持

// 数据权限测试工具
@Component
public class DataPermissionTestHelper {
    
    /**
     * 模拟用户权限上下文
     */
    public void mockUserPermission(Long userId, UserPermissionInfo.PermissionLevel level) {
        UserPermissionInfo mockPermission = UserPermissionInfo.builder()
            .userId(userId)
            .permissionLevel(level)
            .departmentId(1L)
            .build();
            
        DataPermissionContext.setCurrentUser(mockPermission);
    }
    
    /**
     * 清理测试上下文
     */
    public void clearTestContext() {
        DataPermissionContext.clear();
    }
    
    /**
     * 验证SQL是否包含权限条件
     */
    public boolean verifyPermissionInSql(String sql, String tableName) {
        // 验证生成的SQL是否包含权限过滤条件
        String permissionPattern = tableName + "\\.[a-zA-Z_]+\\s*=\\s*\\d+";
        return Pattern.compile(permissionPattern).matcher(sql).find();
    }
}

预期效果

通过这套数据权限行级过滤方案,我们可以实现:

  • 精准控制:不同角色用户只能访问授权的数据
  • 开发效率:注解驱动,一行代码搞定权限控制
  • 性能优化:智能缓存,减少重复权限计算
  • 安全合规:满足数据安全和隐私保护要求
  • 维护便利:权限逻辑集中管理,易于扩展

这套方案让数据权限控制从"复杂繁琐"变成了"简单优雅",大大提升了系统的安全性和开发效率。


欢迎关注公众号"服务端技术精选",获取更多技术干货!
欢迎大家加群交流。


标题:SpringBoot结合MyBatis-Plus实现注解驱动的行级数据过滤,实现真正的数据隔离
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/02/15/1770963673812.html

    评论
    0 评论
avatar

取消