Files
the-island/backend/app/main.py
empty d1b02b4dfd feat: Phase 8 - VFX 和 AI 打赏反应系统
- Unity: 添加 VFXManager 实现金币雨和爱心爆炸特效
- Unity: NetworkManager 支持 GiftEffect 事件
- Unity: AgentVisual 支持自定义时长的 SpeechBubble
- Backend: LLMService 支持生成个性化感谢语
- Backend: Engine 统一处理礼物逻辑 (handle_gift)
- Backend: TwitchBot 接入新的礼物处理流程
2026-01-01 21:38:49 +08:00

159 lines
4.4 KiB
Python

"""
FastAPI entry point for the interactive live-stream game backend.
Configures the application, WebSocket routes, and lifecycle events.
"""
# Load .env file before any other imports
from dotenv import load_dotenv
load_dotenv()
import asyncio
import logging
import os
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from .server import ConnectionManager
from .engine import GameEngine
from .schemas import GameEvent, ClientMessage, EventType
from .twitch_service import TwitchBot
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# Global instances
manager = ConnectionManager()
engine = GameEngine(manager)
# Frontend path
FRONTEND_DIR = Path(__file__).parent.parent.parent / "frontend"
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
Application lifespan manager.
Starts the game engine and Twitch bot on startup, stops them on shutdown.
"""
logger.info("Starting application...")
# Start game engine
await engine.start()
# Start Twitch bot if credentials are provided
twitch_bot = None
if os.getenv("TWITCH_TOKEN") and os.getenv("TWITCH_CHANNEL_NAME"):
try:
twitch_bot = TwitchBot(engine)
# Start bot in background task
asyncio.create_task(twitch_bot.start())
logger.info("Twitch bot started in background")
except Exception as e:
logger.error(f"Failed to start Twitch bot: {e}")
else:
logger.info("Twitch credentials not provided, skipping Twitch bot")
yield
logger.info("Shutting down application...")
# Stop Twitch bot if it was started
if twitch_bot:
try:
await twitch_bot.close()
logger.info("Twitch bot stopped")
except Exception as e:
logger.error(f"Error stopping Twitch bot: {e}")
# Stop game engine
await engine.stop()
# Create FastAPI application
app = FastAPI(
title="The Island - Live Stream Game Backend",
description="Commercial-grade interactive live-stream game backend",
version="0.1.0",
lifespan=lifespan
)
# Configure CORS for frontend access
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def root():
"""Serve the debug client page."""
return FileResponse(FRONTEND_DIR / "debug_client.html")
@app.get("/health")
async def health():
"""Health check endpoint."""
return {
"status": "running",
"service": "The Island Game Backend",
"connections": manager.connection_count,
"engine_running": engine.is_running
}
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
"""
WebSocket endpoint for real-time game communication.
Handles client connections and processes incoming messages.
"""
await manager.connect(websocket)
# Send welcome message
welcome = GameEvent(
event_type=EventType.SYSTEM,
data={"message": "Connected to The Island!"}
)
await manager.send_personal(websocket, welcome)
try:
while True:
# Receive and parse client message
data = await websocket.receive_json()
message = ClientMessage(**data)
# Handle mock comment action
if message.action == "send_comment":
user = message.payload.get("user", "Anonymous")
text = message.payload.get("message", "")
await engine.process_comment(user, text)
# Handle test gift action
elif message.action == "test_gift":
user = message.payload.get("user", "TestUser")
bits = int(message.payload.get("bits", 100))
# Trigger handle_gift in engine
await engine.handle_gift(user, bits, "bits")
except WebSocketDisconnect:
manager.disconnect(websocket)
except Exception as e:
logger.error(f"WebSocket error: {e}")
manager.disconnect(websocket)
# Mount static files (must be after all routes)
app.mount("/static", StaticFiles(directory=FRONTEND_DIR), name="static")