fix: enhance cors origin configuration

This commit is contained in:
chengkai3
2026-04-12 23:00:19 +08:00
parent fcec3a4c31
commit c2505eb70c
8 changed files with 65 additions and 5 deletions
+1
View File
@@ -2,6 +2,7 @@ NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8000
API_HOST=0.0.0.0 API_HOST=0.0.0.0
API_PORT=8000 API_PORT=8000
API_CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 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 DATABASE_URL=postgresql+psycopg://fquiz:fquiz@db:5432/fquiz
FILE_VFS_ROOT=./data/vfs FILE_VFS_ROOT=./data/vfs
JWT_SECRET_KEY=change-this-in-production JWT_SECRET_KEY=change-this-in-production
+2
View File
@@ -155,6 +155,7 @@ jobs:
API_HOST: ${API_HOST:-0.0.0.0} API_HOST: ${API_HOST:-0.0.0.0}
API_PORT: ${API_PORT:-8000} API_PORT: ${API_PORT:-8000}
API_CORS_ORIGINS: ${API_CORS_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000} 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} DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg://fquiz:fquiz@db:5432/fquiz}
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-change-this-in-production} JWT_SECRET_KEY: ${JWT_SECRET_KEY:-change-this-in-production}
ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES:-15} ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES:-15}
@@ -197,6 +198,7 @@ jobs:
API_HOST=0.0.0.0 API_HOST=0.0.0.0
API_PORT=8000 API_PORT=8000
API_CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 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 DATABASE_URL=postgresql+psycopg://fquiz:fquiz@db:5432/fquiz
JWT_SECRET_KEY=change-this-in-production JWT_SECRET_KEY=change-this-in-production
ACCESS_TOKEN_EXPIRE_MINUTES=15 ACCESS_TOKEN_EXPIRE_MINUTES=15
+1
View File
@@ -40,6 +40,7 @@
- `app.models` 包初始化需预加载全部模型模块,确保字符串关系(如 `"AuditLog"`)在启动阶段可解析。 - `app.models` 包初始化需预加载全部模型模块,确保字符串关系(如 `"AuditLog"`)在启动阶段可解析。
- 部署 compose 中 DB 镜像应通过 `POSTGRES_IMAGE` 可配置,默认使用镜像站的 pgvector 镜像(`docker.m.daocloud.io/pgvector/pgvector:pg16`)。 - 部署 compose 中 DB 镜像应通过 `POSTGRES_IMAGE` 可配置,默认使用镜像站的 pgvector 镜像(`docker.m.daocloud.io/pgvector/pgvector:pg16`)。
- 宿主机 DB 暴露端口统一走 `POSTGRES_PORT`(默认 `5433`),用于规避与宿主机已有 PostgreSQL(常见 `5432`)冲突;容器内连接仍保持 `db:5432` - 宿主机 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` 直接中断发布。 - GitHub Actions 使用 `appleboy/ssh-action` 部署时,慢网环境需显式设置 `command_timeout`(建议 `45m`)并为 `docker compose pull` 增加重试,避免出现 `Run Command Timeout` 直接中断发布。
## 前端视觉口径(2026-04-12 ## 前端视觉口径(2026-04-12
+4
View File
@@ -124,4 +124,8 @@ npm run lint:web
说明: 说明:
- `NEXT_PUBLIC_API_BASE_URL` 在 Next.js 中是构建期注入;如果修改该值,需要重新执行 `docker compose up --build`。 - `NEXT_PUBLIC_API_BASE_URL` 在 Next.js 中是构建期注入;如果修改该值,需要重新执行 `docker compose up --build`。
- 若使用 Docker Compose,默认 `DATABASE_URL` 指向容器内 `db` 服务(PostgreSQL)。 - 若使用 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`。 - 默认镜像源已配置为 `docker.m.daocloud.io`,并默认使用 `pgvector` 镜像;如你网络环境可直连 Docker Hub,可在 `.env` 中覆盖 `POSTGRES_IMAGE / PYTHON_BASE_IMAGE / NODE_BASE_IMAGE`。
+34 -5
View File
@@ -1,4 +1,5 @@
from functools import lru_cache from functools import lru_cache
import re
from typing import Literal from typing import Literal
from pydantic import field_validator from pydantic import field_validator
@@ -11,6 +12,7 @@ class Settings(BaseSettings):
api_host: str = "0.0.0.0" api_host: str = "0.0.0.0"
api_port: int = 8000 api_port: int = 8000
api_cors_origins: str = "http://localhost:3000,http://127.0.0.1:3000" 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" database_url: str = "sqlite:///./fquiz.db"
file_vfs_root: str = "./data/vfs" file_vfs_root: str = "./data/vfs"
@@ -45,11 +47,38 @@ class Settings(BaseSettings):
@property @property
def cors_origins(self) -> list[str]: def cors_origins(self) -> list[str]:
return [ origins: list[str] = []
origin.strip() for origin in self.api_cors_origins.split(","):
for origin in self.api_cors_origins.split(",") normalized = origin.strip()
if 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 @lru_cache
+1
View File
@@ -25,6 +25,7 @@ app = FastAPI(
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=settings.cors_origins, allow_origins=settings.cors_origins,
allow_origin_regex=settings.cors_origin_regex,
allow_credentials=True, allow_credentials=True,
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
+1
View File
@@ -39,6 +39,7 @@ services:
API_HOST: ${API_HOST:-0.0.0.0} API_HOST: ${API_HOST:-0.0.0.0}
API_PORT: ${API_PORT:-8000} API_PORT: ${API_PORT:-8000}
API_CORS_ORIGINS: ${API_CORS_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000} 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} DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg://fquiz:fquiz@db:5432/fquiz}
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-change-this-in-production} JWT_SECRET_KEY: ${JWT_SECRET_KEY:-change-this-in-production}
ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES:-15} ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES:-15}
+21
View File
@@ -180,6 +180,27 @@
- 验证: - 验证:
- `cd web && npx eslint src/app/admin/layout.tsx` 通过。 - `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 阻塞) ## 追加修复(Web 镜像构建 TypeScript 阻塞)
- 触发问题: - 触发问题: