diff --git a/pigx-common/pigx-common-data/src/main/java/com/pig4cloud/pigx/common/data/datascope/DataScopeInnerInterceptor.java b/pigx-common/pigx-common-data/src/main/java/com/pig4cloud/pigx/common/data/datascope/DataScopeInnerInterceptor.java index 62f61f311..c791cf28d 100644 --- a/pigx-common/pigx-common-data/src/main/java/com/pig4cloud/pigx/common/data/datascope/DataScopeInnerInterceptor.java +++ b/pigx-common/pigx-common-data/src/main/java/com/pig4cloud/pigx/common/data/datascope/DataScopeInnerInterceptor.java @@ -7,6 +7,8 @@ import com.baomidou.mybatisplus.core.toolkit.PluginUtils; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.Statement; @@ -19,12 +21,18 @@ import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; -import java.util.Collections; import java.util.List; import java.util.Map; /** - * @author legal + * 数据权限内部拦截器 + *

+ * 参照 MyBatis Plus {@code DataPermissionInterceptor} 的实现思路, + * 通过 JSqlParser 解析 SQL 为 AST,使用 {@code PlainSelect.setWhere()} 直接在 + * WHERE 子句上追加数据权限条件,避免子查询包装导致索引失效。 + * 对于 UNION 等复杂 SQL 或解析失败的情况,回退到子查询包装方式保证兼容性。 + * + * @author lengleng * @date 2020/11/29 */ @Slf4j @@ -62,155 +70,149 @@ public class DataScopeInnerInterceptor implements DataScopeInterceptor { mpBs.sql(String.format("SELECT %s FROM (%s) temp_data_scope", dataScope.getFunc().getType(), originalSql)); return; } - // 分割sql语句 - List where = splitSqlByWhere(originalSql); - // 获取表名 - String tableName = getTableName(originalSql); - List deptIds = dataScope.getDeptList(); - - // 1.无数据权限限制,则直接返回 0 条数据 - if (CollUtil.isEmpty(deptIds) && StrUtil.isBlank(dataScope.getUsername())) { - if (CollUtil.isNotEmpty(where) && where.size() > 1) { - originalSql = String.format("%s WHERE 1 = 2 AND %s", where.get(0), where.get(1)); - } else { - originalSql = String.format("%s WHERE 1 = 2", where.get(0)); - } - /*originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE 1 = 2", - dataScope.getFunc().getType(), originalSql);*/ - } - // 2.如果为本人权限 + 部门权限控制 - else if (StrUtil.isNotBlank(dataScope.getUsername()) && CollUtil.isNotEmpty(deptIds)) { - String join = CollectionUtil.join(deptIds, ","); - // 使用配置的组合模式(AND/OR),默认OR保持向后兼容 - String logicKeyword = dataScope.getLogicMode() != null ? dataScope.getLogicMode().getKeyword() - : DataScopeLogicEnum.OR.getKeyword(); - originalSql = String.format("%s WHERE %s.%s = '%s' %s %s.%s IN (%s)", - where.get(0), - tableName, - dataScope.getScopeUserName(), - dataScope.getUsername(), - logicKeyword, - tableName, - dataScope.getScopeDeptName(), - join - ); - if (CollUtil.isNotEmpty(where) && where.size() > 1) { - originalSql = originalSql + " AND " + where.get(1); - } - /*originalSql = String.format( - "SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s = '%s' %s temp_data_scope.%s IN (%s)", - dataScope.getFunc().getType(), originalSql, dataScope.getScopeUserName(), dataScope.getUsername(), - logicKeyword, dataScope.getScopeDeptName(), join);*/ - } - // 3. 如果为本人 - else if (StrUtil.isNotBlank(dataScope.getUsername())) { - if (CollUtil.isNotEmpty(where) && where.size() > 1) { - originalSql = String.format("%s WHERE %s.%s = '%s' AND %s", - where.get(0), - tableName, - dataScope.getScopeUserName(), - dataScope.getUsername(), - where.get(1) - ); - } else { - originalSql = String.format("%s WHERE %s.%s = '%s'", - where.get(0), - tableName, - dataScope.getScopeUserName(), - dataScope.getUsername() - ); - } - /*originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s = '%s'", - dataScope.getFunc().getType(), originalSql, dataScope.getScopeUserName(), dataScope.getUsername());*/ - } - // 4.部门权限控制 - else { - String join = CollectionUtil.join(deptIds, ","); - if (CollUtil.isNotEmpty(where) && where.size() > 1) { - originalSql = String.format("%s WHERE %s.%s IN (%s) AND %s", - where.get(0), - tableName, - dataScope.getScopeDeptName(), - join, - where.get(1) - ); - } else { - originalSql = String.format("%s WHERE %s.%s IN (%s)", - where.get(0), - tableName, - dataScope.getScopeDeptName(), - join - ); - } - /*originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s IN (%s)", - dataScope.getFunc().getType(), originalSql, dataScope.getScopeDeptName(), join);*/ - } - - mpBs.sql(originalSql); - } - - /** - * 获取表名(优先取别名,其次取原表名) - */ - private static String getTableName(String originalSql) { - if (StrUtil.isBlank(originalSql)) { - return ""; - } try { Statement statement = CCJSqlParserUtil.parse(originalSql); if (!(statement instanceof Select selectStatement)) { - return ""; + return; } - // 增加 PlainSelect 类型判断,避免 UNION 等复杂语句强转异常 + // UNION 等复杂语句无法直接操作 WHERE,回退到子查询包装 if (!(selectStatement.getSelectBody() instanceof PlainSelect plainSelect)) { - return ""; + mpBs.sql(buildSubQuerySql(originalSql, dataScope)); + return; } - FromItem fromItem = plainSelect.getFromItem(); - if (fromItem == null) { - return ""; + // 构建数据权限条件表达式 + Expression scopeExpression = buildScopeExpression(plainSelect, dataScope); + if (scopeExpression == null) { + return; } - // 优先使用别名 - if (fromItem.getAlias() != null) { - return fromItem.getAlias().getName(); - } - - // 其次使用原表名 - if (fromItem instanceof Table table) { - return table.getName(); + // 与原有 WHERE 条件合并(数据权限条件 AND 原条件) + Expression existingWhere = plainSelect.getWhere(); + if (existingWhere != null) { + plainSelect.setWhere(new AndExpression(scopeExpression, existingWhere)); + } else { + plainSelect.setWhere(scopeExpression); } + mpBs.sql(selectStatement.toString()); } catch (JSQLParserException e) { - log.error("JSQLParserException: {} -- sql: {}", e.getMessage(), originalSql); + log.warn("数据权限 SQL 解析失败,回退到子查询包装: {}", originalSql, e); + mpBs.sql(buildSubQuerySql(originalSql, dataScope)); } - return ""; } /** - * 分割SQL by WHERE - * @return 分割结果,失败返回空集合 + * 根据 DataScope 配置构建数据权限条件表达式 + *

