142 lines
4.3 KiB
Python
142 lines
4.3 KiB
Python
"""电子书:管理员上传/替换 PDF·EPUB,登录用户在线阅读并记录进度。"""
|
||
from __future__ import annotations
|
||
|
||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||
from fastapi.responses import FileResponse
|
||
from sqlalchemy.orm import Session
|
||
|
||
from database import get_db
|
||
from dependencies import get_current_user, require_permission
|
||
from models import EbookProgress, Textbook, User
|
||
from schemas import EbookProgressIn, EbookProgressOut
|
||
from services import ebooks as store
|
||
|
||
|
||
# 上传 / 删除 / 取文件挂在教材下,进度单独成组
|
||
router = APIRouter(prefix="/textbooks", tags=["ebooks"])
|
||
progress_router = APIRouter(prefix="/ebooks", tags=["ebooks"])
|
||
|
||
|
||
def _load(db: Session, textbook_id: int) -> Textbook:
|
||
textbook = db.get(Textbook, textbook_id)
|
||
if textbook is None:
|
||
raise HTTPException(status_code=404, detail="教材不存在")
|
||
return textbook
|
||
|
||
|
||
def _require_file(textbook: Textbook) -> None:
|
||
if not textbook.ebook_file:
|
||
raise HTTPException(status_code=404, detail="该书还没有上传电子书")
|
||
|
||
|
||
@router.post("/{textbook_id}/ebook")
|
||
async def upload_ebook(
|
||
textbook_id: int,
|
||
file: UploadFile = File(...),
|
||
db: Session = Depends(get_db),
|
||
_: User = Depends(require_permission("catalog:manage")),
|
||
):
|
||
textbook = _load(db, textbook_id)
|
||
await store.store_upload(db, textbook, file)
|
||
return {
|
||
"id": textbook.id,
|
||
"ebook_format": textbook.ebook_format,
|
||
"ebook_name": textbook.ebook_name,
|
||
"ebook_size": textbook.ebook_size,
|
||
"ebook_pages": textbook.ebook_pages,
|
||
}
|
||
|
||
|
||
@router.delete("/{textbook_id}/ebook")
|
||
def delete_ebook(
|
||
textbook_id: int,
|
||
db: Session = Depends(get_db),
|
||
_: User = Depends(require_permission("catalog:manage")),
|
||
):
|
||
textbook = _load(db, textbook_id)
|
||
_require_file(textbook)
|
||
store.clear_ebook(db, textbook)
|
||
return {"deleted": textbook_id}
|
||
|
||
|
||
@router.get("/{textbook_id}/ebook/file/{filename}")
|
||
def ebook_file(
|
||
textbook_id: int,
|
||
filename: str,
|
||
db: Session = Depends(get_db),
|
||
_: User = Depends(get_current_user),
|
||
):
|
||
"""阅读器取原始文件。FileResponse 自带 Range/206 支持,PDF 可分段加载。"""
|
||
textbook = _load(db, textbook_id)
|
||
_require_file(textbook)
|
||
if filename != textbook.ebook_file:
|
||
raise HTTPException(status_code=404, detail="电子书文件不存在")
|
||
path = store.resolve_path(textbook.id, filename)
|
||
return FileResponse(
|
||
path,
|
||
media_type=store.media_type(textbook.ebook_format),
|
||
filename=textbook.ebook_name or path.name,
|
||
content_disposition_type="inline",
|
||
)
|
||
|
||
|
||
@progress_router.get("/progress", response_model=list[EbookProgressOut])
|
||
def list_progress(
|
||
db: Session = Depends(get_db),
|
||
user: User = Depends(get_current_user),
|
||
):
|
||
rows = (
|
||
db.query(EbookProgress)
|
||
.filter(EbookProgress.user_id == user.id)
|
||
.all()
|
||
)
|
||
return [
|
||
EbookProgressOut(
|
||
textbook_id=row.textbook_id,
|
||
locator=row.locator,
|
||
page=row.page,
|
||
total=row.total,
|
||
position=row.position,
|
||
updated_at=row.updated_at,
|
||
)
|
||
for row in rows
|
||
]
|
||
|
||
|
||
@progress_router.put("/{textbook_id}/progress", response_model=EbookProgressOut)
|
||
def save_progress(
|
||
textbook_id: int,
|
||
payload: EbookProgressIn,
|
||
db: Session = Depends(get_db),
|
||
user: User = Depends(get_current_user),
|
||
):
|
||
textbook = _load(db, textbook_id)
|
||
_require_file(textbook)
|
||
row = (
|
||
db.query(EbookProgress)
|
||
.filter(
|
||
EbookProgress.user_id == user.id,
|
||
EbookProgress.textbook_id == textbook_id,
|
||
)
|
||
.first()
|
||
)
|
||
if row is None:
|
||
row = EbookProgress(user_id=user.id, textbook_id=textbook_id)
|
||
db.add(row)
|
||
row.locator = payload.locator[:512]
|
||
row.page = max(0, payload.page)
|
||
row.total = max(0, payload.total)
|
||
row.position = min(1.0, max(0.0, payload.position))
|
||
# 阅读器拿到的才是真实篇幅(PDF 页数 / EPUB 章节数),回填给管理端展示
|
||
if row.total > 0 and textbook.ebook_pages != row.total:
|
||
textbook.ebook_pages = row.total
|
||
db.commit()
|
||
return EbookProgressOut(
|
||
textbook_id=textbook_id,
|
||
locator=row.locator,
|
||
page=row.page,
|
||
total=row.total,
|
||
position=row.position,
|
||
updated_at=row.updated_at,
|
||
)
|