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<DmsFilePreviewTicket, String> 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<DmsFilePreviewTicket, String> sourceTicketIssuer) {
|
this(docx, null, null, pageRenderer, sourceTicketIssuer);
|
}
|
|
DmsFileWatermarkService(DmsDocxWatermarkService docx, DmsImageWatermarkService image,
|
PageWatermarkApi pageRenderer,
|
java.util.function.Function<DmsFilePreviewTicket, String> sourceTicketIssuer) {
|
this(docx, image, null, pageRenderer, sourceTicketIssuer);
|
}
|
|
DmsFileWatermarkService(DmsDocxWatermarkService docx, DmsImageWatermarkService image,
|
DmsPdfWatermarkService pdf, PageWatermarkApi pageRenderer,
|
java.util.function.Function<DmsFilePreviewTicket, String> 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<DmsWatermarkSnapshot> 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<DmsWatermarkSnapshot> 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<DmsWatermarkSnapshot> everyPage = new ArrayList<>();
|
List<DmsWatermarkSnapshot> selectedPages = new ArrayList<>();
|
for (DmsWatermarkSnapshot mark : marks) {
|
(mark.getRenderPages() == null ? everyPage : selectedPages).add(mark);
|
}
|
if (selectedPages.isEmpty()) return docx.apply(source, everyPage);
|
try {
|
List<PageWatermarkRequest.Overlay> 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<byte[]> 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 水印失败");
|
}
|
}
|
}
|