Files
system-backu/backend/app/main.py
T
publ 4e3a4b4602 feat: 数据备份管理系统 v0.1.0
带 Web 操作界面的备份系统,支持:
- 数据源:MySQL 数据库(mysqldump)、服务器目录(tar.gz)
- 存储目标:本地目录、S3 兼容对象存储(MinIO/Ceph/AWS S3)
- 触发方式:手动 + Cron 定时调度(APScheduler)
- 备份还原:MySQL 库、目录
- JWT 登录认证 + Fernet 字段加密
- 保留策略:按数量 + 按天数双重清理

技术栈:FastAPI + SQLAlchemy 2 + APScheduler + aioboto3;
前端 Vite + React 18 + TypeScript + Ant Design 5 + Zustand。
Docker Compose 一键起。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-22 18:27:41 +08:00

100 lines
2.8 KiB
Python

"""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__}"},
)