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;
|
}
|
}
|