package jnpf.dmsService.pdf; import jnpf.base.ActionResult; import jnpf.dms.DmsPersistentPdfApi; import jnpf.dmsEntity.permission.entity.DmsFileVersionEntity; import jnpf.dmsEntity.storage.entity.DmsFileObjectEntity; import jnpf.dmsEntity.storage.form.DmsPersistentPdfGenerateForm; import jnpf.dmsEntity.storage.pdf.DmsPdfGenerationTask; import jnpf.dmsEntity.storage.vo.DmsPersistentPdfGenerateVO; import jnpf.dmsMapper.DmsFileObjectMapper; import jnpf.dmsMapper.DmsFileVersionMapper; import jnpf.dmsMapper.DmsPdfGenerationTaskMapper; import jnpf.exception.DataException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.util.UUID; @Slf4j @Service @EnableScheduling @RequiredArgsConstructor @ConditionalOnProperty(name = "dms.pdf-generation.worker-enabled", havingValue = "true") public class DmsPdfGenerationJob { private final DmsPdfGenerationTaskMapper tasks; private final DmsFileVersionMapper versions; private final DmsFileObjectMapper objects; private final DmsPersistentPdfApi fileApi; private final DmsPdfGenerationCompletion completion; @Value("${dms.pdf-generation.batch-size:20}") private int batchSize; @Value("${dms.pdf-generation.max-attempts:8}") private int maxAttempts; @Value("${dms.pdf-generation.lease-seconds:300}") private int leaseSeconds; @Value("${dms.pdf-generation.backfill-enabled:false}") private boolean backfillEnabled; @Scheduled(cron = "${dms.pdf-generation.cron:*/30 * * * * ?}") public void generateDue() { if (backfillEnabled) tasks.discover(Math.max(1, batchSize)); for (DmsPdfGenerationTask task : tasks.candidates(Math.max(1, batchSize), maxAttempts)) { process(task); } } void process(DmsPdfGenerationTask task) { String token = UUID.randomUUID().toString(); if (tasks.claim(task.getTenantId(), task.getFileVersionId(), token, maxAttempts, leaseSeconds) != 1) return; try { DmsFileVersionEntity version = versions.selectByTenantAndId( task.getTenantId(), task.getFileVersionId()); DmsFileObjectEntity object = objects.selectByTenantAndFileVersionId( task.getTenantId(), task.getFileVersionId()); if (version == null || object == null || !task.getSourceChecksumSha256().equalsIgnoreCase(version.getChecksumSha256())) { throw new DataException("持久化 PDF 源文件不存在或已变化"); } ActionResult response = fileApi.generate( "jnpf-dms", form(task, version, object)); if (response == null || !Integer.valueOf(200).equals(response.getCode()) || response.getData() == null) { throw new DataException(response == null || response.getMsg() == null ? "文件服务未返回持久化 PDF" : response.getMsg()); } completion.succeed(task, token, response.getData()); } catch (RuntimeException error) { int attempts = task.getAttempts() == null ? 1 : task.getAttempts() + 1; boolean terminal = attempts >= maxAttempts; tasks.fail(task.getTenantId(), task.getFileVersionId(), token, message(error), retryDelay(attempts), terminal); log.warn("DMS 持久化 PDF 生成失败 tenant={} version={} attempts={}", task.getTenantId(), task.getFileVersionId(), attempts, error); } } private DmsPersistentPdfGenerateForm form(DmsPdfGenerationTask task, DmsFileVersionEntity version, DmsFileObjectEntity object) { DmsPersistentPdfGenerateForm form = new DmsPersistentPdfGenerateForm(); form.setTenantId(task.getTenantId()); form.setFileVersionId(task.getFileVersionId()); form.setFileName(version.getFileName()); form.setMimeType(version.getMimeType()); form.setSizeBytes(version.getSizeBytes()); form.setStoragePlatform(object.getStoragePlatform()); form.setBucketName(object.getBucketName()); form.setBasePath(object.getBasePath()); form.setObjectPath(object.getObjectPath()); form.setObjectName(object.getObjectName()); form.setSourceChecksumSha256(task.getSourceChecksumSha256()); return form; } static int retryDelay(int attempts) { int[] delays = {60, 300, 900, 3600, 21600}; return delays[Math.min(Math.max(attempts - 1, 0), delays.length - 1)]; } private String message(Throwable error) { String value = error.getMessage(); if (value == null || value.isBlank()) value = error.getClass().getSimpleName(); return value.length() <= 1000 ? value : value.substring(0, 1000); } }