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<String> 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;
|
}
|
}
|
}
|