fix(ports): docker 默认对外8502并修复空DB连接串回退
This commit is contained in:
37
Dockerfile
Normal file
37
Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
# Video Flow - Python 后端 Dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
# 安装系统依赖
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ffmpeg \
|
||||
libsm6 \
|
||||
libxext6 \
|
||||
libgl1 \
|
||||
fonts-noto-cjk \
|
||||
fonts-wqy-zenhei \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 复制依赖文件
|
||||
COPY requirements.txt .
|
||||
|
||||
# 安装 Python 依赖
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 复制应用代码
|
||||
COPY . .
|
||||
|
||||
# 创建必要的目录
|
||||
RUN mkdir -p /app/output /app/temp /app/assets
|
||||
|
||||
# 暴露端口
|
||||
# - 8000: FastAPI
|
||||
# - 8503: Streamlit(docker-compose.yml 中运行在 8503;8502 是历史 runtime 标识)
|
||||
EXPOSE 8000 8503
|
||||
|
||||
# 默认命令
|
||||
CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
138
api/main.py
Normal file
138
api/main.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""
|
||||
Video Flow API - FastAPI Backend
|
||||
前后端分离架构的后端服务
|
||||
端口:8000(与 Streamlit 8503 共存;8502 为历史 runtime/素材目录标识)
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
# 确保项目根目录在 path 中
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
import config
|
||||
from api.routes import projects, editor, assets, compose
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""应用生命周期管理"""
|
||||
logger.info("Video Flow API 启动中...")
|
||||
logger.info(f"项目根目录: {PROJECT_ROOT}")
|
||||
logger.info(f"输出目录: {config.OUTPUT_DIR}")
|
||||
logger.info(f"临时目录: {config.TEMP_DIR}")
|
||||
yield
|
||||
logger.info("Video Flow API 关闭中...")
|
||||
|
||||
|
||||
# 创建 FastAPI 应用
|
||||
app = FastAPI(
|
||||
title="Video Flow API",
|
||||
description="视频工作流后端 API - 支持项目管理、素材处理、视频编辑与合成",
|
||||
version="1.0.0",
|
||||
lifespan=lifespan,
|
||||
docs_url="/api/docs",
|
||||
redoc_url="/api/redoc",
|
||||
openapi_url="/api/openapi.json"
|
||||
)
|
||||
|
||||
# CORS 配置 - 允许 React 前端访问
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=[
|
||||
"http://localhost:3000", # React dev server
|
||||
"http://localhost:5173", # Vite dev server
|
||||
"http://127.0.0.1:3000",
|
||||
"http://127.0.0.1:5173",
|
||||
],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# 挂载静态文件目录(用于访问生成的视频/图片)
|
||||
app.mount("/static/output", StaticFiles(directory=str(config.OUTPUT_DIR)), name="output")
|
||||
app.mount("/static/temp", StaticFiles(directory=str(config.TEMP_DIR)), name="temp")
|
||||
app.mount("/static/assets", StaticFiles(directory=str(config.ASSETS_DIR)), name="assets")
|
||||
|
||||
# Legacy mounts(8502 runtime 产物,宿主机目录通过 docker-compose 挂载到容器内)
|
||||
try:
|
||||
app.mount("/static/legacy-temp", StaticFiles(directory="/legacy/temp"), name="legacy-temp")
|
||||
app.mount("/static/legacy-output", StaticFiles(directory="/legacy/output"), name="legacy-output")
|
||||
except Exception:
|
||||
# 本地非 docker 环境可能不存在这些目录
|
||||
pass
|
||||
|
||||
# 注册路由
|
||||
app.include_router(projects.router, prefix="/api/projects", tags=["Projects"])
|
||||
app.include_router(editor.router, prefix="/api/editor", tags=["Editor"])
|
||||
app.include_router(assets.router, prefix="/api/assets", tags=["Assets"])
|
||||
app.include_router(compose.router, prefix="/api/compose", tags=["Compose"])
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
async def health_check():
|
||||
"""健康检查"""
|
||||
return {
|
||||
"status": "ok",
|
||||
"service": "video-flow-api",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/config")
|
||||
async def get_config():
|
||||
"""获取前端所需的配置信息"""
|
||||
return {
|
||||
"video_settings": config.VIDEO_SETTINGS,
|
||||
"available_voices": [
|
||||
{"id": config.VOLC_TTS_DEFAULT_VOICE, "name": "三通永 (默认)"},
|
||||
{"id": "zh_female_meilinvyou_saturn_bigtts", "name": "美丽女友"},
|
||||
],
|
||||
"available_bgm": _list_bgm_files(),
|
||||
}
|
||||
|
||||
|
||||
def _list_bgm_files():
|
||||
"""列出可用的 BGM 文件"""
|
||||
bgm_dir = config.ASSETS_DIR / "bgm"
|
||||
if not bgm_dir.exists():
|
||||
return []
|
||||
|
||||
bgm_files = []
|
||||
for f in bgm_dir.iterdir():
|
||||
if f.suffix.lower() in ['.mp3', '.mp4', '.m4a', '.wav']:
|
||||
bgm_files.append({
|
||||
"id": f.name,
|
||||
"name": f.stem,
|
||||
"path": f"/static/assets/bgm/{f.name}"
|
||||
})
|
||||
return bgm_files
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(
|
||||
"api.main:app",
|
||||
host="0.0.0.0",
|
||||
port=8000,
|
||||
reload=True,
|
||||
log_level="info"
|
||||
)
|
||||
|
||||
|
||||
|
||||
10
config.py
10
config.py
@@ -15,9 +15,13 @@ load_dotenv()
|
||||
VOLC_API_KEY = os.getenv("VOLC_API_KEY", "05aed9c1-f5e6-487b-9273-fe7d6be51957")
|
||||
VOLC_BASE_URL = os.getenv("VOLC_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3")
|
||||
|
||||
# OpenAI (for GPT models)
|
||||
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
|
||||
OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
|
||||
|
||||
# Models (Updated with User-Provided Endpoint IDs)
|
||||
# LLM: Doubao Pro 1.5 (Using provided brain/vision endpoint)
|
||||
BRAIN_MODEL_ID = os.getenv("BRAIN_MODEL_ID", "ep-20251203231055-dpsp7")
|
||||
BRAIN_MODEL_ID = os.getenv("BRAIN_MODEL_ID", "ep-20251203231055-dpsp7")
|
||||
# Vision: Doubao Vision Pro 1.5
|
||||
VISION_MODEL_ID = os.getenv("VISION_MODEL_ID", "ep-20251203232121-xjt2s")
|
||||
# Image: Doubao Image (Updated to user provided model)
|
||||
@@ -115,8 +119,8 @@ FONTS_DIR.mkdir(exist_ok=True)
|
||||
# Database Configuration
|
||||
# ============================================================
|
||||
# Format: postgresql://user:password@host:port/dbname
|
||||
# Default to SQLite if not provided
|
||||
DB_CONNECTION_STRING = os.getenv("DB_CONNECTION_STRING", f"sqlite:///{BASE_DIR}/video_flow.db")
|
||||
# Default to SQLite if not provided OR provided as empty string
|
||||
DB_CONNECTION_STRING = os.getenv("DB_CONNECTION_STRING") or f"sqlite:///{BASE_DIR}/video_flow.db"
|
||||
|
||||
# ============================================================
|
||||
# Font Settings (字体配置)
|
||||
|
||||
149
docker-compose.yml
Normal file
149
docker-compose.yml
Normal file
@@ -0,0 +1,149 @@
|
||||
version: '3.8'
|
||||
|
||||
# Video Flow - Docker Compose 配置
|
||||
# 支持水平扩展的视频处理服务
|
||||
|
||||
services:
|
||||
# ============================================================
|
||||
# Redis - 消息队列和缓存
|
||||
# ============================================================
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
command: redis-server --appendonly yes
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
# ============================================================
|
||||
# FastAPI Backend - API 服务
|
||||
# ============================================================
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- ./modules:/app/modules:ro
|
||||
- ./api:/app/api:ro
|
||||
- ./config.py:/app/config.py:ro
|
||||
- ./assets:/app/assets
|
||||
- ./output:/app/output
|
||||
- ./temp:/app/temp
|
||||
- ./video_flow.db:/app/video_flow.db
|
||||
# 方案A:通过 Unix Socket 连接宿主机 Postgres(不暴露 5432)
|
||||
- /var/run/postgresql:/var/run/postgresql
|
||||
# 复用 8502(/root/video-flow)生成的历史素材
|
||||
- /root/video-flow/temp:/legacy/temp:ro
|
||||
- /root/video-flow/output:/legacy/output:ro
|
||||
environment:
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- PYTHONUNBUFFERED=1
|
||||
# 8503 读写 8502 的 Postgres(从 .env 注入,避免写死密码)
|
||||
- DB_CONNECTION_STRING=${VIDEO_FLOW_DB_CONNECTION_STRING}
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
command: uvicorn api.main:app --host 0.0.0.0 --port 8000
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
|
||||
# ============================================================
|
||||
# Celery Worker - 视频处理 Worker
|
||||
# 可以通过 docker-compose scale worker=N 水平扩展
|
||||
# ============================================================
|
||||
worker:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
volumes:
|
||||
- ./modules:/app/modules:ro
|
||||
- ./api:/app/api:ro
|
||||
- ./config.py:/app/config.py:ro
|
||||
- ./bin:/app/bin:ro
|
||||
- ./assets:/app/assets
|
||||
- ./output:/app/output
|
||||
- ./temp:/app/temp
|
||||
- ./video_flow.db:/app/video_flow.db
|
||||
- /var/run/postgresql:/var/run/postgresql
|
||||
- /root/video-flow/temp:/legacy/temp:ro
|
||||
- /root/video-flow/output:/legacy/output:ro
|
||||
environment:
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- PYTHONUNBUFFERED=1
|
||||
- C_FORCE_ROOT=1
|
||||
- DB_CONNECTION_STRING=${VIDEO_FLOW_DB_CONNECTION_STRING}
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
command: celery -A api.celery_app worker --loglevel=info --concurrency=2 -Q default,video,audio
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
# 生产环境可以增加副本数
|
||||
replicas: 1
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2'
|
||||
memory: 4G
|
||||
|
||||
# ============================================================
|
||||
# React Frontend - 前端服务 (生产环境)
|
||||
# ============================================================
|
||||
web:
|
||||
build:
|
||||
context: ./web
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3000:80"
|
||||
depends_on:
|
||||
- api
|
||||
restart: unless-stopped
|
||||
|
||||
# ============================================================
|
||||
# Streamlit - 原有调试界面 (保留在 8503)
|
||||
# 连接到 8502 的数据目录,复用历史素材
|
||||
# ============================================================
|
||||
streamlit:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
# 默认对外保持 8502(兼容历史入口);容器内仍运行在 8503
|
||||
- "${STREAMLIT_PORT:-8502}:8503"
|
||||
volumes:
|
||||
- ./app.py:/app/app.py:ro
|
||||
- ./modules:/app/modules:ro
|
||||
- ./config.py:/app/config.py:ro
|
||||
- ./assets:/app/assets
|
||||
- ./output:/app/output
|
||||
# 挂载 8502 服务的 temp 目录,复用历史项目数据
|
||||
- /opt/gloda-factory/temp:/app/temp
|
||||
- ./video_flow.db:/app/video_flow.db
|
||||
- /var/run/postgresql:/var/run/postgresql
|
||||
- /root/video-flow/temp:/legacy/temp:ro
|
||||
- /root/video-flow/output:/legacy/output:ro
|
||||
environment:
|
||||
- PYTHONUNBUFFERED=1
|
||||
- DB_CONNECTION_STRING=${VIDEO_FLOW_DB_CONNECTION_STRING}
|
||||
- WEB_BASE_URL=${WEB_BASE_URL}
|
||||
command: streamlit run app.py --server.port 8503 --server.address 0.0.0.0
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
redis_data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: video-flow-network
|
||||
|
||||
@@ -21,7 +21,8 @@ echo ""
|
||||
echo "📌 访问地址:"
|
||||
echo " - 视频编辑器: http://localhost:3000"
|
||||
echo " - API 文档: http://localhost:8000/api/docs"
|
||||
echo " - 工作流控制台: http://localhost:8503"
|
||||
STREAMLIT_PORT="${STREAMLIT_PORT:-8502}"
|
||||
echo " - 工作流控制台: http://localhost:${STREAMLIT_PORT}"
|
||||
echo ""
|
||||
echo "📊 查看日志: docker-compose logs -f"
|
||||
echo "🔧 扩展 Worker: docker-compose scale worker=3"
|
||||
|
||||
Reference in New Issue
Block a user