新增PDF批量预生成
This commit is contained in:
@@ -7,10 +7,11 @@ from app.api.deps import require_roles
|
||||
from app.core.security import generate_public_token, hash_token
|
||||
from app.db.session import get_db
|
||||
from app.models import AdminUser, Certificate, CertificateAccessToken, Learner, ProjectCourse
|
||||
from app.schemas.certificate import CertificateCreate, CertificateOut
|
||||
from app.schemas.certificate import CertificateCreate, CertificateOut, PdfPregenerationJobCreate, PdfPregenerationJobOut
|
||||
from app.services.certificate_number import build_certificate_no
|
||||
from app.services.logs import log_action
|
||||
from app.services.pdf import PdfGenerationBusy, render_certificate_pdf
|
||||
from app.services.pdf_pregeneration import pdf_pregeneration_manager
|
||||
from app.services.system_settings import get_pdf_generation_concurrency_limit
|
||||
|
||||
router = APIRouter()
|
||||
@@ -33,10 +34,12 @@ def _create_token(db: Session, certificate_id: int, token_type: str) -> int:
|
||||
def list_certificates(
|
||||
keyword: str | None = Query(default=None),
|
||||
status_value: str | None = Query(default=None, alias="status"),
|
||||
pdf_status: str | None = Query(default=None),
|
||||
import_batch_id: int | None = Query(default=None),
|
||||
db: Session = Depends(get_db),
|
||||
_: AdminUser = Depends(require_roles("system_admin", "certificate_admin", "readonly")),
|
||||
) -> list[Certificate]:
|
||||
query = db.query(Certificate).order_by(Certificate.id.desc())
|
||||
) -> list[dict[str, object]]:
|
||||
query = db.query(Certificate, Learner).outerjoin(Learner, Learner.id == Certificate.learner_id).order_by(Certificate.id.desc())
|
||||
if keyword:
|
||||
like = f"%{keyword}%"
|
||||
query = query.filter(
|
||||
@@ -46,11 +49,16 @@ def list_certificates(
|
||||
Certificate.project_code.like(like),
|
||||
Certificate.course_name.like(like),
|
||||
Certificate.stage_name.like(like),
|
||||
Learner.current_name.like(like),
|
||||
)
|
||||
)
|
||||
if status_value:
|
||||
query = query.filter(Certificate.status == status_value)
|
||||
return query.limit(100).all()
|
||||
if pdf_status:
|
||||
query = query.filter(Certificate.pdf_status == pdf_status)
|
||||
if import_batch_id:
|
||||
query = query.filter(Certificate.import_batch_id == import_batch_id)
|
||||
return [_certificate_payload(certificate, learner) for certificate, learner in query.limit(200).all()]
|
||||
|
||||
|
||||
@router.post("", response_model=CertificateOut, status_code=status.HTTP_201_CREATED)
|
||||
@@ -89,6 +97,51 @@ def create_certificate(
|
||||
return certificate
|
||||
|
||||
|
||||
@router.get("/pdf-pregeneration-jobs", response_model=list[PdfPregenerationJobOut])
|
||||
def list_pdf_pregeneration_jobs(
|
||||
_: AdminUser = Depends(require_roles("system_admin", "certificate_admin", "readonly")),
|
||||
) -> list[dict[str, object]]:
|
||||
return [job.to_dict() for job in pdf_pregeneration_manager.list()]
|
||||
|
||||
|
||||
@router.post("/pdf-pregeneration-jobs", response_model=PdfPregenerationJobOut, status_code=status.HTTP_201_CREATED)
|
||||
def start_pdf_pregeneration_job(
|
||||
payload: PdfPregenerationJobCreate,
|
||||
db: Session = Depends(get_db),
|
||||
admin: AdminUser = Depends(require_roles("system_admin", "certificate_admin")),
|
||||
) -> dict[str, object]:
|
||||
certificate_ids = _resolve_pregeneration_certificate_ids(db, payload)
|
||||
if not certificate_ids:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="没有可预生成的证书")
|
||||
concurrency_limit = get_pdf_generation_concurrency_limit(db)
|
||||
job = pdf_pregeneration_manager.start(certificate_ids, concurrency_limit)
|
||||
log_action(
|
||||
db,
|
||||
admin,
|
||||
"start_pdf_pregeneration",
|
||||
"certificate",
|
||||
job.id,
|
||||
detail={
|
||||
"total": job.total,
|
||||
"concurrency_limit": concurrency_limit,
|
||||
"import_batch_id": payload.import_batch_id,
|
||||
},
|
||||
)
|
||||
db.commit()
|
||||
return job.to_dict()
|
||||
|
||||
|
||||
@router.get("/pdf-pregeneration-jobs/{job_id}", response_model=PdfPregenerationJobOut)
|
||||
def get_pdf_pregeneration_job(
|
||||
job_id: str,
|
||||
_: AdminUser = Depends(require_roles("system_admin", "certificate_admin", "readonly")),
|
||||
) -> dict[str, object]:
|
||||
job = pdf_pregeneration_manager.get(job_id)
|
||||
if not job:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="预生成任务不存在或已过期")
|
||||
return job.to_dict()
|
||||
|
||||
|
||||
@router.get("/{certificate_id}", response_model=CertificateOut)
|
||||
def get_certificate(
|
||||
certificate_id: int,
|
||||
@@ -101,6 +154,67 @@ def get_certificate(
|
||||
return certificate
|
||||
|
||||
|
||||
def _certificate_payload(certificate: Certificate, learner: Learner | None = None) -> dict[str, object]:
|
||||
return {
|
||||
"id": certificate.id,
|
||||
"learner_id": certificate.learner_id,
|
||||
"learner_name": learner.current_name if learner else None,
|
||||
"import_batch_id": certificate.import_batch_id,
|
||||
"project_code": certificate.project_code,
|
||||
"certificate_no": certificate.certificate_no,
|
||||
"certificate_name": certificate.certificate_name,
|
||||
"class_name": certificate.class_name,
|
||||
"course_name": certificate.course_name,
|
||||
"stage_name": certificate.stage_name,
|
||||
"issue_date": certificate.issue_date,
|
||||
"issuer_name": certificate.issuer_name,
|
||||
"status": certificate.status,
|
||||
"pdf_status": certificate.pdf_status,
|
||||
"created_at": certificate.created_at,
|
||||
"updated_at": certificate.updated_at,
|
||||
}
|
||||
|
||||
|
||||
def _resolve_pregeneration_certificate_ids(db: Session, payload: PdfPregenerationJobCreate) -> list[int]:
|
||||
ids: list[int] = []
|
||||
if payload.import_batch_id:
|
||||
ids.extend(
|
||||
item[0]
|
||||
for item in db.query(Certificate.id)
|
||||
.filter(Certificate.import_batch_id == payload.import_batch_id)
|
||||
.filter(Certificate.status == "valid")
|
||||
.order_by(Certificate.id.asc())
|
||||
.all()
|
||||
)
|
||||
selected_ids = _dedupe_ids(payload.certificate_ids)
|
||||
if selected_ids:
|
||||
valid_selected_ids = {
|
||||
item[0]
|
||||
for item in db.query(Certificate.id)
|
||||
.filter(Certificate.id.in_(selected_ids))
|
||||
.filter(Certificate.status == "valid")
|
||||
.all()
|
||||
}
|
||||
ids.extend(certificate_id for certificate_id in selected_ids if certificate_id in valid_selected_ids)
|
||||
seen: set[int] = set()
|
||||
result: list[int] = []
|
||||
for item in ids:
|
||||
if item not in seen:
|
||||
seen.add(item)
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
def _dedupe_ids(values: list[int]) -> list[int]:
|
||||
seen: set[int] = set()
|
||||
result: list[int] = []
|
||||
for value in values:
|
||||
if value not in seen:
|
||||
seen.add(value)
|
||||
result.append(value)
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/{certificate_id}/preview")
|
||||
def preview_certificate(
|
||||
certificate_id: int,
|
||||
|
||||
Reference in New Issue
Block a user