Spring Boot + MyBatis-Plus 數(shù)據(jù)權(quán)限管理入門技術(shù)方案
一、需求分析
實(shí)現(xiàn)不同用戶/角色在訪問同一數(shù)據(jù)接口時(shí),根據(jù)預(yù)設(shè)規(guī)則動(dòng)態(tài)過濾數(shù)據(jù)。例如:
- 普通用戶只能查看自己創(chuàng)建的數(shù)據(jù)
- 部門管理員可查看本部門所有數(shù)據(jù)
- 超級(jí)管理員可查看全部數(shù)據(jù)
二、技術(shù)選型
- Spring Boot 3.x:基礎(chǔ)框架
- MyBatis-Plus 3.5+:數(shù)據(jù)訪問層增強(qiáng)
- Sa-Token/Spring Security:權(quán)限認(rèn)證(可選)
- Jackson:JSON處理
- MySQL:數(shù)據(jù)庫
三、核心設(shè)計(jì)
3.1 數(shù)據(jù)權(quán)限模型
@Data
public class DataScope {
// 權(quán)限類型:ALL, DEPT, SELF, CUSTOM
private String scopeType;
// 可見部門ID集合
private Set<Long> deptIds;
// 用戶ID
private Long userId;
// 自定義SQL條件
private String customCondition;
}3.2 實(shí)現(xiàn)方案
通過MyBatis-Plus的攔截器機(jī)制動(dòng)態(tài)修改SQL,結(jié)合自定義注解實(shí)現(xiàn)聲明式數(shù)據(jù)權(quán)限控制。
圖片
四、實(shí)現(xiàn)步驟
4.1 添加依賴(pom.xml)
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>4.2 定義數(shù)據(jù)權(quán)限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {
/**
* 部門表別名(多表關(guān)聯(lián)時(shí)使用)
*/
String deptAlias() default "";
/**
* 用戶ID字段名(默認(rèn)create_by)
*/
String userColumn() default "create_by";
/**
* 部門ID字段名(默認(rèn)dept_id)
*/
String deptColumn() default "dept_id";
}4.3 實(shí)現(xiàn)數(shù)據(jù)權(quán)限攔截器
@Intercepts({
@Signature(type = Executor.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class DataPermissionInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 獲取當(dāng)前用戶數(shù)據(jù)權(quán)限
DataScope dataScope = SecurityUtils.getDataScope();
if (dataScope == null || dataScope.isAllAccess()) {
return invocation.proceed();
}
StatementHandler handler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(handler);
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// 獲取注解信息
DataPermission dataPermission = getDataPermissionAnnotation(mappedStatement);
if (dataPermission != null) {
BoundSql boundSql = handler.getBoundSql();
String originalSql = boundSql.getSql();
String newSql = buildDataScopeSql(originalSql, dataScope, dataPermission);
metaObject.setValue("delegate.boundSql.sql", newSql);
}
return invocation.proceed();
}
private String buildDataScopeSql(String originalSql, DataScope dataScope, DataPermission annotation) {
StringBuilder where = new StringBuilder();
switch (dataScope.getScopeType()) {
case "DEPT":
where.append(annotation.deptColumn())
.append(" IN (")
.append(StringUtils.join(dataScope.getDeptIds(), ","))
.append(")");
break;
case "SELF":
where.append(annotation.userColumn())
.append(" = ")
.append(dataScope.getUserId());
break;
case "CUSTOM":
where.append(dataScope.getCustomCondition());
break;
}
if (originalSql.toUpperCase().contains("WHERE")) {
return originalSql + " AND " + where;
} else {
return originalSql + " WHERE " + where;
}
}
}4.4 注冊(cè)攔截器
@Configuration
public class MybatisPlusConfig {
@Bean
public DataPermissionInterceptor dataPermissionInterceptor() {
return new DataPermissionInterceptor();
}
}4.5 Service層應(yīng)用
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> {
@DataPermission(deptColumn = "order_dept_id")
public Page<Order> listOrders(Page<Order> page) {
return baseMapper.selectPage(page, null);
}
}五、初步數(shù)據(jù)表設(shè)計(jì)
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY,
username VARCHAR(50),
dept_id BIGINT
);
CREATE TABLE sys_order (
id BIGINT PRIMARY KEY,
order_no VARCHAR(50),
create_by BIGINT COMMENT '創(chuàng)建人ID',
order_dept_id BIGINT COMMENT '訂單所屬部門'
);六、擴(kuò)展優(yōu)化
- 多表關(guān)聯(lián)處理:通過注解的deptAlias指定表別名
- 緩存優(yōu)化:緩存用戶權(quán)限數(shù)據(jù),避免頻繁查詢
- 租戶隔離:結(jié)合多租戶方案實(shí)現(xiàn)更復(fù)雜場景
- 性能監(jiān)控:記錄SQL修改日志用于審計(jì)
七、測試驗(yàn)證
@Test
void testDataPermission() {
// 模擬普通用戶登錄
loginUser("user1", "DEPT", Set.of(1001L));
List<Order> orders = orderService.listOrders();
assertThat(orders).allMatch(order -> order.getOrderDeptId() == 1001);
// 模擬管理員登錄
loginUser("admin", "ALL", null);
orders = orderService.listOrders();
assertThat(orders).hasSize(100);
}八、注意事項(xiàng)
- SQL注入防護(hù):嚴(yán)格校驗(yàn)自定義條件表達(dá)式
- 索引優(yōu)化:確保添加的過濾條件字段有合適索引
- 事務(wù)處理:在事務(wù)邊界內(nèi)保持?jǐn)?shù)據(jù)權(quán)限一致性
- 性能影響:避免過度復(fù)雜的權(quán)限條件影響查詢性能
該方案通過注解驅(qū)動(dòng)的方式實(shí)現(xiàn)靈活的數(shù)據(jù)權(quán)限控制,可根據(jù)實(shí)際業(yè)務(wù)需求擴(kuò)展權(quán)限類型和過濾規(guī)則。建議結(jié)合具體業(yè)務(wù)場景調(diào)整數(shù)據(jù)權(quán)限模型和SQL生成策略。
































