package com.ruoyi.framework.interceptor;
|
|
import com.ruoyi.common.config.RuoYiConfig;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Component;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
|
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletResponse;
|
import java.nio.charset.StandardCharsets;
|
import java.util.Arrays;
|
import java.util.List;
|
|
/**
|
* 集群版对接中台时的写操作兜底拦截。
|
*
|
* <p>目的:前端隐藏按钮只能减少误触,不能阻止绕过页面直接调用接口。</p>
|
* <p>影响范围:访客后台非本地管理模块的新增、编辑、删除、状态变更、注销、导入等写接口。</p>
|
*/
|
@Component
|
public class MiddlePlatformReadonlyInterceptor implements HandlerInterceptor {
|
|
private static final String MESSAGE = "集群版对接中台时,当前模块仅支持查询和导出";
|
private static final String RESET_PASSWORD_MESSAGE = "集群版对接中台时,不支持重置密码";
|
|
/** 受中台只读规则约束的访客后台接口前缀。 */
|
private static final List<String> CONTROLLED_PREFIXES = Arrays.asList(
|
"/system/visit/area",
|
"/system/dept",
|
"/system/employee",
|
"/system/visitor/record",
|
"/system/visitor/machine",
|
"/system/door/device",
|
"/system/version/update",
|
"/system/protocol/config",
|
"/system/platform/admin",
|
"/system/role/manage"
|
);
|
|
/** 本地仍可维护的白名单模块:账号管理、系统配置、入园承诺、访客机版本、首页、操作日志。 */
|
private static final List<String> LOCAL_MODULE_PREFIXES = Arrays.asList(
|
"/system/platform/admin",
|
"/system/role/manage",
|
"/system/visitor/config",
|
"/system/protocol/config",
|
"/system/version/update",
|
"/system/dashboard",
|
"/system/operation/log"
|
);
|
|
/** 非白名单模块在中台模式下只允许这些查询、导出和模板下载接口通过。 */
|
private static final List<String> READONLY_SUFFIXES = Arrays.asList(
|
"/page",
|
"/detail",
|
"/tree",
|
"/overview",
|
"/roleOptions",
|
"/permissionTree",
|
"/export",
|
"/importTemplate"
|
);
|
|
@Autowired
|
private RuoYiConfig ruoyiConfig;
|
|
/**
|
* 目的:在请求进入 Controller 前判断中台只读规则,阻止非白名单模块写操作。
|
*/
|
@Override
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
if (!ruoyiConfig.isConnectMiddlePlatform()) {
|
return true;
|
}
|
|
String path = request.getRequestURI();
|
String contextPath = request.getContextPath();
|
if (contextPath != null && contextPath.length() > 0 && path.startsWith(contextPath)) {
|
path = path.substring(contextPath.length());
|
}
|
|
if (!isControlledPath(path)) {
|
return true;
|
}
|
String method = request.getMethod();
|
// 放行预检请求,避免 CORS OPTIONS 被业务只读规则误拦截。
|
if (isPreflight(method)) {
|
return true;
|
}
|
// 账号管理属于本地白名单,但业务要求中台模式下不允许展示或调用重置密码。
|
if (path.startsWith("/system/platform/admin/resetPassword")) {
|
return reject(response, RESET_PASSWORD_MESSAGE);
|
}
|
// GET/HEAD 和明确的查询导出接口为只读请求;白名单模块按本地管理逻辑继续放行。
|
if (isLocalModule(path) || isReadonlyEndpoint(path) || isReadMethod(method)) {
|
return true;
|
}
|
return reject(response);
|
}
|
|
/**
|
* 目的:判断当前请求是否属于访客后台需要受控的接口范围。
|
*/
|
private boolean isControlledPath(String path) {
|
for (String prefix : CONTROLLED_PREFIXES) {
|
if (path.startsWith(prefix)) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
/**
|
* 目的:判断当前请求是否属于中台模式仍允许本地维护的白名单模块。
|
*/
|
private boolean isLocalModule(String path) {
|
for (String prefix : LOCAL_MODULE_PREFIXES) {
|
if (path.startsWith(prefix)) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
/**
|
* 目的:判断 POST 查询、导出和模板下载这类只读接口是否可放行。
|
*/
|
private boolean isReadonlyEndpoint(String path) {
|
for (String suffix : READONLY_SUFFIXES) {
|
if (path.endsWith(suffix)) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
/**
|
* 目的:识别 HTTP 层面的只读方法,支持原生 GET 查询接口。
|
*/
|
private boolean isReadMethod(String method) {
|
return "GET".equalsIgnoreCase(method) || "HEAD".equalsIgnoreCase(method);
|
}
|
|
/**
|
* 目的:识别 CORS 预检请求,避免 OPTIONS 被业务规则误拦截。
|
*/
|
private boolean isPreflight(String method) {
|
return "OPTIONS".equalsIgnoreCase(method);
|
}
|
|
/**
|
* 目的:使用默认只读提示拒绝当前写操作请求。
|
*/
|
private boolean reject(HttpServletResponse response) throws Exception {
|
return reject(response, MESSAGE);
|
}
|
|
/**
|
* 目的:按指定提示返回统一 Ajax JSON 错误。
|
*/
|
private boolean reject(HttpServletResponse response, String message) throws Exception {
|
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
response.setContentType("application/json;charset=UTF-8");
|
response.getWriter().write("{\"code\":500,\"msg\":\"" + message + "\"}");
|
return false;
|
}
|
}
|