Spaces:
Running
Running
| import os | |
| import aiohttp | |
| import logging | |
| from fastapi import FastAPI, Request, HTTPException | |
| from fastapi.responses import JSONResponse, FileResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.staticfiles import StaticFiles | |
| from fastapi.exceptions import RequestValidationError | |
| from .config import Config | |
| from .key_manager import key_manager | |
| from .api import main_router | |
| from .api.dependencies import session_pool | |
| # 配置日志 | |
| logging.basicConfig( | |
| level=getattr(logging, os.getenv("LOG_LEVEL", "INFO").upper()), | |
| format="%(asctime)s [%(levelname)s] %(message)s", | |
| datefmt="%Y-%m-%d %H:%M:%S" | |
| ) | |
| logger = logging.getLogger("sora-api") | |
| # 创建FastAPI应用 | |
| app = FastAPI( | |
| title="OpenAI Compatible Sora API", | |
| description="为Sora提供OpenAI兼容接口的API服务", | |
| version="2.0.0", | |
| docs_url="/docs", | |
| redoc_url="/redoc", | |
| ) | |
| # 配置CORS中间件 | |
| origins = [ | |
| "http://localhost", | |
| "http://localhost:8890", | |
| f"http://{Config.HOST}:{Config.PORT}", | |
| Config.BASE_URL, | |
| ] | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=origins, | |
| allow_credentials=True, | |
| allow_methods=["GET", "POST", "PUT", "DELETE"], | |
| allow_headers=["Authorization", "Content-Type"], | |
| ) | |
| # 异常处理 | |
| async def validation_exception_handler(request: Request, exc: RequestValidationError): | |
| """处理请求验证错误""" | |
| return JSONResponse( | |
| status_code=422, | |
| content={"detail": f"请求验证错误: {str(exc)}"} | |
| ) | |
| async def global_exception_handler(request: Request, exc: Exception): | |
| """全局异常处理器""" | |
| # 记录错误 | |
| logger.error(f"全局异常: {str(exc)}", exc_info=True) | |
| # 如果是已知的HTTPException,保持原始状态码和详情 | |
| if isinstance(exc, HTTPException): | |
| return JSONResponse( | |
| status_code=exc.status_code, | |
| content={"detail": exc.detail} | |
| ) | |
| # 其他异常返回500状态码 | |
| return JSONResponse( | |
| status_code=500, | |
| content={"detail": f"服务器内部错误: {str(exc)}"} | |
| ) | |
| # 应用启动和关闭事件 | |
| async def startup_event(): | |
| """应用启动时执行的操作""" | |
| global session_pool | |
| # 创建共享会话池 | |
| session_pool = aiohttp.ClientSession() | |
| logger.info("应用已启动,创建了全局会话池") | |
| # 初始化时保存管理员密钥 | |
| Config.save_admin_key() | |
| # 确保静态文件目录存在 | |
| os.makedirs(os.path.join(Config.STATIC_DIR, "admin"), exist_ok=True) | |
| os.makedirs(os.path.join(Config.STATIC_DIR, "admin/js"), exist_ok=True) | |
| os.makedirs(os.path.join(Config.STATIC_DIR, "admin/css"), exist_ok=True) | |
| os.makedirs(Config.IMAGE_SAVE_DIR, exist_ok=True) | |
| # 输出图片保存目录的信息 | |
| logger.info(f"图片保存目录: {Config.IMAGE_SAVE_DIR}") | |
| # 图片访问URL | |
| base_url = Config.BASE_URL.rstrip('/') | |
| if Config.STATIC_PATH_PREFIX: | |
| logger.info(f"图片将通过 {base_url}{Config.STATIC_PATH_PREFIX}/images/<filename> 访问") | |
| else: | |
| logger.info(f"图片将通过 {base_url}/images/<filename> 访问") | |
| # 打印配置信息 | |
| Config.print_config() | |
| async def shutdown_event(): | |
| """应用关闭时执行的操作""" | |
| # 关闭会话池 | |
| if session_pool: | |
| await session_pool.close() | |
| logger.info("应用已关闭,清理了全局会话池") | |
| # 添加根路径路由 | |
| async def root(): | |
| """根路径,返回系统状态信息""" | |
| return { | |
| "status": "OK", | |
| "message": "系统运行正常", | |
| "version": app.version, | |
| "name": app.title | |
| } | |
| # 挂载静态文件目录 | |
| app.mount("/static", StaticFiles(directory=Config.STATIC_DIR), name="static") | |
| # 通用图片访问路由 - 支持多种路径格式 | |
| async def get_image(filename: str): | |
| """处理图片请求 - 无论保存在哪里""" | |
| # 直接从IMAGE_SAVE_DIR获取图片 | |
| file_path = os.path.join(Config.IMAGE_SAVE_DIR, filename) | |
| if os.path.exists(file_path): | |
| return FileResponse(file_path) | |
| else: | |
| logger.warning(f"请求的图片不存在: {file_path}") | |
| raise HTTPException(status_code=404, detail="图片不存在") | |
| # 添加静态文件路径前缀的兼容路由 | |
| if Config.STATIC_PATH_PREFIX: | |
| prefix_path = Config.STATIC_PATH_PREFIX.lstrip("/") | |
| async def get_prefixed_image(filename: str): | |
| """处理带前缀的图片请求""" | |
| return await get_image(filename) | |
| # 管理界面路由 | |
| async def admin_panel(): | |
| """返回管理面板HTML页面""" | |
| return FileResponse(os.path.join(Config.STATIC_DIR, "admin/index.html")) | |
| # 现在使用JWT认证,不再需要直接暴露管理员密钥 | |
| # 注册所有API路由 | |
| app.include_router(main_router) | |
| # 应用入口点(供uvicorn直接调用) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run( | |
| "app:app", | |
| host=Config.HOST, | |
| port=Config.PORT, | |
| reload=Config.VERBOSE_LOGGING | |
| ) |