package jnpf.fileStorage.dms; import jnpf.base.ActionResult; import jnpf.base.ActionResultCode; import jnpf.dmsEntity.preview.model.DmsFilePreviewTicket; import jnpf.dmsEntity.storage.form.DmsPersistentPdfGenerateForm; import jnpf.dmsEntity.storage.vo.DmsPersistentPdfGenerateVO; import jnpf.exception.DataException; import jnpf.onlyoffice.DmsPreviewPdfApi; import jnpf.onlyoffice.DmsPreviewPdfRequest; import jnpf.util.JsonUtil; import jnpf.util.TicketUtil; import lombok.RequiredArgsConstructor; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.dromara.x.file.storage.core.platform.MinioFileStorage; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.util.Collections; import java.util.Locale; import java.util.Objects; @Service @RequiredArgsConstructor public class DmsPersistentPdfStorageService { private static final long TICKET_TTL_SECONDS = 300L; private final FileStorageService fileStorageService; private final DmsPreviewPdfApi previewPdfApi; public DmsPersistentPdfGenerateVO generate(DmsPersistentPdfGenerateForm form) { validateSource(form); String ticket = TicketUtil.createTicket(JsonUtil.getObjectToString(sourceTicket(form)), TICKET_TTL_SECONDS); FileInfo stored = null; try { ActionResult result = previewPdfApi.render( new DmsPreviewPdfRequest(form.getFileVersionId(), ticket)); if (result == null || !ActionResultCode.SUCCESS.getCode().equals(result.getCode()) || result.getData() == null || result.getData().length == 0) { throw new DataException(result == null || result.getMsg() == null ? "持久化 PDF 生成失败" : result.getMsg()); } MultipartFile pdf = new GeneratedPdf(form.getFileVersionId() + ".pdf", result.getData()); stored = fileStorageService.of(pdf) .setPlatform(form.getStoragePlatform()) .setPath("dms-derived/pdf/" + form.getFileVersionId() + "/") .setSaveFilename(form.getSourceChecksumSha256() + ".pdf") .setOriginalFilename(pdf.getOriginalFilename()) .setHashCalculatorSha256() .upload(); if (stored == null || stored.getSize() == null || stored.getSize() <= 0 || stored.getHashInfo() == null || stored.getHashInfo().getSha256() == null) { deleteQuietly(stored); throw new DataException("持久化 PDF 保存失败"); } DmsPersistentPdfGenerateVO value = new DmsPersistentPdfGenerateVO(); value.setObjectKey(empty(stored.getPath()) + stored.getFilename()); value.setSizeBytes(stored.getSize()); value.setChecksumSha256(stored.getHashInfo().getSha256().toLowerCase(Locale.ROOT)); value.setSourceChecksumSha256(form.getSourceChecksumSha256()); return value; } finally { TicketUtil.deleteTicket(ticket); } } private void validateSource(DmsPersistentPdfGenerateForm form) { String extension = extension(form.getFileName()); if (!"doc".equals(extension) && !"docx".equals(extension)) { throw new DataException("仅支持为 Word 文件生成持久化 PDF"); } MinioFileStorage storage = fileStorageService.getFileStorageVerify(form.getStoragePlatform()); if (!Objects.equals(storage.getBucketName(), form.getBucketName())) { throw new DataException("DMS 原文件存储桶与当前配置不匹配"); } } private DmsFilePreviewTicket sourceTicket(DmsPersistentPdfGenerateForm form) { DmsFilePreviewTicket ticket = new DmsFilePreviewTicket(); ticket.setScope(DmsFilePreviewTicket.SCOPE); ticket.setTenantId(form.getTenantId()); ticket.setUserId("dms-pdf-generator"); ticket.setFileVersionId(form.getFileVersionId()); ticket.setFileName(form.getFileName()); ticket.setMimeType(form.getMimeType()); ticket.setSizeBytes(form.getSizeBytes()); ticket.setStoragePlatform(form.getStoragePlatform()); ticket.setBucketName(form.getBucketName()); ticket.setBasePath(empty(form.getBasePath())); ticket.setObjectPath(form.getObjectPath()); ticket.setObjectName(form.getObjectName()); ticket.setWatermarks(Collections.emptyList()); ticket.setExpiresAt(System.currentTimeMillis() + TICKET_TTL_SECONDS * 1000L); return ticket; } private void deleteQuietly(FileInfo stored) { if (stored == null) return; try { fileStorageService.delete(stored); } catch (RuntimeException ignored) { } } private String extension(String fileName) { int dot = fileName == null ? -1 : fileName.lastIndexOf('.'); return dot < 0 ? "" : fileName.substring(dot + 1).toLowerCase(Locale.ROOT); } private String empty(String value) { return value == null ? "" : value; } private static final class GeneratedPdf implements MultipartFile { private final String fileName; private final byte[] content; private GeneratedPdf(String fileName, byte[] content) { this.fileName = fileName; this.content = content; } @Override public String getName() { return "file"; } @Override public String getOriginalFilename() { return fileName; } @Override public String getContentType() { return MediaType.APPLICATION_PDF_VALUE; } @Override public boolean isEmpty() { return content.length == 0; } @Override public long getSize() { return content.length; } @Override public byte[] getBytes() { return content.clone(); } @Override public InputStream getInputStream() { return new ByteArrayInputStream(content); } @Override public void transferTo(File destination) throws IOException { Files.write(destination.toPath(), content); } } }