feat: backend env config upgrade - multi-env (dev/prod), DB_* support, Docker compatible
Some checks failed
Build and Deploy / test-backend (push) Has been cancelled
Build and Deploy / build-backend (push) Has been cancelled
Build and Deploy / build-admin (push) Has been cancelled
Deploy to Production / build-backend (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
bujie9527
2026-02-06 12:07:02 +08:00
parent 91c3d75557
commit b715755b76
11 changed files with 141 additions and 35 deletions

6
backend/.env.dev Normal file
View File

@@ -0,0 +1,6 @@
# Local development
DB_HOST=localhost
DB_PORT=5432
DB_USER=wecom
DB_PASSWORD=password
DB_NAME=wecom_ai

10
backend/.env.example Normal file
View File

@@ -0,0 +1,10 @@
# Database (optional: set DATABASE_URL directly, or use DB_* below)
DB_HOST=localhost
DB_PORT=5432
DB_USER=wecom
DB_PASSWORD=password
DB_NAME=wecom
DATABASE_URL=
# Sync URL for Alembic (optional; default: built from DB_*)
# DATABASE_URL_SYNC=

View File

@@ -5,13 +5,13 @@ from sqlalchemy import engine_from_config
from sqlalchemy.engine import Connection from sqlalchemy.engine import Connection
from sqlalchemy import pool from sqlalchemy import pool
from app.config import settings
from app.models import Base from app.models import Base
from config.settings import DATABASE_URL_SYNC
config = context.config config = context.config
if config.config_file_name is not None: if config.config_file_name is not None:
fileConfig(config.config_file_name) fileConfig(config.config_file_name)
config.set_main_option("sqlalchemy.url", settings.database_url_sync) config.set_main_option("sqlalchemy.url", DATABASE_URL_SYNC)
target_metadata = Base.metadata target_metadata = Base.metadata

View File

@@ -1,13 +1,16 @@
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
# 数据库 URL 由 config.settings 提供(环境变量优先,支持 DB_* 拼接,容器兼容)
from config.settings import DATABASE_URL, DATABASE_URL_SYNC
class Settings(BaseSettings): class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore") model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
api_host: str = "0.0.0.0" api_host: str = "0.0.0.0"
api_port: int = 8000 api_port: int = 8000
database_url: str = "postgresql+asyncpg://wecom:wecom_secret@localhost:5432/wecom_ai" database_url: str = DATABASE_URL
database_url_sync: str = "postgresql://wecom:wecom_secret@localhost:5432/wecom_ai" database_url_sync: str = DATABASE_URL_SYNC
jwt_secret: str = "change-me" jwt_secret: str = "change-me"
jwt_algorithm: str = "HS256" jwt_algorithm: str = "HS256"

View File

@@ -1,26 +0,0 @@
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
api_host: str = "0.0.0.0"
api_port: int = 8000
database_url: str = "postgresql+asyncpg://wecom:wecom_secret@localhost:5432/wecom_ai"
database_url_sync: str = "postgresql://wecom:wecom_secret@localhost:5432/wecom_ai"
jwt_secret: str = "change-me"
jwt_algorithm: str = "HS256"
jwt_expire_minutes: int = 60
wecom_corp_id: str = ""
wecom_agent_id: str = ""
wecom_secret: str = ""
wecom_token: str = ""
wecom_encoding_aes_key: str = ""
wecom_api_base: str = "https://qyapi.weixin.qq.com"
wecom_api_timeout: int = 10
wecom_api_retries: int = 2
log_level: str = "INFO"
log_json: bool = True
settings = Settings()

View File

@@ -0,0 +1 @@
# Config package: env-based settings, DATABASE_URL from env or DB_*.

View File

@@ -0,0 +1,48 @@
"""
统一配置加载环境变量优先fallback 到 .env无 DATABASE_URL 时按 DB_* 拼接。
支持多环境dev/prod、容器DB_HOST=db、向后兼容。
"""
import os
from pathlib import Path
# 环境变量优先fallback 到 .env不覆盖已存在的系统变量
def _load_dotenv():
try:
from dotenv import load_dotenv
except ImportError:
return
env = os.getenv("ENV", "").lower()
base = Path(__file__).resolve().parent.parent
if env == "prod":
load_dotenv(base / ".env.prod", override=False)
elif env == "dev":
load_dotenv(base / ".env.dev", override=False)
load_dotenv(base / ".env", override=False)
_load_dotenv()
def build_database_url_async() -> str:
"""拼接异步数据库 URLpostgresql+asyncpg"""
user = os.getenv("DB_USER", "wecom")
password = os.getenv("DB_PASSWORD", "password")
host = os.getenv("DB_HOST", "localhost")
port = os.getenv("DB_PORT", "5432")
db = os.getenv("DB_NAME", "wecom_ai")
return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{db}"
def build_database_url_sync() -> str:
"""拼接同步数据库 URLpostgresqlAlembic/psycopg2"""
user = os.getenv("DB_USER", "wecom")
password = os.getenv("DB_PASSWORD", "password")
host = os.getenv("DB_HOST", "localhost")
port = os.getenv("DB_PORT", "5432")
db = os.getenv("DB_NAME", "wecom_ai")
return f"postgresql://{user}:{password}@{host}:{port}/{db}"
# 优先使用环境变量,否则按 DB_* 拼接(向后兼容)
DATABASE_URL: str = os.getenv("DATABASE_URL") or build_database_url_async()
DATABASE_URL_SYNC: str = os.getenv("DATABASE_URL_SYNC") or build_database_url_sync()

59
backend/docs/config.md Normal file
View File

@@ -0,0 +1,59 @@
# 环境配置说明
## 多环境与加载顺序
- **系统环境变量** 优先于任何文件。
- **config/settings.py** 会按 `ENV` 选择 dotenv 文件,再统一从环境变量或 `DB_*` 拼接数据库 URL。
- 若已设置 `DATABASE_URL` / `DATABASE_URL_SYNC`,则直接使用,否则用 `DB_HOST``DB_PORT``DB_USER``DB_PASSWORD``DB_NAME` 自动拼接。
## 环境与文件
| 环境 | 建议使用的文件 | 说明 |
|------|----------------|------|
| **dev** | `.env.dev` | 本地开发,`DB_HOST=localhost` |
| **prod / Docker** | `.env.prod` 或 compose 传入的 env | 容器内 `DB_HOST=db`compose 服务名) |
| 任意 | `.env` | 可选,可覆盖部分变量 |
## 使用方式
### 本地开发dev
```bash
cd backend
cp .env.example .env.dev # 首次可复制后按需修改
# 可选:设置 ENV=dev 以显式加载 .env.dev
export ENV=dev
# 或直接依赖系统已设置的 DB_* / DATABASE_URL
alembic upgrade head
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```
使用 `.env.dev` 时,默认 `DB_HOST=localhost`,数据库连接本机 PostgreSQL。
### Docker / 生产prod
-**docker-compose****k8s** 中通过环境变量传入配置,不要写死 localhost。
- 示例:在 compose 中为 backend 服务设置:
```yaml
environment:
- DB_HOST=db
- DB_PORT=5432
- DB_USER=wecom
- DB_PASSWORD=xxx
- DB_NAME=wecom_ai
```
或使用 `env_file: .env.prod``.env.prod` 中 `DB_HOST=db`)。
- 容器内应用和 Alembic 迁移都会使用上述变量;不再依赖 localhost。
### CI/CD
- 在流水线中设置 `DB_HOST`、`DB_PORT`、`DB_USER`、`DB_PASSWORD`、`DB_NAME`,或直接设置 `DATABASE_URL` / `DATABASE_URL_SYNC`。
- 无需在代码或镜像中写死数据库地址。
## 向后兼容
- 若仍在使用旧版只配置 `DATABASE_URL` 的 `.env`,无需修改,会继续生效。
- 未设置 `DATABASE_URL` 时,将使用 `DB_*` 拼接;未设置 `DB_NAME` 时默认库名为 `wecom_ai`。

