package com.youlai.boot.plugin.mybatis; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler; import com.youlai.boot.common.annotation.DataPermission; import com.youlai.boot.common.enums.DataScopeEnum; import com.youlai.boot.security.model.RoleDataScope; import com.youlai.boot.security.util.SecurityUtils; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.sf.jsqlparser.expression.*; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.conditional.OrExpression; import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.ParenthesedSelect; import java.lang.reflect.Method; import java.util.List; /** * 数据权限控制器 *
* 支持多角色数据权限合并(并集策略): * - 如果任一角色是 ALL,则跳过数据权限过滤 * - 否则用 OR 连接各角色的数据权限条件 *
* 使用 JSQLParser 构建 SQL 条件,避免字符串拼接,提高代码安全性和可读性。
*
* @author zc
* @since 2021-12-10 13:28
*/
@Slf4j
public class MyDataPermissionHandler implements DataPermissionHandler {
private static final String DEPT_TABLE = "sys_dept";
private static final String DEPT_ID_COLUMN = "id";
private static final String DEPT_TREE_PATH_COLUMN = "tree_path";
/**
* 获取数据权限的sql片段
*
* @param where 查询条件
* @param mappedStatementId mapper接口方法的全路径
* @return sql片段
*/
@Override
@SneakyThrows
public Expression getSqlSegment(Expression where, String mappedStatementId) {
// 如果是未登录,或者是定时任务执行的SQL,或者是超级管理员,直接返回
if (SecurityUtils.getUserId() == null || SecurityUtils.isRoot()) {
return where;
}
// 获取当前用户的数据权限列表
List
* 多个角色的数据权限通过 OR 连接,实现并集效果
*
* @param annotation 数据权限注解
* @param dataScopes 数据权限列表
* @param where 原始查询条件
* @return 追加权限过滤后的查询条件
*/
private Expression dataScopeFilterWithUnion(DataPermission annotation, List
* 使用 JSQLParser 构建 Expression,避免字符串拼接
*
* @param deptAlias 部门表别名
* @param deptIdColumnName 部门ID字段名
* @param userAlias 用户表别名
* @param userIdColumnName 用户ID字段名
* @param roleDataScope 角色数据权限
* @return 数据权限条件表达式
*/
private Expression buildRoleDataScopeExpression(String deptAlias, String deptIdColumnName,
String userAlias, String userIdColumnName,
RoleDataScope roleDataScope) {
Column deptColumn = buildColumn(deptAlias, deptIdColumnName);
Column userColumn = buildColumn(userAlias, userIdColumnName);
Long deptId = SecurityUtils.getDeptId();
Long userId = SecurityUtils.getUserId();
DataScopeEnum dataScopeEnum = DataScopeEnum.getByValue(roleDataScope.getDataScope());
if (dataScopeEnum == null) {
return null;
}
return switch (dataScopeEnum) {
case ALL -> null; // 全部数据权限,不添加过滤条件
case DEPT_AND_SUB -> buildDeptAndSubExpression(deptColumn, deptId);
case DEPT -> buildEqualsExpression(deptColumn, deptId);
case SELF -> buildEqualsExpression(userColumn, userId);
case CUSTOM -> buildCustomDeptExpression(deptColumn, roleDataScope.getCustomDeptIds());
};
}
/**
* 构建列引用
*
* @param alias 表别名
* @param columnName 列名
* @return 列引用
*/
private Column buildColumn(String alias, String columnName) {
if (StrUtil.isNotBlank(alias)) {
return new Column(alias + StringPool.DOT + columnName);
}
return new Column(columnName);
}
/**
* 构建等于条件
*
* @param column 列
* @param value 值
* @return 等于表达式
*/
private Expression buildEqualsExpression(Column column, Long value) {
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(column);
equalsTo.setRightExpression(new LongValue(value));
return equalsTo;
}
/**
* 构建部门及子部门数据权限条件
*
* SQL: dept_id IN (SELECT id FROM sys_dept WHERE id = ? OR FIND_IN_SET(?, tree_path))
*
* @param deptColumn 部门列
* @param deptId 部门ID
* @return IN 子查询表达式
*/
private Expression buildDeptAndSubExpression(Column deptColumn, Long deptId) {
// 构建子查询: SELECT id FROM sys_dept WHERE id = ? OR FIND_IN_SET(?, tree_path)
PlainSelect subSelectBody = new PlainSelect();
subSelectBody.setFromItem(new Table(DEPT_TABLE));
subSelectBody.addSelectItems(new Column(DEPT_ID_COLUMN));
// WHERE id = ?
EqualsTo idEquals = new EqualsTo();
idEquals.setLeftExpression(new Column(DEPT_ID_COLUMN));
idEquals.setRightExpression(new LongValue(deptId));
// FIND_IN_SET(?, tree_path)
Function findInSet = new Function();
findInSet.setName("FIND_IN_SET");
findInSet.setParameters(new ExpressionList<>(
new LongValue(deptId),
new Column(DEPT_TREE_PATH_COLUMN)
));
// WHERE id = ? OR FIND_IN_SET(?, tree_path)
OrExpression whereClause = new OrExpression(idEquals, findInSet);
subSelectBody.setWhere(whereClause);
// 构建子查询
ParenthesedSelect subSelect = new ParenthesedSelect();
subSelect.setSelect(subSelectBody);
// 构建 IN 表达式
return new InExpression(deptColumn, subSelect);
}
/**
* 构建自定义部门数据权限条件
*
* SQL: dept_id IN (?, ?, ...)
*
* @param deptColumn 部门列
* @param customDeptIds 自定义部门ID列表
* @return IN 表达式,如果没有部门则返回 1=0
*/
private Expression buildCustomDeptExpression(Column deptColumn, List