+ * 使用 {@code CCJSqlParserUtil.parseCondExpression()} 解析条件字符串为 Expression 对象, + * 与 MyBatis Plus DataPermissionInterceptor 保持一致的实现方式。 + * + * @param plainSelect 解析后的 PlainSelect 对象 + * @param dataScope 数据权限参数 + * @return 数据权限条件表达式,无需处理时返回 null */ - private static List splitSqlByWhere(String sql) { - if (StrUtil.isBlank(sql)) { - return Collections.emptyList(); + private Expression buildScopeExpression(PlainSelect plainSelect, DataScope dataScope) + throws JSQLParserException { + List deptIds = dataScope.getDeptList(); + String username = dataScope.getUsername(); + + // 获取表引用(优先别名,其次表名),用于限定列名 + String tableRef = getTableReference(plainSelect.getFromItem()); + String prefix = StrUtil.isNotBlank(tableRef) ? tableRef + "." : ""; + + // 1. 无数据权限限制,返回 0 条数据 + if (CollUtil.isEmpty(deptIds) && StrUtil.isBlank(username)) { + return CCJSqlParserUtil.parseCondExpression("1 = 2"); } - try { - // 统一转大写匹配,避免遗漏大小写混合情况(如 Where / wHere) - String upperSql = sql.toUpperCase(); - int index = upperSql.indexOf(" WHERE "); - if (index != -1) { - return List.of( - sql.substring(0, index), - sql.substring(index + " WHERE ".length()) - ); - } - } catch (Exception e) { - log.error("分割SQL异常:{}--:{}", e, sql); + + // 2. 本人权限 + 部门权限:加括号保证与原有条件组合时的优先级 + if (StrUtil.isNotBlank(username) && CollUtil.isNotEmpty(deptIds)) { + String join = CollectionUtil.join(deptIds, ","); + String logicKeyword = dataScope.getLogicMode() != null ? dataScope.getLogicMode().getKeyword() + : DataScopeLogicEnum.OR.getKeyword(); + String condSql = String.format("(%s%s = '%s' %s %s%s IN (%s))", prefix, dataScope.getScopeUserName(), + escapeSql(username), logicKeyword, prefix, dataScope.getScopeDeptName(), join); + return CCJSqlParserUtil.parseCondExpression(condSql); } - return Collections.emptyList(); + + // 3. 仅本人权限 + if (StrUtil.isNotBlank(username)) { + String condSql = String.format("%s%s = '%s'", prefix, dataScope.getScopeUserName(), escapeSql(username)); + return CCJSqlParserUtil.parseCondExpression(condSql); + } + + // 4. 仅部门权限 + String join = CollectionUtil.join(deptIds, ","); + String condSql = String.format("%s%s IN (%s)", prefix, dataScope.getScopeDeptName(), join); + return CCJSqlParserUtil.parseCondExpression(condSql); + } + + /** + * 获取表引用名称(优先别名,其次表名) + * + * @param fromItem FROM 子句项 + * @return 表引用名称,无法获取时返回 null + */ + private String getTableReference(FromItem fromItem) { + if (fromItem == null) { + return null; + } + if (fromItem.getAlias() != null) { + return fromItem.getAlias().getName(); + } + if (fromItem instanceof Table table) { + return table.getName(); + } + return null; + } + + /** + * 回退方案:子查询包装(用于 UNION 等复杂 SQL 或解析失败时) + */ + private String buildSubQuerySql(String originalSql, DataScope dataScope) { + List deptIds = dataScope.getDeptList(); + String username = dataScope.getUsername(); + String selectType = dataScope.getFunc().getType(); + + // 无权限 + if (CollUtil.isEmpty(deptIds) && StrUtil.isBlank(username)) { + return String.format("SELECT %s FROM (%s) temp_data_scope WHERE 1 = 2", selectType, originalSql); + } + + // 本人 + 部门 + if (StrUtil.isNotBlank(username) && CollUtil.isNotEmpty(deptIds)) { + String join = CollectionUtil.join(deptIds, ","); + String logicKeyword = dataScope.getLogicMode() != null ? dataScope.getLogicMode().getKeyword() + : DataScopeLogicEnum.OR.getKeyword(); + return String.format( + "SELECT %s FROM (%s) temp_data_scope WHERE (temp_data_scope.%s = '%s' %s temp_data_scope.%s IN (%s))", + selectType, originalSql, dataScope.getScopeUserName(), escapeSql(username), logicKeyword, + dataScope.getScopeDeptName(), join); + } + + // 仅本人 + if (StrUtil.isNotBlank(username)) { + return String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s = '%s'", selectType, + originalSql, dataScope.getScopeUserName(), escapeSql(username)); + } + + // 仅部门 + String join = CollectionUtil.join(deptIds, ","); + return String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s IN (%s)", selectType, + originalSql, dataScope.getScopeDeptName(), join); + } + + /** + * 转义 SQL 字符串值中的单引号,防止 SQL 注入 + */ + private String escapeSql(String value) { + if (value == null) { + return ""; + } + return value.replace("'", "''"); } /** @@ -225,11 +227,11 @@ public class DataScopeInnerInterceptor implements DataScopeInterceptor { } else if (parameterObj instanceof Map) { for (Object val : ((Map) parameterObj).values()) { if (val instanceof DataScope) { - return (DataScope) val; - } - } - } - return null; - } + return (DataScope) val; + } + } + } + return null; + } -} \ No newline at end of file +}