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>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
"""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__}"},
|
||||
)
|
||||
Reference in New Issue
Block a user