chore(config): align server environment templates
This commit is contained in:
+19
-5
@@ -1,6 +1,7 @@
|
||||
# TJWater Server 环境变量模板
|
||||
# NextTJWater Server 环境变量模板
|
||||
# 复制为 .env 后按启用的功能填写;不要提交 .env 或任何真实凭据。
|
||||
# 生产配置由部署主机的受控 backend.env 注入,不要把完整配置保存为仓库密钥。
|
||||
# 生产配置由部署主机的受控 next-backend.env 注入,不随镜像或 CI/CD 同步本地 .env。
|
||||
# 进程环境变量优先;修改 Compose env_file 后需重新创建容器。
|
||||
|
||||
# 基础运行
|
||||
PROJECT_NAME="TJWater Server"
|
||||
@@ -69,9 +70,11 @@ KEYCLOAK_ALGORITHM="RS256"
|
||||
KEYCLOAK_AUDIENCE="account"
|
||||
KEYCLOAK_ACCESS_TOKEN_MAX_AGE_SECONDS=900
|
||||
|
||||
# 工作台事件与跨域
|
||||
# 认证接口必须使用明确来源,逗号分隔;不要配置为 *。
|
||||
CORS_ALLOWED_ORIGINS="http://127.0.0.1:5173,http://localhost:5173,http://127.0.0.1:15173,http://localhost:15173"
|
||||
# 浏览器跨域访问(主 API)
|
||||
# 必须填写明确来源(协议、域名或 IP、端口),英文逗号分隔;不允许 * 或网段正则。
|
||||
CORS_ALLOWED_ORIGINS=http://127.0.0.1:5173,http://localhost:5173,http://127.0.0.1:15173,http://localhost:15173
|
||||
|
||||
# 工作台事件
|
||||
WORKBENCH_EVENTS_ENABLED=true
|
||||
WORKBENCH_EVENT_RETENTION_DAYS=7
|
||||
|
||||
@@ -116,6 +119,17 @@ OPERATIONAL_ACTION_PERIPHERAL_URL=""
|
||||
OPERATIONAL_ACTION_COMMAND_GATEWAY_URL=""
|
||||
OPERATIONAL_ACTION_TIMEOUT_SECONDS=10
|
||||
|
||||
# 外围样板(挂载于 /peripheral;故障注入默认关闭)
|
||||
# 独立启动时用于调用主 API 验证身份;容器内按网络填写主服务地址。
|
||||
PERIPHERAL_SAMPLE_NEXT_SERVER_URL="http://127.0.0.1:8000"
|
||||
# 以下两个默认库选择只供种子、导出等脚本使用,HTTP 请求仍按已授权项目路由。
|
||||
PERIPHERAL_SAMPLE_DB_NAME="tjwater_v2"
|
||||
PERIPHERAL_SAMPLE_PROJECT_CODE="tjwater_v2"
|
||||
# 子应用独立运行时的浏览器来源列表,使用 JSON 格式;挂载时还受主 API CORS 配置控制。
|
||||
PERIPHERAL_SAMPLE_CORS_ORIGINS='["http://127.0.0.1:5173","http://localhost:5173"]'
|
||||
PERIPHERAL_SAMPLE_ENABLE_FAULTS=false
|
||||
PERIPHERAL_SAMPLE_TIMEOUT_SECONDS=5
|
||||
|
||||
# MCP 查询服务;OAuth 和所有项目业务库就绪前保持关闭。
|
||||
TJWATER_MCP_ENABLED=false
|
||||
TJWATER_MCP_ISSUER_URI=""
|
||||
|
||||
@@ -70,6 +70,18 @@ conda activate server
|
||||
|
||||
基础数据库变量用于启动和项目供应。运行时的项目请求仍以元数据库中的 `biz_data` 和 `iot_data` 路由为准,不会因为存在默认数据库变量而回退到固定项目。
|
||||
|
||||
CORS 通过后端 `.env` 或部署进程环境变量中的 `CORS_ALLOWED_ORIGINS` 配置,进程环境变量优先。
|
||||
必须使用英文逗号分隔的明确页面来源(协议、域名或 IP、端口,不带路径),不能填写 `*`。
|
||||
未设置时默认只允许本机 `5173`、`15173` 开发来源。正式部署按实际浏览器地址填写,例如:
|
||||
|
||||
```dotenv
|
||||
CORS_ALLOWED_ORIGINS=https://next-demo.waternetwork.cn,http://192.168.1.133,https://192.168.1.133,http://192.168.1.110,https://192.168.1.110
|
||||
```
|
||||
|
||||
CORS 匹配浏览器页面来源,不是 Nginx 连接 IP 或域名解析结果;非默认端口须逐项列出。
|
||||
保留跨域凭据支持及 `Authorization`、`X-Project-Id` 请求头,接口认证和权限校验继续生效。
|
||||
修改后需重启进程;使用 Compose `env_file` 注入时需重新创建容器才能读取新值。
|
||||
|
||||
## 常用命令
|
||||
|
||||
外围接入样板作为 `/peripheral` 子应用随主 API 进程启动,复用 PostgreSQL 且不自动建表;授权边界和契约见 [外围样板说明](docs/peripheral-sample.md)。
|
||||
|
||||
@@ -138,7 +138,7 @@ conda run -n server python scripts/cleanup_workbench_events.py \
|
||||
|
||||
禁止省略 `--project-id` 执行清理。数据库维护脚本同样遵循先在 `tjwater_v2` 验证、再逐库扩展的规则。
|
||||
|
||||
浏览器使用 `GET /api/v1/workbench/sync-snapshot` 建立原子快照,并以该游标连接 `GET /api/v1/workbench/events`。游标早于保留窗口、超前或对应事件已全部清理时返回 `reset_required`,客户端必须重建快照。SSE 需要 Bearer 与 `X-Project-Id`,且 `CORS_ALLOWED_ORIGINS` 必须配置为明确来源。
|
||||
浏览器使用 `GET /api/v1/workbench/sync-snapshot` 建立原子快照,并以该游标连接 `GET /api/v1/workbench/events`。游标早于保留窗口、超前或对应事件已全部清理时返回 `reset_required`,客户端必须重建快照。SSE 需要 Bearer 与 `X-Project-Id`,且 `CORS_ALLOWED_ORIGINS` 必须配置为明确来源,不允许 `*`。配置说明见 [README](../README.md#环境配置)。
|
||||
|
||||
## 外围样板工单同步
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.core.config import Settings
|
||||
|
||||
|
||||
ALLOWED_ORIGINS = [
|
||||
"https://next-demo.waternetwork.cn",
|
||||
"http://192.168.1.133",
|
||||
"https://192.168.1.133",
|
||||
"http://192.168.1.110",
|
||||
"https://192.168.1.110",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("origins", ["*", " *, http://localhost:5173", "http://localhost:5173,*"])
|
||||
def test_wildcard_origins_are_rejected(origins: str):
|
||||
with pytest.raises(ValueError, match="must list explicit origins"):
|
||||
Settings(_env_file=None, CORS_ALLOWED_ORIGINS=origins).cors_allowed_origins
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"origin",
|
||||
ALLOWED_ORIGINS + [
|
||||
"http://192.168.1.114",
|
||||
"http://192.168.1.133:5173",
|
||||
"https://demo.waternetwork.cn",
|
||||
"http://192.168.1.133.evil.example",
|
||||
"null",
|
||||
],
|
||||
)
|
||||
def test_explicit_origin_preflight(origin: str):
|
||||
settings = Settings(_env_file=None, CORS_ALLOWED_ORIGINS=", ".join(ALLOWED_ORIGINS))
|
||||
app = FastAPI()
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.cors_allowed_origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
with TestClient(app) as client:
|
||||
response = client.options(
|
||||
"/api/v1/projects",
|
||||
headers={
|
||||
"Origin": origin,
|
||||
"Access-Control-Request-Method": "GET",
|
||||
"Access-Control-Request-Headers": "authorization,x-project-id",
|
||||
},
|
||||
)
|
||||
allowed = origin in ALLOWED_ORIGINS
|
||||
assert response.status_code == (200 if allowed else 400)
|
||||
assert response.headers.get("access-control-allow-origin") == (origin if allowed else None)
|
||||
if allowed:
|
||||
assert response.headers["access-control-allow-credentials"] == "true"
|
||||
assert "authorization" in response.headers["access-control-allow-headers"]
|
||||
|
||||
|
||||
def test_default_origins_are_explicit_local_sources(monkeypatch):
|
||||
monkeypatch.delenv("CORS_ALLOWED_ORIGINS", raising=False)
|
||||
settings = Settings(_env_file=None)
|
||||
assert settings.cors_allowed_origins == [
|
||||
"http://127.0.0.1:5173", "http://localhost:5173",
|
||||
"http://127.0.0.1:15173", "http://localhost:15173",
|
||||
]
|
||||
|
||||
|
||||
def test_environment_controls_origins(monkeypatch):
|
||||
monkeypatch.setenv("CORS_ALLOWED_ORIGINS", "http://192.168.1.133,https://192.168.1.110")
|
||||
settings = Settings(_env_file=None)
|
||||
assert settings.cors_allowed_origins == ["http://192.168.1.133", "https://192.168.1.110"]
|
||||
Reference in New Issue
Block a user