刘光辉
117 分钟以前 06f314545ac3514d7ce28939920866ae8188d31c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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 水印失败");
        }
    }
}