"""FastAPI 应用入口:lifespan 管理 scheduler、首次启动创建 admin。""" import logging from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from sqlalchemy.exc import OperationalError from app.api.router import api_router from app.config import get_settings from app.database import Base, SessionLocal, engine from app.models.user import User from app.scheduler.scheduler import scheduler_lifespan from app.security import hash_password from app.utils.logging import setup_logging setup_logging() log = logging.getLogger(__name__) settings = get_settings() def init_database() -> None: """建表(Alembic 是首选;这里兜底确保首次启动也能跑)。""" try: # 实际生产用 alembic upgrade head;这里仅当表不存在时创建 Base.metadata.create_all(bind=engine) log.info("Database tables ensured") except OperationalError as e: log.error("Database init failed: %s", e) raise def init_admin() -> None: """首次启动创建 admin 账号(仅当 users 表为空时)。""" with SessionLocal() as db: try: existing = db.query(User).count() if existing > 0: log.info("Existing users found (%d), skip admin init", existing) return admin = User( username="admin", password_hash=hash_password(settings.INITIAL_ADMIN_PASSWORD), is_admin=True, is_active=True, ) db.add(admin) db.commit() log.warning( "Initial admin account created. username=admin " "Please change the password after first login." ) except Exception: db.rollback() raise @asynccontextmanager async def lifespan(app: FastAPI): init_database() init_admin() async with scheduler_lifespan(): log.info("Application ready") yield log.info("Application shutting down") app = FastAPI( title="Backup System", description="数据备份管理系统 - MySQL / 目录 备份到本地 / S3", version="0.1.0", lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins_list, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/api/health") def health() -> dict: return {"status": "ok"} app.include_router(api_router, prefix="/api") @app.exception_handler(Exception) async def global_exception_handler(request, exc): log.exception("Unhandled error on %s %s", request.method, request.url) return JSONResponse( status_code=500, content={"detail": f"Internal server error: {type(exc).__name__}"}, )