基于注解与动态SQL的SpringBoot数据权限实现方案
项目概述 easy-data-scope 是一个轻量级的数据权限控制框架,通过拦截 MyBatis 执行流程,在运行时动态注入 SQL 条件,实现灵活的数据访问控制。支持 MyBatis、MyBatis-Plus 和 MyBatis-Flex 等主流 ORM 框架。开发者仅需使用注解即可完成权限配置,无需修改原有业务逻辑。
环境搭建
- 数据库准备 以用户表为例:
CREATE TABLE `user` (
`id` INT PRIMARY KEY,
`username` VARCHAR(50),
`age` INT
);
目标实现以下权限规则:
- 只能查看 id 为 1 的记录
- 只能查看 age 为 111 的用户
- 只能查看 age 为 222 的用户
- 可查看 age 为 111 或 222 的所有用户(合并条件)
- 添加基础依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
</dependencies>
- 引入核心组件
<dependency>
<groupId>cn.zlinchuan</groupId>
<artifactId>ds-mybatis</artifactId>
<version>1.0.1</version>
</dependency>
- 配置 application.yml
server:
port: 8001
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test_db
username: root
password: 123456
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- 启动类
@SpringBootApplication
public class DataScopeApplication {
public static void main(String[] args) {
SpringApplication.run(DataScopeApplication.class, args);
}
}
集成数据权限控制
需要实现 DataScopeFindRule 接口并注册为 Spring Bean,用于根据当前上下文生成数据范围信息。
@Component
public class CustomDataScopeRule implements DataScopeFindRule {
@Autowired
private AuthDatascopeMapper scopeMapper;
@Override
public List<DataScopeInfo> find(String[] keys) {
LoginUser currentUser = SecurityContextHolder.getCurrentUser();
if (currentUser == null || keys.length == 0) {
return Collections.emptyList();
}
QueryWrapper<AuthDatascopeEntity> wrapper = new QueryWrapper<>();
wrapper.in("datascope_key", keys);
wrapper.in("role_id", currentUser.getRoleIds());
List<AuthDatascopeEntity> records = scopeMapper.selectList(wrapper);
return records.stream().map(entity -> {
DataScopeInfo info = new DataScopeInfo();
info.setKey(entity.getDatascopeKey());
info.setTableName(entity.getDatascopeTbName());
info.setColumnName(entity.getDatascopeColName());
info.setOperator(entity.getDatascopeOpName());
info.setValue(entity.getDatascopeValue());
info.setSort(entity.getDatascopeSort());
return info;
}).collect(Collectors.toList());
}
}
数据权限存储表结构
CREATE TABLE auth_datascope (
id INT AUTO_INCREMENT PRIMARY KEY,
datascope_key VARCHAR(200) COMMENT '权限标识',
datascope_tb_name VARCHAR(500) COMMENT '表别名',
datascope_col_name VARCHAR(500) COMMENT '字段名',
datascope_op_name VARCHAR(10) COMMENT '操作符',
datascope_value VARCHAR(200) COMMENT '值',
role_id INT COMMENT '角色ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注解使用详解
@DataScope 是核心注解,作用于方法上,触发权限拦截。
基本属性说明
- keys: 权限键数组,传递给
find()方法用于查询对应规则 - logical: 连接符,默认为 AND,可选 WHERE、OR
- merge: 是否自动合并相同字段的等值条件为 IN 查询
- flag: 是否启用占位符模式,配合 XML 中的 {{_DATA_SCOPE_FLAG}} 使用
- template: 自定义多个 key 的组合模板
实际应用场景演示
场景一:限制查看特定 ID 用户
@DataScope(keys = "USER_BY_ID", logical = SqlConsts.WHERE)
public List<UserEntity> fetchById() {
return userMapper.selectList(null);
}
生成 SQL:
SELECT id, username, age FROM user WHERE (user.id = 1)
场景二:仅允许查看年龄为 111 的用户
@DataScope(keys = "USER_AGE_111", logical = SqlConsts.WHERE)
public List<UserEntity> listAge111() {
return userMapper.selectList(null);
}
输出 SQL:
SELECT id, username, age FROM user WHERE (user.age = 111)
场景三:同时查看年龄为 111 和 222 的用户(启用合并)
@DataScope(keys = {"USER_AGE_111", "USER_AGE_222"}, merge = true, logical = SqlConsts.WHERE)
public List<UserEntity> listAges() {
return userMapper.selectList(null);
}
最终 SQL 将被优化为 IN 查询:
SELECT id, username, age FROM user WHERE (user.age IN (111, 222))
场景四:使用 flag 占位符控制嵌套查询 Mapper 接口:
@DataScope(keys = {"USER_AGE_111", "USER_AGE_222"}, flag = true)
List<UserEntity> queryWithSubQuery();
XML 映射文件:
<select id="queryWithSubQuery" resultType="UserEntity">
SELECT * FROM (
SELECT * FROM user WHERE {{_DATA_SCOPE_FLAG}}
) t WHERE 1 = 1
</select>
执行效果:
SELECT * FROM (SELECT * FROM user WHERE user.age IN (111, 222)) t WHERE 1 = 1
场景五:自定义模板组合条件
@DataScope(
keys = {"USER_AGE_111", "USER_AGE_222"},
flag = true,
template = "{{USER_AGE_111}} OR {{USER_AGE_222}}"
)
List<UserEntity> customTemplateQuery();
结合上述 XML,生成如下 SQL:
SELECT * FROM (SELECT * FROM user WHERE user.age = 111 OR user.age = 222) t WHERE 1 = 1
总结 该方案通过 AOP + SQL 动态改写的方式,实现了非侵入式的数据权限管理。结合数据库配置与注解驱动,使权限逻辑集中化、可视化,极大提升了系统的安全性和可维护性。