588c2236e6
- 背景图配置改为从七牛云 CDN 远程拉取,支持不发版新增图片 - 删除本地 public/bg/ 图片副本,减小包体积 - 封面制作:'换一组'按钮移至标签右侧、优化按钮样式、删除冗余提示 - 封面制作/视频合成右侧预览区分别添加'封面预览'/'视频预览'标题 - 素材匹配服务支持远程 fetch materials.json + reload API
190 lines
5.3 KiB
Python
190 lines
5.3 KiB
Python
"""
|
||
FastAPI 应用入口
|
||
================
|
||
"""
|
||
|
||
import logging
|
||
import sys
|
||
from contextlib import asynccontextmanager
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
|
||
import uvicorn
|
||
from fastapi import FastAPI
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from fastapi.responses import JSONResponse
|
||
|
||
from app.api.v1.router import api_router
|
||
from app.config import get_settings
|
||
from app.db.session import close_db, init_db
|
||
from app.schemas.common import ApiResponse
|
||
|
||
settings = get_settings()
|
||
|
||
# 配置日志 - 同时输出到控制台和文件
|
||
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||
log_level = getattr(logging, settings.LOG_LEVEL)
|
||
|
||
# 创建日志目录(在用户文档目录下)
|
||
log_dir = Path.home() / "Documents" / "Meijiaka-zy" / "logs"
|
||
log_dir.mkdir(parents=True, exist_ok=True)
|
||
|
||
# 日志文件名按日期
|
||
log_file = log_dir / f"api_{datetime.now().strftime('%Y%m%d')}.log"
|
||
|
||
# 配置根日志记录器
|
||
logging.basicConfig(
|
||
level=log_level,
|
||
format=log_format,
|
||
handlers=[
|
||
logging.StreamHandler(sys.stdout), # 控制台输出
|
||
logging.FileHandler(log_file, encoding="utf-8", mode="a"), # 文件输出
|
||
],
|
||
)
|
||
logger = logging.getLogger(__name__)
|
||
logger.info(f"日志文件位置: {log_file}")
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI):
|
||
"""
|
||
应用生命周期管理
|
||
|
||
- 启动时:初始化数据库、加载模型配置
|
||
- 关闭时:清理资源
|
||
"""
|
||
logger.info(f"Starting {settings.APP_NAME} v{settings.APP_VERSION}")
|
||
|
||
# 开发和测试环境自动创建表
|
||
if settings.DEBUG and settings.ENV in ("development", "staging"):
|
||
logger.info("Initializing database tables...")
|
||
try:
|
||
# 确保所有模型已注册到 metadata
|
||
from app.models import User # noqa: F401
|
||
|
||
await init_db()
|
||
logger.info("Database tables initialized")
|
||
except Exception as e:
|
||
logger.warning(f"Database initialization skipped: {e}")
|
||
|
||
# 加载 AI 模型配置(从 YAML 文件)
|
||
try:
|
||
from app.core.config_loader import get_config_loader
|
||
|
||
config_loader = get_config_loader()
|
||
platforms_count = len(config_loader.get_all_platforms())
|
||
models_count = len(config_loader.get_enabled_models())
|
||
|
||
logger.info(f"Loaded {platforms_count} platforms, {models_count} models from config file")
|
||
except Exception as e:
|
||
logger.warning(f"Failed to load models from config: {e}")
|
||
|
||
# 加载空镜素材配置(优先远程 CDN,fallback 本地 JSON)
|
||
try:
|
||
from app.services.material_service import load_config
|
||
|
||
load_config()
|
||
logger.info("Loaded material config")
|
||
except Exception as e:
|
||
logger.warning(f"Failed to load material config: {e}")
|
||
|
||
yield
|
||
|
||
# 关闭时清理
|
||
logger.info("Shutting down...")
|
||
await close_db()
|
||
logger.info("Cleanup complete")
|
||
|
||
|
||
def create_app() -> FastAPI:
|
||
"""创建 FastAPI 应用实例"""
|
||
|
||
app = FastAPI(
|
||
title=settings.APP_NAME,
|
||
version=settings.APP_VERSION,
|
||
description="美家卡智影 - AI 视频创作后端 API",
|
||
docs_url="/docs" if settings.DEBUG else None,
|
||
redoc_url="/redoc" if settings.DEBUG else None,
|
||
lifespan=lifespan,
|
||
)
|
||
|
||
# CORS 配置
|
||
# 开发环境下允许所有来源,避免跨域问题
|
||
allow_origins = ["*"] if settings.DEBUG else settings.cors_origins_list
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=allow_origins,
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
# 注册路由
|
||
app.include_router(api_router, prefix="/api/v1")
|
||
|
||
# 全局异常处理(统一返回 ApiResponse 格式)
|
||
@app.exception_handler(Exception)
|
||
async def global_exception_handler(request, exc):
|
||
"""全局异常捕获"""
|
||
logger.exception("Unhandled exception")
|
||
return JSONResponse(
|
||
status_code=500,
|
||
content={
|
||
"code": 500,
|
||
"message": "服务器内部错误",
|
||
"data": None,
|
||
"detail": {"error": str(exc)} if settings.DEBUG else None,
|
||
},
|
||
)
|
||
|
||
# 健康检查
|
||
@app.get("/health", tags=["System"])
|
||
async def health_check():
|
||
"""服务健康检查"""
|
||
return ApiResponse(
|
||
code=200,
|
||
data={
|
||
"status": "healthy",
|
||
"version": settings.APP_VERSION,
|
||
"environment": settings.ENV,
|
||
},
|
||
message="服务运行正常",
|
||
)
|
||
|
||
# 根路由
|
||
@app.get("/", tags=["System"])
|
||
async def root():
|
||
"""API 根路径"""
|
||
return ApiResponse(
|
||
code=200,
|
||
data={
|
||
"name": settings.APP_NAME,
|
||
"version": settings.APP_VERSION,
|
||
"docs": "/docs" if settings.DEBUG else None,
|
||
},
|
||
message="美家卡智影 API 服务",
|
||
)
|
||
|
||
return app
|
||
|
||
|
||
# 创建应用实例
|
||
app = create_app()
|
||
|
||
|
||
def main():
|
||
"""入口函数(用于命令行启动)"""
|
||
uvicorn.run(
|
||
"app.main:app",
|
||
host=settings.HOST,
|
||
port=settings.PORT,
|
||
workers=settings.WORKERS if not settings.DEBUG else 1,
|
||
reload=settings.DEBUG,
|
||
log_level=settings.LOG_LEVEL.lower(),
|
||
)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
# test
|