package com.ruoyi.common.interceptor; import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; import com.ruoyi.common.config.IgnoreTableConfig; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.framework.security.LoginUser; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.springframework.stereotype.Component; import java.sql.SQLException; import java.util.Locale; import java.util.Set; @Component public class DataScopeSqlInterceptor implements InnerInterceptor { private static final String DATA_SCOPE_ALL = "1"; private static final String DATA_SCOPE_CUSTOM = "2"; private static final String DATA_SCOPE_DEPT = "3"; private static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; private static final String DATA_SCOPE_SELF = "5"; private static final String DATA_SCOPE_MARKER = "/*data_scope*/"; @Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { LoginUser loginUser; try { loginUser = SecurityUtils.getLoginUser(); } catch (Exception ignored) { return; } if (shouldSkip(loginUser, boundSql.getSql())) { return; } // 获取主表 TableSegment tableSegment = resolveMainTable(boundSql.getSql()); // ====================== 【表白名单】直接放行 ====================== if (tableSegment == null || ignoreTable(tableSegment.tableName)) { return; } String condition = buildCondition(tableSegment.qualifier, loginUser); if (condition == null) { return; } String newSql = appendCondition(boundSql.getSql(), condition); if (newSql.equals(boundSql.getSql())) { return; } MetaObject metaObject = SystemMetaObject.forObject(boundSql); metaObject.setValue("sql", newSql); System.out.println("[DataScopeSqlInterceptor] rewrite: " + ms.getId()); System.out.println("[DataScopeSqlInterceptor] sql: " + newSql); } private boolean shouldSkip(LoginUser loginUser, String sql) { if (loginUser == null || loginUser.getUser() == null || loginUser.getUser().isAdmin()) { return true; } if (sql == null || sql.trim().isEmpty()) { return true; } String normalizedSql = sql.toLowerCase(Locale.ROOT); if (!normalizedSql.startsWith("select")) { return true; } if (normalizedSql.contains(DATA_SCOPE_MARKER)) { return true; } return DATA_SCOPE_ALL.equals(loginUser.getDataScope()); } private boolean ignoreTable(String tableName) { Set ignoreTables = IgnoreTableConfig.IGNORE_TABLES; return ignoreTables.contains(tableName); } private String buildCondition(String qualifier, LoginUser loginUser) { String prefix = qualifier + "."; String dataScope = loginUser.getDataScope(); if (DATA_SCOPE_SELF.equals(dataScope)) { return prefix + "create_user = " + loginUser.getUserId(); } if (DATA_SCOPE_DEPT.equals(dataScope)) { Long deptId = resolveDeptId(loginUser); return deptId == null ? null : prefix + "dept_id = " + deptId; } if (DATA_SCOPE_CUSTOM.equals(dataScope) || DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) { Long[] deptIds = loginUser.getDeptIds(); if (deptIds == null || deptIds.length == 0) { return null; } StringBuilder builder = new StringBuilder(prefix).append("dept_id in ("); for (int i = 0; i < deptIds.length; i++) { if (i > 0) { builder.append(", "); } builder.append(deptIds[i]); } return builder.append(')').toString(); } return null; } private Long resolveDeptId(LoginUser loginUser) { if (loginUser.getCurrentDeptId() != null) { return loginUser.getCurrentDeptId(); } Long[] deptIds = loginUser.getDeptIds(); return deptIds != null && deptIds.length > 0 ? deptIds[0] : null; } private String appendCondition(String sql, String condition) { int insertPos = findInsertPosition(sql); String prefixSql = sql.substring(0, insertPos); String suffixSql = sql.substring(insertPos); if (hasTopLevelKeyword(prefixSql, "where")) { return prefixSql + " AND " + DATA_SCOPE_MARKER + " " + condition + " " + suffixSql; } return prefixSql + " WHERE " + DATA_SCOPE_MARKER + " " + condition + " " + suffixSql; } private int findInsertPosition(String sql) { int orderBy = findTopLevelKeyword(sql, "order by"); int groupBy = findTopLevelKeyword(sql, "group by"); int having = findTopLevelKeyword(sql, "having"); int limit = findTopLevelKeyword(sql, "limit"); int union = findTopLevelKeyword(sql, "union"); int insertPos = sql.length(); insertPos = minPositive(insertPos, orderBy); insertPos = minPositive(insertPos, groupBy); insertPos = minPositive(insertPos, having); insertPos = minPositive(insertPos, limit); insertPos = minPositive(insertPos, union); return insertPos; } private int minPositive(int current, int candidate) { return candidate >= 0 && candidate < current ? candidate : current; } private boolean hasTopLevelKeyword(String sql, String keyword) { return findTopLevelKeyword(sql, keyword) >= 0; } private int findTopLevelKeyword(String sql, String keyword) { String normalizedSql = sql.toLowerCase(Locale.ROOT); String normalizedKeyword = keyword.toLowerCase(Locale.ROOT); int depth = 0; for (int i = 0; i <= normalizedSql.length() - normalizedKeyword.length(); i++) { char current = normalizedSql.charAt(i); if (current == '(') { depth++; continue; } if (current == ')') { depth = Math.max(0, depth - 1); continue; } if (depth > 0) { continue; } if (matchesKeyword(normalizedSql, i, normalizedKeyword)) { return i; } } return -1; } private boolean matchesKeyword(String sql, int index, String keyword) { if (!sql.regionMatches(index, keyword, 0, keyword.length())) { return false; } boolean startOk = index == 0 || !Character.isLetterOrDigit(sql.charAt(index - 1)); int endIndex = index + keyword.length(); boolean endOk = endIndex >= sql.length() || !Character.isLetterOrDigit(sql.charAt(endIndex)); return startOk && endOk; } private TableSegment resolveMainTable(String sql) { int fromIndex = findTopLevelKeyword(sql, "from"); if (fromIndex < 0) { return null; } String fromPart = sql.substring(fromIndex + 4).trim(); if (fromPart.isEmpty() || fromPart.charAt(0) == '(') { return null; } String[] tokens = fromPart.split("\\s+"); if (tokens.length == 0) { return null; } String rawTableName = trimToken(tokens[0]); if (rawTableName.isEmpty()) { return null; } String alias = null; if (tokens.length > 1) { String second = trimToken(tokens[1]); if ("as".equalsIgnoreCase(second) && tokens.length > 2) { alias = trimToken(tokens[2]); } else if (!isClauseKeyword(second)) { alias = second; } } String tableName = normalizeTableName(rawTableName); String qualifier = alias != null && !alias.isEmpty() ? alias : rawTableName; return new TableSegment(tableName, qualifier.replace("`", "")); } private String trimToken(String token) { if (token == null) { return ""; } return token.replace(",", "").trim(); } private String normalizeTableName(String tableName) { String normalized = tableName.replace("`", ""); int dotIndex = normalized.lastIndexOf('.'); if (dotIndex >= 0) { normalized = normalized.substring(dotIndex + 1); } return normalized; } private boolean isClauseKeyword(String token) { String normalized = token.toLowerCase(Locale.ROOT); return "left".equals(normalized) || "right".equals(normalized) || "inner".equals(normalized) || "outer".equals(normalized) || "join".equals(normalized) || "where".equals(normalized) || "order".equals(normalized) || "group".equals(normalized) || "limit".equals(normalized) || "union".equals(normalized) || "having".equals(normalized); } private static class TableSegment { private final String tableName; private final String qualifier; private TableSegment(String tableName, String qualifier) { this.tableName = tableName; this.qualifier = qualifier; } } }