学生资料设置、头像与自动备份恢复。

首页卡片支持修改/删除;详情页设置 Tab 可维护学校、年级与头像;系统设置新增数据备份下载与恢复;备份默认存 /root/grade-archive-backups,详见 docs/BACKUP.md。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-28 17:56:09 +08:00
parent 1cb3c7fad5
commit 530a8b70a1
25 changed files with 1230 additions and 194 deletions
+20 -1
View File
@@ -1,5 +1,8 @@
from contextlib import asynccontextmanager
from pathlib import Path
import logging
import threading
import time
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
@@ -8,7 +11,8 @@ from fastapi.staticfiles import StaticFiles
from app.core.config import settings
from app.core.database import Base, SessionLocal, engine
from app.routers import admin, auth, compositions, exams, export, settings as settings_router, students, subjects, wrong_questions
from app.routers import admin, auth, backups, compositions, exams, export, settings as settings_router, students, subjects, wrong_questions
from app.services import backup as backup_service
from app.services import ocr as ocr_service
from app.services.migrate import run_migrations
from app.services.seed import seed_admin_and_settings, seed_subjects
@@ -24,9 +28,21 @@ def resolve_frontend_dist() -> Path | None:
return None
def _auto_backup_loop() -> None:
interval = max(settings.AUTO_BACKUP_INTERVAL_HOURS, 1) * 3600
time.sleep(300)
while True:
try:
backup_service.create_backup()
except Exception:
logging.getLogger(__name__).exception("自动备份失败")
time.sleep(interval)
@asynccontextmanager
async def lifespan(app: FastAPI):
Path(settings.UPLOAD_DIR).mkdir(parents=True, exist_ok=True)
Path(settings.BACKUP_DIR).mkdir(parents=True, exist_ok=True)
Base.metadata.create_all(bind=engine)
run_migrations()
db = SessionLocal()
@@ -36,6 +52,8 @@ async def lifespan(app: FastAPI):
finally:
db.close()
ocr_service.warmup_ocr_engine()
if settings.AUTO_BACKUP_INTERVAL_HOURS > 0:
threading.Thread(target=_auto_backup_loop, daemon=True).start()
yield
@@ -53,6 +71,7 @@ app.add_middleware(
app.include_router(auth.router, prefix="/api")
app.include_router(settings_router.router, prefix="/api")
app.include_router(admin.router, prefix="/api")
app.include_router(backups.router, prefix="/api")
app.include_router(students.router, prefix="/api")
app.include_router(subjects.router, prefix="/api")
app.include_router(exams.router, prefix="/api")