View File

@@ -4,8 +4,8 @@
# 镜像标签 # 镜像标签
TAG=latest TAG=latest
# 数据库配置 # 数据库配置backend 也支持仅设置 DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME自动拼接
DATABASE_URL=postgresql://postgres:CHANGE_ME@db:5432/wecom_ai DATABASE_URL=postgresql+asyncpg://postgres:CHANGE_ME@db:5432/wecom_ai
DATABASE_URL_SYNC=postgresql://postgres:CHANGE_ME@db:5432/wecom_ai DATABASE_URL_SYNC=postgresql://postgres:CHANGE_ME@db:5432/wecom_ai
POSTGRES_USER=postgres POSTGRES_USER=postgres
POSTGRES_PASSWORD=CHANGE_ME POSTGRES_PASSWORD=CHANGE_ME

View File

@@ -61,8 +61,13 @@ services:
ports: ports:
- "80:80" - "80:80"
- "443:443" - "443:443"
volumes: # 注意:镜像构建时已经包含了配置,无需挂载
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro # 如果需要覆盖配置,取消下面的注释并确保 nginx.conf 路径正确
# volumes:
# - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
environment:
- NGINX_HOST=_
- NGINX_PORT=80
depends_on: depends_on:
- backend - backend
- admin - admin

View File

@@ -44,7 +44,7 @@ services:
ports: ports:
- "80:80" - "80:80"
volumes: volumes:
- ./deploy/nginx.conf:/etc/nginx/nginx.conf:ro - ./deploy/nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on: depends_on:
- backend - backend
- admin - admin