mirror of
https://github.com/TheSmallHanCat/flow2api.git
synced 2026-05-22 20:31:42 +08:00
210 lines
7.3 KiB
Python
210 lines
7.3 KiB
Python
"""FastAPI application initialization"""
|
||
from fastapi import FastAPI
|
||
from fastapi.responses import HTMLResponse, FileResponse
|
||
from fastapi.staticfiles import StaticFiles
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from contextlib import asynccontextmanager
|
||
from pathlib import Path
|
||
|
||
from .core.config import config
|
||
from .core.database import Database
|
||
from .services.flow_client import FlowClient
|
||
from .services.proxy_manager import ProxyManager
|
||
from .services.token_manager import TokenManager
|
||
from .services.load_balancer import LoadBalancer
|
||
from .services.concurrency_manager import ConcurrencyManager
|
||
from .services.generation_handler import GenerationHandler
|
||
from .api import routes, admin
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI):
|
||
"""Application lifespan manager"""
|
||
# Startup
|
||
print("=" * 60)
|
||
print("Flow2API Starting...")
|
||
print("=" * 60)
|
||
|
||
# Get config from setting.toml
|
||
config_dict = config.get_raw_config()
|
||
|
||
# Check if database exists (determine if first startup)
|
||
is_first_startup = not db.db_exists()
|
||
|
||
# Initialize database tables structure
|
||
await db.init_db()
|
||
|
||
# Handle database initialization based on startup type
|
||
if is_first_startup:
|
||
print("🎉 First startup detected. Initializing database and configuration from setting.toml...")
|
||
await db.init_config_from_toml(config_dict, is_first_startup=True)
|
||
print("✓ Database and configuration initialized successfully.")
|
||
else:
|
||
print("🔄 Existing database detected. Checking for missing tables and columns...")
|
||
await db.check_and_migrate_db(config_dict)
|
||
print("✓ Database migration check completed.")
|
||
|
||
# Load admin config from database
|
||
admin_config = await db.get_admin_config()
|
||
if admin_config:
|
||
config.set_admin_username_from_db(admin_config.username)
|
||
config.set_admin_password_from_db(admin_config.password)
|
||
config.api_key = admin_config.api_key
|
||
|
||
# Load cache configuration from database
|
||
cache_config = await db.get_cache_config()
|
||
config.set_cache_enabled(cache_config.cache_enabled)
|
||
config.set_cache_timeout(cache_config.cache_timeout)
|
||
config.set_cache_base_url(cache_config.cache_base_url or "")
|
||
|
||
# Load generation configuration from database
|
||
generation_config = await db.get_generation_config()
|
||
config.set_image_timeout(generation_config.image_timeout)
|
||
config.set_video_timeout(generation_config.video_timeout)
|
||
|
||
# Load debug configuration from database
|
||
debug_config = await db.get_debug_config()
|
||
config.set_debug_enabled(debug_config.enabled)
|
||
|
||
# Load captcha configuration from database
|
||
captcha_config = await db.get_captcha_config()
|
||
config.set_captcha_method(captcha_config.captcha_method)
|
||
config.set_yescaptcha_api_key(captcha_config.yescaptcha_api_key)
|
||
config.set_yescaptcha_base_url(captcha_config.yescaptcha_base_url)
|
||
|
||
# Initialize browser captcha service if needed
|
||
browser_service = None
|
||
if True:
|
||
from .services.browser_captcha_personal import BrowserCaptchaService
|
||
browser_service = await BrowserCaptchaService.get_instance(db)
|
||
await browser_service.open_login_window()
|
||
print("✓ Browser captcha service initialized (webui mode)")
|
||
elif captcha_config.captcha_method == "browser":
|
||
from .services.browser_captcha import BrowserCaptchaService
|
||
browser_service = await BrowserCaptchaService.get_instance(db)
|
||
print("✓ Browser captcha service initialized (headless mode)")
|
||
|
||
# Initialize concurrency manager
|
||
tokens = await token_manager.get_all_tokens()
|
||
await concurrency_manager.initialize(tokens)
|
||
|
||
# Start file cache cleanup task
|
||
await generation_handler.file_cache.start_cleanup_task()
|
||
|
||
# Start 429 auto-unban task
|
||
import asyncio
|
||
async def auto_unban_task():
|
||
"""定时任务:每小时检查并解禁429被禁用的token"""
|
||
while True:
|
||
try:
|
||
await asyncio.sleep(3600) # 每小时执行一次
|
||
await token_manager.auto_unban_429_tokens()
|
||
except Exception as e:
|
||
print(f"❌ Auto-unban task error: {e}")
|
||
|
||
auto_unban_task_handle = asyncio.create_task(auto_unban_task())
|
||
|
||
print(f"✓ Database initialized")
|
||
print(f"✓ Total tokens: {len(tokens)}")
|
||
print(f"✓ Cache: {'Enabled' if config.cache_enabled else 'Disabled'} (timeout: {config.cache_timeout}s)")
|
||
print(f"✓ File cache cleanup task started")
|
||
print(f"✓ 429 auto-unban task started (runs every hour)")
|
||
print(f"✓ Server running on http://{config.server_host}:{config.server_port}")
|
||
print("=" * 60)
|
||
|
||
yield
|
||
|
||
# Shutdown
|
||
print("Flow2API Shutting down...")
|
||
# Stop file cache cleanup task
|
||
await generation_handler.file_cache.stop_cleanup_task()
|
||
# Stop auto-unban task
|
||
auto_unban_task_handle.cancel()
|
||
try:
|
||
await auto_unban_task_handle
|
||
except asyncio.CancelledError:
|
||
pass
|
||
# Close browser if initialized
|
||
if browser_service:
|
||
await browser_service.close()
|
||
print("✓ Browser captcha service closed")
|
||
print("✓ File cache cleanup task stopped")
|
||
print("✓ 429 auto-unban task stopped")
|
||
|
||
|
||
# Initialize components
|
||
db = Database()
|
||
proxy_manager = ProxyManager(db)
|
||
flow_client = FlowClient(proxy_manager)
|
||
token_manager = TokenManager(db, flow_client)
|
||
concurrency_manager = ConcurrencyManager()
|
||
load_balancer = LoadBalancer(token_manager, concurrency_manager)
|
||
generation_handler = GenerationHandler(
|
||
flow_client,
|
||
token_manager,
|
||
load_balancer,
|
||
db,
|
||
concurrency_manager,
|
||
proxy_manager # 添加 proxy_manager 参数
|
||
)
|
||
|
||
# Set dependencies
|
||
routes.set_generation_handler(generation_handler)
|
||
admin.set_dependencies(token_manager, proxy_manager, db)
|
||
|
||
# Create FastAPI app
|
||
app = FastAPI(
|
||
title="Flow2API",
|
||
description="OpenAI-compatible API for Google VideoFX (Veo)",
|
||
version="1.0.0",
|
||
lifespan=lifespan
|
||
)
|
||
|
||
# CORS middleware
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=["*"],
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
# Include routers
|
||
app.include_router(routes.router)
|
||
app.include_router(admin.router)
|
||
|
||
# Static files - serve tmp directory for cached files
|
||
tmp_dir = Path(__file__).parent.parent / "tmp"
|
||
tmp_dir.mkdir(exist_ok=True)
|
||
app.mount("/tmp", StaticFiles(directory=str(tmp_dir)), name="tmp")
|
||
|
||
# HTML routes for frontend
|
||
static_path = Path(__file__).parent.parent / "static"
|
||
|
||
|
||
@app.get("/", response_class=HTMLResponse)
|
||
async def index():
|
||
"""Redirect to login page"""
|
||
login_file = static_path / "login.html"
|
||
if login_file.exists():
|
||
return FileResponse(str(login_file))
|
||
return HTMLResponse(content="<h1>Flow2API</h1><p>Frontend not found</p>", status_code=404)
|
||
|
||
|
||
@app.get("/login", response_class=HTMLResponse)
|
||
async def login_page():
|
||
"""Login page"""
|
||
login_file = static_path / "login.html"
|
||
if login_file.exists():
|
||
return FileResponse(str(login_file))
|
||
return HTMLResponse(content="<h1>Login Page Not Found</h1>", status_code=404)
|
||
|
||
|
||
@app.get("/manage", response_class=HTMLResponse)
|
||
async def manage_page():
|
||
"""Management console page"""
|
||
manage_file = static_path / "manage.html"
|
||
if manage_file.exists():
|
||
return FileResponse(str(manage_file))
|
||
return HTMLResponse(content="<h1>Management Page Not Found</h1>", status_code=404)
|