fix: enhance cors origin configuration
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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=["*"],
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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 阻塞)
|
||||||
|
|
||||||
- 触发问题:
|
- 触发问题:
|
||||||
|
|||||||
Reference in New Issue
Block a user