diff --git a/CLAUDE.md b/CLAUDE.md
index 7f0585b..224d467 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -14,6 +14,7 @@
- `/sys/watchUserLogin` → **本地处理**:解密验证设备密码(防伪造)+ 单飞换 token + Redis 缓存
- 其余业务接口 → **手动 HttpUtils 透传**至源服务器 `http://10.10.10.228:29999`
- token 失效三重自愈:10秒主动探活 + 预过期刷新 + 业务接口 401 被动兜底
+- **全局 CORS**:CorsFilter 处理跨域(设备后台跨域访问网关),OPTIONS 预检直接短路返回
## 技术栈
diff --git a/src/main/java/com/renkang/gateway/filter/CorsFilter.java b/src/main/java/com/renkang/gateway/filter/CorsFilter.java
new file mode 100644
index 0000000..84e1c8d
--- /dev/null
+++ b/src/main/java/com/renkang/gateway/filter/CorsFilter.java
@@ -0,0 +1,52 @@
+package com.renkang.gateway.filter;
+
+import org.noear.solon.annotation.Component;
+import org.noear.solon.core.handle.Context;
+import org.noear.solon.core.handle.Filter;
+import org.noear.solon.core.handle.FilterChain;
+
+/**
+ * 全局 CORS 跨域过滤器。
+ *
+ * 健康设备后台(如 http://10.10.10.228:23001)跨域访问本网关时,浏览器要求响应带
+ * Access-Control-Allow-Origin 等头,否则被拦截。
+ *
+ * 处理策略(CORS 最佳实践,避免逐个枚举允许的头):
+ *
+ * - OPTIONS 预检请求:读取客户端请求的 Access-Control-Request-Headers,
+ * 原样回显到 Access-Control-Allow-Headers,并返回 200,不透传源服务器
+ * - 其他请求:放行到后续 Controller,响应头补充 CORS 头
+ *
+ * 这样无论前端(jeecg vue3 等)加什么自定义头(X-Version、X-TIMESTAMP、X-Sign 等)都不会被拦。
+ *
+ * @author renkang
+ */
+@Component(index = -200) // index 越小越先执行,确保 CORS 在业务透传前处理
+public class CorsFilter implements Filter {
+
+ @Override
+ public void doFilter(Context ctx, FilterChain chain) throws Throwable {
+ // 统一设置基础 CORS 响应头(OPTIONS 和普通请求都需要)
+ ctx.headerSet("Access-Control-Allow-Origin", "*");
+ ctx.headerSet("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH");
+ // 动态回显客户端预检请求的头,避免逐个枚举(最佳实践)
+ String reqHeaders = ctx.header("Access-Control-Request-Headers");
+ if (reqHeaders != null && !reqHeaders.isEmpty()) {
+ ctx.headerSet("Access-Control-Allow-Headers", reqHeaders);
+ } else {
+ // 无预检头时给一个兜底白名单(含 jeecg vue3 常用头)
+ ctx.headerSet("Access-Control-Allow-Headers",
+ "Content-Type, Authorization, X-Access-Token, X-Requested-With, X-Version, X-TIMESTAMP, X-Sign, token, Accept, Origin");
+ }
+ ctx.headerSet("Access-Control-Max-Age", "3600");
+
+ // OPTIONS 预检请求直接短路返回,不透传源服务器(降低源服务器压力)
+ if ("OPTIONS".equalsIgnoreCase(ctx.method())) {
+ ctx.status(200);
+ return; // 不进入后续过滤器/Controller
+ }
+
+ // 其他请求放行到后续处理(GatewayController 透传 / TokenController)
+ chain.doFilter(ctx);
+ }
+}