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 评论