142 lines
4.3 KiB
Python
142 lines
4.3 KiB
Python
"""数学学习系统后端入口。"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
from sqlalchemy import text
|
|
|
|
from database import REPO_ROOT, SessionLocal, ensure_schema
|
|
from routers import (
|
|
analytics,
|
|
archive,
|
|
auth,
|
|
catalog,
|
|
courses,
|
|
data,
|
|
ebooks,
|
|
llm,
|
|
knowledge_graph,
|
|
overview,
|
|
practice,
|
|
question_bank,
|
|
users,
|
|
)
|
|
from seed.catalog import ensure_catalog
|
|
from services import ebooks as ebook_store
|
|
from seed.ebooks import ensure_ebooks
|
|
from seed.knowledge_graph import ensure_knowledge_graph
|
|
from seed.rbac import ensure_rbac
|
|
from services.learning_profile import (
|
|
ensure_default_profiles,
|
|
remove_unused_admin_profile,
|
|
user_has_admin_role,
|
|
)
|
|
from services.llm_config import ensure_llm_channels
|
|
from models import AttemptItem, ErrorEntry, Question, User
|
|
from services.knowledge_service import set_question_knowledge_names
|
|
|
|
|
|
ensure_schema()
|
|
db = SessionLocal()
|
|
try:
|
|
ensure_rbac(db)
|
|
ensure_knowledge_graph(db)
|
|
ensure_default_profiles(db)
|
|
for user in db.query(User).all():
|
|
if user_has_admin_role(db, user.id):
|
|
remove_unused_admin_profile(db, user.id)
|
|
ensure_catalog(db)
|
|
ensure_ebooks(db)
|
|
ebook_store.backfill_pages(db)
|
|
ensure_llm_channels(db)
|
|
db.execute(
|
|
text(
|
|
"UPDATE questions SET used_count = ("
|
|
"SELECT COUNT(*) FROM attempt_items "
|
|
"WHERE attempt_items.question_id = questions.id"
|
|
") WHERE id IN (SELECT DISTINCT question_id FROM attempt_items "
|
|
"WHERE question_id IS NOT NULL)"
|
|
)
|
|
)
|
|
for question in db.query(Question).all():
|
|
has_links = db.execute(
|
|
text(
|
|
"SELECT 1 FROM question_knowledge "
|
|
"WHERE question_id = :qid LIMIT 1"
|
|
),
|
|
{"qid": question.id},
|
|
).first()
|
|
if has_links is None:
|
|
set_question_knowledge_names(
|
|
db,
|
|
question,
|
|
[question.knowledge_name],
|
|
attach_to_chapter=True,
|
|
)
|
|
for row in db.query(ErrorEntry).all():
|
|
if not row.knowledge_names or row.knowledge_names == "[]":
|
|
row.knowledge_names = json.dumps(
|
|
[row.knowledge_name], ensure_ascii=False
|
|
)
|
|
for row in db.query(AttemptItem).all():
|
|
if not row.knowledge_names or row.knowledge_names == "[]":
|
|
row.knowledge_names = json.dumps(
|
|
[row.knowledge_name], ensure_ascii=False
|
|
)
|
|
db.commit()
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def _cors_origins() -> list[str]:
|
|
raw = os.getenv("CORS_ORIGINS", "http://localhost:5173,http://127.0.0.1:5173")
|
|
return [origin.strip() for origin in raw.split(",") if origin.strip()]
|
|
|
|
|
|
app = FastAPI(
|
|
title="数学学习系统 API",
|
|
description="个人数学学习系统后端:章节组卷判分、掌握度引擎、错题与学习档案。",
|
|
version="1.0.0",
|
|
)
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=_cors_origins(),
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
# 阅读器依赖 Range 分段加载,需要能读到这几个响应头
|
|
expose_headers=["Accept-Ranges", "Content-Range", "Content-Length"],
|
|
)
|
|
|
|
app.include_router(overview.router, prefix="/api")
|
|
app.include_router(data.router, prefix="/api")
|
|
app.include_router(analytics.router, prefix="/api")
|
|
app.include_router(practice.router, prefix="/api")
|
|
app.include_router(archive.router, prefix="/api")
|
|
app.include_router(catalog.router, prefix="/api")
|
|
app.include_router(courses.router, prefix="/api")
|
|
app.include_router(ebooks.router, prefix="/api")
|
|
app.include_router(ebooks.progress_router, prefix="/api")
|
|
app.include_router(question_bank.router, prefix="/api")
|
|
app.include_router(llm.router, prefix="/api")
|
|
app.include_router(auth.router, prefix="/api")
|
|
app.include_router(users.router, prefix="/api")
|
|
app.include_router(knowledge_graph.router, prefix="/api")
|
|
|
|
image_dir = REPO_ROOT / "data" / "question_images"
|
|
image_dir.mkdir(parents=True, exist_ok=True)
|
|
app.mount(
|
|
"/api/question-images",
|
|
StaticFiles(directory=image_dir),
|
|
name="question-images",
|
|
)
|
|
|
|
|
|
@app.get("/api/health")
|
|
def health():
|
|
return {"status": "ok", "service": "math-learning-backend"}
|