iOSBuildServer/backend/main.py

94 lines
3.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""FastAPI 主入口"""
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from .database import init_db
from .routers import config, apps, tasks, auth, users
from .services.log_streamer import log_streamer
from .config import BACKEND_PORT, CORS_ALLOWED_ORIGINS, TRUSTED_HOSTS, validate_production_security
from .deps import decode_current_user
STATIC_DIR = Path(__file__).parent / "static"
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期"""
validate_production_security()
init_db()
yield
app = FastAPI(
title="iOS 自动打包服务",
version="1.0.0",
lifespan=lifespan,
)
# 仅接受显式允许的跨域前端;同域部署不需要 CORS 配置。
if CORS_ALLOWED_ORIGINS:
app.add_middleware(
CORSMiddleware,
allow_origins=CORS_ALLOWED_ORIGINS,
allow_credentials=False,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Authorization", "Content-Type"],
)
app.add_middleware(TrustedHostMiddleware, allowed_hosts=TRUSTED_HOSTS)
# 路由
app.include_router(auth.router)
app.include_router(users.router)
app.include_router(config.router)
app.include_router(apps.router)
app.include_router(tasks.router)
@app.websocket("/ws/tasks/{task_id}")
async def websocket_logs(websocket: WebSocket, task_id: str):
"""经 JWT 鉴权的 WebSocket 实时日志。"""
protocol = websocket.headers.get("sec-websocket-protocol", "")
token_protocol = next((item.strip() for item in protocol.split(",") if item.strip().startswith("jwt.")), "")
if not token_protocol:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
return
try:
decode_current_user(token_protocol.removeprefix("jwt."))
except Exception:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
return
await websocket.accept(subprotocol=token_protocol)
try:
async for msg in log_streamer.subscribe(task_id):
await websocket.send_json(msg)
except WebSocketDisconnect:
pass
except Exception:
pass
@app.get("/api/health")
async def health():
"""健康检查"""
return {"status": "ok", "port": BACKEND_PORT}
# 静态文件服务(生产模式:前端 build 产物由 FastAPI 直接 serving
if STATIC_DIR.exists():
app.mount("/assets", StaticFiles(directory=str(STATIC_DIR / "assets")), name="assets")
@app.get("/{full_path:path}")
async def serve_spa(request: Request, full_path: str):
"""SPA catch-all非 API 路由统一返回 index.html"""
file_path = STATIC_DIR / full_path
if file_path.is_file():
return FileResponse(str(file_path))
return FileResponse(str(STATIC_DIR / "index.html"))