刘光辉
4 小时以前 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
package jnpf.dmsService;
 
import feign.Response;
import jnpf.file.FileUploadApi;
import jnpf.model.upload.UploadFileModel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
 
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
 
/** Reads the flow-version binding from a qianpiye DOCX without exposing file bytes to the plugin. */
@Service
@RequiredArgsConstructor
@Slf4j
public class DmsQianpiyeDocxPropertiesReader {
    private static final String CUSTOM_PROPERTIES_PART = "docProps/custom.xml";
    private static final String NEW_FLOW_VERSION_PROPERTY = "DMS_NEW_FLOW_VERSION_ID";
    private static final String EDIT_FLOW_VERSION_PROPERTY = "DMS_EDIT_FLOW_VERSION_ID";
    private static final int MAX_CUSTOM_PROPERTIES_BYTES = 512 * 1024;
 
    private final FileUploadApi fileUploadApi;
 
    public Map<String, String> read(String fileType, String fileId) {
        if (!StringUtils.hasText(fileType) || !StringUtils.hasText(fileId)) return Map.of();
        try (Response response = fileUploadApi.downFile(new UploadFileModel(fileType, fileId))) {
            if (response == null || response.status() < 200 || response.status() >= 300 || response.body() == null) {
                return Map.of();
            }
            try (InputStream input = response.body().asInputStream()) {
                byte[] custom = findCustomProperties(input);
                return custom == null ? Map.of() : parse(custom);
            }
        } catch (Exception e) {
            // Existing templates must remain editable when their original file is unavailable.
            log.debug("读取签批页 DOCX 自定义属性失败,file={}", fileId, e);
            return Map.of();
        }
    }
 
    public static boolean hasCompleteFlowVersions(Map<String, String> properties) {
        return StringUtils.hasText(properties.get(NEW_FLOW_VERSION_PROPERTY))
                && StringUtils.hasText(properties.get(EDIT_FLOW_VERSION_PROPERTY));
    }
 
    public static String newFlowVersionId(Map<String, String> properties) {
        return properties.get(NEW_FLOW_VERSION_PROPERTY);
    }
 
    public static String revisionFlowVersionId(Map<String, String> properties) {
        return properties.get(EDIT_FLOW_VERSION_PROPERTY);
    }
 
    private byte[] findCustomProperties(InputStream source) throws Exception {
        try (ZipInputStream input = new ZipInputStream(source)) {
            ZipEntry entry;
            while ((entry = input.getNextEntry()) != null) {
                if (!CUSTOM_PROPERTIES_PART.equals(entry.getName())) continue;
                ByteArrayOutputStream output = new ByteArrayOutputStream();
                byte[] buffer = new byte[8192];
                int total = 0;
                for (int read; (read = input.read(buffer)) >= 0; ) {
                    total += read;
                    if (total > MAX_CUSTOM_PROPERTIES_BYTES) return null;
                    output.write(buffer, 0, read);
                }
                return output.toByteArray();
            }
        }
        return null;
    }
 
    private Map<String, String> parse(byte[] source) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        org.w3c.dom.Document document = factory.newDocumentBuilder().parse(new ByteArrayInputStream(source));
        NodeList nodes = document.getElementsByTagNameNS(
                "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties", "property");
        Map<String, String> result = new LinkedHashMap<>();
        for (int index = 0; index < nodes.getLength(); index++) {
            Element property = (Element) nodes.item(index);
            String name = property.getAttribute("name");
            if (NEW_FLOW_VERSION_PROPERTY.equals(name) || EDIT_FLOW_VERSION_PROPERTY.equals(name)) {
                // The editor may temporarily append a newer value. The final occurrence is authoritative.
                result.put(name, property.getTextContent().trim());
            }
        }
        return result;
    }
}