Spring Boot + MyBatis-Plus 數(shù)據(jù)權(quán)限管理入門技術(shù)方案
作者:farerboy
通過注解驅(qū)動的方式實現(xiàn)靈活的數(shù)據(jù)權(quán)限控制,可根據(jù)實際業(yè)務(wù)需求擴展權(quán)限類型和過濾規(guī)則。建議結(jié)合具體業(yè)務(wù)場景調(diào)整數(shù)據(jù)權(quán)限模型和SQL生成策略。?
一、需求分析
實現(xiàn)不同用戶/角色在訪問同一數(shù)據(jù)接口時,根據(jù)預(yù)設(shè)規(guī)則動態(tài)過濾數(shù)據(jù)。例如:
- 普通用戶只能查看自己創(chuàng)建的數(shù)據(jù)
- 部門管理員可查看本部門所有數(shù)據(jù)
- 超級管理員可查看全部數(shù)據(jù)
二、技術(shù)選型
- Spring Boot 3.x:基礎(chǔ)框架
- MyBatis-Plus 3.5+:數(shù)據(jù)訪問層增強
- Sa-Token/Spring Security:權(quán)限認證(可選)
- Jackson:JSON處理
- MySQL:數(shù)據(jù)庫
三、核心設(shè)計
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 實現(xiàn)方案
通過MyBatis-Plus的攔截器機制動態(tài)修改SQL,結(jié)合自定義注解實現(xiàn)聲明式數(shù)據(jù)權(quán)限控制。
圖片
四、實現(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)時使用)
*/
String deptAlias() default "";
/**
* 用戶ID字段名(默認create_by)
*/
String userColumn() default "create_by";
/**
* 部門ID字段名(默認dept_id)
*/
String deptColumn() default "dept_id";
}
4.3 實現(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 {
// 獲取當前用戶數(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 注冊攔截器
@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è)計
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 '訂單所屬部門'
);
六、擴展優(yōu)化
- 多表關(guān)聯(lián)處理:通過注解的deptAlias指定表別名
- 緩存優(yōu)化:緩存用戶權(quán)限數(shù)據(jù),避免頻繁查詢
- 租戶隔離:結(jié)合多租戶方案實現(xiàn)更復(fù)雜場景
- 性能監(jiān)控:記錄SQL修改日志用于審計
七、測試驗證
@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);
}
八、注意事項
- SQL注入防護:嚴格校驗自定義條件表達式
- 索引優(yōu)化:確保添加的過濾條件字段有合適索引
- 事務(wù)處理:在事務(wù)邊界內(nèi)保持數(shù)據(jù)權(quán)限一致性
- 性能影響:避免過度復(fù)雜的權(quán)限條件影響查詢性能
該方案通過注解驅(qū)動的方式實現(xiàn)靈活的數(shù)據(jù)權(quán)限控制,可根據(jù)實際業(yè)務(wù)需求擴展權(quán)限類型和過濾規(guī)則。建議結(jié)合具體業(yè)務(wù)場景調(diào)整數(shù)據(jù)權(quán)限模型和SQL生成策略。
責任編輯:武曉燕
來源:
小編程聊林