From c2505eb70c0b60f21715b7332f03d92d850000a7 Mon Sep 17 00:00:00 2001 From: chengkai3 Date: Sun, 12 Apr 2026 23:00:19 +0800 Subject: [PATCH] fix: enhance cors origin configuration --- .env.example | 1 + .github/workflows/main.yml | 2 ++ MEMORY.md | 1 + README.md | 4 ++++ api/app/core/config.py | 39 +++++++++++++++++++++++++++++++++----- api/app/main.py | 1 + docker-compose.yml | 1 + memory/2026-04-12.md | 21 ++++++++++++++++++++ 8 files changed, 65 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index d4c7d7c..2e770d4 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,7 @@ NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8000 API_HOST=0.0.0.0 API_PORT=8000 API_CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 +API_CORS_ORIGIN_REGEX= DATABASE_URL=postgresql+psycopg://fquiz:fquiz@db:5432/fquiz FILE_VFS_ROOT=./data/vfs JWT_SECRET_KEY=change-this-in-production diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 64bd744..9a41531 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -155,6 +155,7 @@ jobs: API_HOST: ${API_HOST:-0.0.0.0} API_PORT: ${API_PORT:-8000} API_CORS_ORIGINS: ${API_CORS_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000} + API_CORS_ORIGIN_REGEX: ${API_CORS_ORIGIN_REGEX:-} DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg://fquiz:fquiz@db:5432/fquiz} JWT_SECRET_KEY: ${JWT_SECRET_KEY:-change-this-in-production} ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES:-15} @@ -197,6 +198,7 @@ jobs: API_HOST=0.0.0.0 API_PORT=8000 API_CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 + API_CORS_ORIGIN_REGEX= DATABASE_URL=postgresql+psycopg://fquiz:fquiz@db:5432/fquiz JWT_SECRET_KEY=change-this-in-production ACCESS_TOKEN_EXPIRE_MINUTES=15 diff --git a/MEMORY.md b/MEMORY.md index cc79c8e..aa22ea4 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -40,6 +40,7 @@ - `app.models` 包初始化需预加载全部模型模块,确保字符串关系(如 `"AuditLog"`)在启动阶段可解析。 - 部署 compose 中 DB 镜像应通过 `POSTGRES_IMAGE` 可配置,默认使用镜像站的 pgvector 镜像(`docker.m.daocloud.io/pgvector/pgvector:pg16`)。 - 宿主机 DB 暴露端口统一走 `POSTGRES_PORT`(默认 `5433`),用于规避与宿主机已有 PostgreSQL(常见 `5432`)冲突;容器内连接仍保持 `db:5432`。 +- CORS 来源控制采用“双轨配置”:`API_CORS_ORIGINS`(精确列表)+ `API_CORS_ORIGIN_REGEX`(正则,可选);`API_CORS_ORIGINS` 支持 `*` 和通配符域名并在后端转换为 `allow_origin_regex`。 - GitHub Actions 使用 `appleboy/ssh-action` 部署时,慢网环境需显式设置 `command_timeout`(建议 `45m`)并为 `docker compose pull` 增加重试,避免出现 `Run Command Timeout` 直接中断发布。 ## 前端视觉口径(2026-04-12) diff --git a/README.md b/README.md index 07b327a..9f19f58 100644 --- a/README.md +++ b/README.md @@ -124,4 +124,8 @@ npm run lint:web 说明: - `NEXT_PUBLIC_API_BASE_URL` 在 Next.js 中是构建期注入;如果修改该值,需要重新执行 `docker compose up --build`。 - 若使用 Docker Compose,默认 `DATABASE_URL` 指向容器内 `db` 服务(PostgreSQL)。 +- 若出现跨域(CORS)错误,请在 `.env` 配置: + - `API_CORS_ORIGINS`:精确来源列表(逗号分隔),如 `https://admin.example.com,http://localhost:3000` + - `API_CORS_ORIGIN_REGEX`:来源正则(可选),如 `https://.*\\.example\\.com` + - 支持在 `API_CORS_ORIGINS` 中使用通配符(如 `https://*.example.com`)或 `*`(仅建议开发调试) - 默认镜像源已配置为 `docker.m.daocloud.io`,并默认使用 `pgvector` 镜像;如你网络环境可直连 Docker Hub,可在 `.env` 中覆盖 `POSTGRES_IMAGE / PYTHON_BASE_IMAGE / NODE_BASE_IMAGE`。 diff --git a/api/app/core/config.py b/api/app/core/config.py index 4fa5d32..b3b7372 100644 --- a/api/app/core/config.py +++ b/api/app/core/config.py @@ -1,4 +1,5 @@ from functools import lru_cache +import re from typing import Literal from pydantic import field_validator @@ -11,6 +12,7 @@ class Settings(BaseSettings): api_host: str = "0.0.0.0" api_port: int = 8000 api_cors_origins: str = "http://localhost:3000,http://127.0.0.1:3000" + api_cors_origin_regex: str | None = None database_url: str = "sqlite:///./fquiz.db" file_vfs_root: str = "./data/vfs" @@ -45,11 +47,38 @@ class Settings(BaseSettings): @property def cors_origins(self) -> list[str]: - return [ - origin.strip() - for origin in self.api_cors_origins.split(",") - if origin.strip() - ] + origins: list[str] = [] + for origin in self.api_cors_origins.split(","): + normalized = origin.strip() + if not normalized: + continue + if normalized == "*" or "*" in normalized: + continue + origins.append(normalized) + return origins + + @property + def cors_origin_regex(self) -> str | None: + regex_parts: list[str] = [] + for origin in self.api_cors_origins.split(","): + normalized = origin.strip() + if not normalized: + continue + if normalized == "*": + regex_parts.append(".*") + continue + if "*" in normalized: + wildcard_regex = re.escape(normalized).replace(r"\*", ".*") + regex_parts.append(f"^{wildcard_regex}$") + + if self.api_cors_origin_regex: + normalized = self.api_cors_origin_regex.strip() + if normalized: + regex_parts.append(normalized) + + if not regex_parts: + return None + return "|".join(f"(?:{part})" for part in regex_parts) @lru_cache diff --git a/api/app/main.py b/api/app/main.py index d58d499..2f5c656 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -25,6 +25,7 @@ app = FastAPI( app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins, + allow_origin_regex=settings.cors_origin_regex, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], diff --git a/docker-compose.yml b/docker-compose.yml index ec6ab0b..0c81c95 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,7 @@ services: API_HOST: ${API_HOST:-0.0.0.0} API_PORT: ${API_PORT:-8000} API_CORS_ORIGINS: ${API_CORS_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000} + API_CORS_ORIGIN_REGEX: ${API_CORS_ORIGIN_REGEX:-} DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg://fquiz:fquiz@db:5432/fquiz} JWT_SECRET_KEY: ${JWT_SECRET_KEY:-change-this-in-production} ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES:-15} diff --git a/memory/2026-04-12.md b/memory/2026-04-12.md index df5e2e5..3169717 100644 --- a/memory/2026-04-12.md +++ b/memory/2026-04-12.md @@ -180,6 +180,27 @@ - 验证: - `cd web && npx eslint src/app/admin/layout.tsx` 通过。 +## 追加修复(CORS 跨域配置增强) + +- 触发问题: + - 前端调用 API 出现浏览器 CORS 拦截,需要支持更灵活的来源配置。 +- 处理: + - 后端 CORS 配置增强(`api/app/core/config.py` + `api/app/main.py`): + - 新增 `API_CORS_ORIGIN_REGEX` 配置项(可选)。 + - `API_CORS_ORIGINS` 支持 `*` 与通配符域名(如 `https://*.example.com`),内部转换为正则匹配。 + - `CORSMiddleware` 增加 `allow_origin_regex=settings.cors_origin_regex`。 + - 部署与环境模板同步: + - `.env.example` 新增 `API_CORS_ORIGIN_REGEX=`。 + - `docker-compose.yml` API 环境变量新增 `API_CORS_ORIGIN_REGEX` 透传。 + - `.github/workflows/main.yml` 生产 compose 模板与默认 `.env` 模板同步新增该变量。 + - 文档补充: + - `README.md` 增加 CORS 配置说明与示例。 +- 验证: + - `python3 -m compileall api/app/core/config.py api/app/main.py` 通过。 + - `docker compose config` 展开结果包含: + - `API_CORS_ORIGINS` + - `API_CORS_ORIGIN_REGEX` + ## 追加修复(Web 镜像构建 TypeScript 阻塞) - 触发问题: