package jnpf.fileStorage.dms; import jnpf.dmsEntity.download.model.DmsFileDownloadTicket; import jnpf.dmsEntity.preview.enums.DmsWatermarkTargetType; import jnpf.dmsEntity.preview.model.DmsFilePreviewTicket; import jnpf.dmsEntity.preview.model.DmsWatermarkSnapshot; import jnpf.exception.DataException; import jnpf.onlyoffice.PageWatermarkApi; import jnpf.onlyoffice.PageWatermarkRequest; import jnpf.util.JsonUtil; import jnpf.util.TicketUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.UUID; @Service public class DmsFileWatermarkService { private final DmsDocxWatermarkService docx; private final DmsImageWatermarkService image; private final DmsPdfWatermarkService pdf; private final PageWatermarkApi pageRenderer; private final java.util.function.Function sourceTicketIssuer; @Autowired public DmsFileWatermarkService(DmsDocxWatermarkService docx, DmsImageWatermarkService image, DmsPdfWatermarkService pdf, PageWatermarkApi pageRenderer) { this(docx, image, pdf, pageRenderer, ticket -> TicketUtil.createTicket(JsonUtil.getObjectToString(ticket), 180L)); } DmsFileWatermarkService(DmsDocxWatermarkService docx, PageWatermarkApi pageRenderer, java.util.function.Function sourceTicketIssuer) { this(docx, null, null, pageRenderer, sourceTicketIssuer); } DmsFileWatermarkService(DmsDocxWatermarkService docx, DmsImageWatermarkService image, PageWatermarkApi pageRenderer, java.util.function.Function sourceTicketIssuer) { this(docx, image, null, pageRenderer, sourceTicketIssuer); } DmsFileWatermarkService(DmsDocxWatermarkService docx, DmsImageWatermarkService image, DmsPdfWatermarkService pdf, PageWatermarkApi pageRenderer, java.util.function.Function sourceTicketIssuer) { this.docx = docx; this.image = image; this.pdf = pdf; this.pageRenderer = pageRenderer; this.sourceTicketIssuer = sourceTicketIssuer; } public byte[] apply(byte[] source, DmsFilePreviewTicket ticket) { return apply(source, ticket.getFileName(), ticket.getMimeType(), ticket.getWatermarks(), ticket); } public byte[] apply(byte[] source, DmsFileDownloadTicket ticket) { return apply(source, ticket.getFileName(), ticket.getMimeType(), ticket.getWatermarks(), ticket); } public byte[] loadAndApply(DmsImageWatermarkService.SourceReader sourceReader, DmsFilePreviewTicket ticket) { return loadAndApply(sourceReader, ticket.getFileName(), ticket.getMimeType(), ticket.getWatermarks(), ticket); } public byte[] loadAndApply(DmsImageWatermarkService.SourceReader sourceReader, DmsFileDownloadTicket ticket) { return loadAndApply(sourceReader, ticket.getFileName(), ticket.getMimeType(), ticket.getWatermarks(), ticket); } private byte[] loadAndApply(DmsImageWatermarkService.SourceReader sourceReader, String fileName, String mimeType, List marks, Object sourceContext) { DmsWatermarkTargetType targetType = DmsWatermarkTargetType.from(fileName, mimeType); if (marks != null && !marks.isEmpty() && targetType == DmsWatermarkTargetType.IMAGE) { if (image == null) { throw new DataException("图片水印服务未配置"); } return image.loadAndApply(sourceReader, fileName, mimeType, marks); } try { return apply(sourceReader.read(), fileName, mimeType, marks, sourceContext); } catch (DataException exception) { throw exception; } catch (Exception exception) { throw new DataException("读取 DMS 文件失败:" + exception.getMessage()); } } private byte[] apply(byte[] source, String fileName, String mimeType, List marks, Object sourceContext) { if (marks == null || marks.isEmpty()) { return source; } DmsWatermarkTargetType targetType = DmsWatermarkTargetType.from(fileName, mimeType); if (targetType == DmsWatermarkTargetType.IMAGE) { if (image == null) { throw new DataException("图片水印服务未配置"); } return image.apply(source, fileName, mimeType, marks); } if (targetType == DmsWatermarkTargetType.PDF) { if (pdf == null) { throw new DataException("PDF 水印服务未配置"); } return pdf.apply(source, marks); } if (targetType != DmsWatermarkTargetType.DOCX) { throw new DataException("该文件格式不支持动态水印"); } List everyPage = new ArrayList<>(); List selectedPages = new ArrayList<>(); for (DmsWatermarkSnapshot mark : marks) { (mark.getRenderPages() == null ? everyPage : selectedPages).add(mark); } if (selectedPages.isEmpty()) return docx.apply(source, everyPage); try { List overlays = docx.pageOverlays(source, selectedPages); String prefix = "DMS_CUSTOM_" + UUID.randomUUID().toString().replace("-", ""); // This delegated ticket is only sent server-to-server. Its source has no dynamic // watermarks, so Document Builder fetching it cannot recursively start another job. DmsFilePreviewTicket delegated = new DmsFilePreviewTicket(); BeanUtils.copyProperties(sourceContext, delegated); delegated.setScope(DmsFilePreviewTicket.SCOPE); delegated.setWatermarks(new ArrayList<>()); delegated.setExpiresAt(System.currentTimeMillis() + 180_000L); String sourceTicket = sourceTicketIssuer.apply(delegated); jnpf.base.ActionResult result = pageRenderer.render(new PageWatermarkRequest(delegated.getFileVersionId(), sourceTicket, prefix, overlays)); if (result == null || result.getCode() != 200 || result.getData() == null) { throw new DataException(result == null || result.getMsg() == null ? "OnlyOffice 指定页水印生成失败" : result.getMsg()); } int expected = overlays.stream().mapToInt(overlay -> overlay.getPages().size()).sum(); byte[] placed = DmsPageWatermarkAnchors.fix(result.getData(), prefix, expected); // Header overlays do not change body pagination. Apply them after the page job so // its source can remain the immutable original without publishing a temporary file. return docx.apply(placed, everyPage); } catch (DataException e) { throw e; } catch (Exception e) { throw new DataException("生成指定各页的 DOCX 水印失败"); } } }