新增PDF生成并发设置

This commit is contained in:
2026-06-23 12:54:14 +08:00
parent 529e175d2c
commit 1e9404cc7d
27 changed files with 541 additions and 24 deletions

View File

@@ -10,6 +10,7 @@ from app.api.routes import (
admin_logs,
admin_pdf_cleanup,
admin_projects,
admin_settings,
auth,
health,
public,
@@ -27,4 +28,5 @@ api_router.include_router(admin_exports.router, prefix="/admin/exports", tags=["
api_router.include_router(admin_logs.router, prefix="/admin/logs", tags=["admin-logs"])
api_router.include_router(admin_pdf_cleanup.router, prefix="/admin/pdf-cleanup", tags=["admin-pdf-cleanup"])
api_router.include_router(admin_backups.router, prefix="/admin/backups", tags=["admin-backups"])
api_router.include_router(admin_settings.router, prefix="/admin/settings", tags=["admin-settings"])
api_router.include_router(public.router, prefix="/public", tags=["public"])

View File

@@ -10,7 +10,8 @@ from app.models import AdminUser, Certificate, CertificateAccessToken, Learner,
from app.schemas.certificate import CertificateCreate, CertificateOut
from app.services.certificate_number import build_certificate_no
from app.services.logs import log_action
from app.services.pdf import render_certificate_pdf
from app.services.pdf import PdfGenerationBusy, render_certificate_pdf
from app.services.system_settings import get_pdf_generation_concurrency_limit
router = APIRouter()
@@ -143,7 +144,16 @@ def download_certificate(
project = db.query(ProjectCourse).filter(ProjectCourse.code == certificate.project_code).first()
if not learner or not token:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Certificate data is incomplete")
pdf_path = render_certificate_pdf(certificate, learner, project, token)
try:
pdf_path = render_certificate_pdf(
certificate,
learner,
project,
token,
concurrency_limit=get_pdf_generation_concurrency_limit(db),
)
except PdfGenerationBusy as exc:
raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail=str(exc)) from exc
db.commit()
return FileResponse(pdf_path, media_type="application/pdf", filename=f"{certificate.certificate_no}.pdf")

View File

@@ -0,0 +1,44 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.api.deps import require_roles
from app.db.session import get_db
from app.models import AdminUser
from app.schemas.setting import PdfSettingsOut, PdfSettingsUpdate
from app.services.logs import log_action
from app.services.system_settings import get_pdf_settings, update_pdf_generation_concurrency_limit
router = APIRouter()
@router.get("/pdf", response_model=PdfSettingsOut)
def get_pdf_generation_settings(
db: Session = Depends(get_db),
_: AdminUser = Depends(require_roles("system_admin")),
) -> dict[str, object]:
settings = get_pdf_settings(db)
db.commit()
return settings
@router.put("/pdf", response_model=PdfSettingsOut)
def update_pdf_generation_settings(
payload: PdfSettingsUpdate,
db: Session = Depends(get_db),
admin: AdminUser = Depends(require_roles("system_admin")),
) -> dict[str, object]:
before = get_pdf_settings(db)
after = update_pdf_generation_concurrency_limit(db, payload.pdf_generation_concurrency_limit)
log_action(
db,
admin,
"update_pdf_settings",
"system_setting",
"pdf_generation_concurrency_limit",
detail={
"before": before["pdf_generation_concurrency_limit"],
"after": after["pdf_generation_concurrency_limit"],
},
)
db.commit()
return after

View File

@@ -8,9 +8,10 @@ from app.db.session import get_db
from app.models import Certificate, CertificateAccessToken, Learner, ProjectCourse
from app.services.captcha import make_captcha, verify_captcha
from app.services.logs import log_action, mask_phone
from app.services.pdf import render_certificate_pdf
from app.services.pdf import PdfGenerationBusy, render_certificate_pdf
from app.services.rate_limit import hit_too_often
from app.services.sms import generate_sms_code, verify_sms_code
from app.services.system_settings import get_pdf_generation_concurrency_limit
router = APIRouter()
@@ -150,7 +151,16 @@ def download_certificate_pdf(token: str, db: Session = Depends(get_db)) -> FileR
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="证书已作废,不支持下载正常 PDF")
project = db.query(ProjectCourse).filter(ProjectCourse.code == certificate.project_code).first()
pdf_path = render_certificate_pdf(certificate, learner, project, access_token)
try:
pdf_path = render_certificate_pdf(
certificate,
learner,
project,
access_token,
concurrency_limit=get_pdf_generation_concurrency_limit(db),
)
except PdfGenerationBusy as exc:
raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail=str(exc)) from exc
log_action(db, None, "public_download_certificate", "certificate", certificate.id, {"certificate_no": certificate.certificate_no, "learner_name": learner.current_name, "project_code": certificate.project_code})
db.commit()
return FileResponse(pdf_path, media_type="application/pdf", filename=f"{certificate.certificate_no}.pdf")