刘光辉
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
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
package jnpf.fileStorage.dms;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jnpf.dmsEntity.storage.form.DmsFileUploadRegisterForm;
import jnpf.exception.DataException;
import jnpf.model.filecontent.FileContentImportForm;
import jnpf.model.filecontent.FileContentReceipt;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
 
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Locale;
 
/** Adapts the existing reader and storage method; never invokes registration or workflow callbacks. */
@Service
@RequiredArgsConstructor
public class FileContentImportService {
    private final DmsCommonFileReader reader;
    private final DmsFileStorageService storage;
    private final DmsApprovalPageMergeService approvalPageMergeService;
    private final DmsQianpiyeDocxWriter qianpiyeDocxWriter;
    private final DmsDocxCommentCleaner commentCleaner;
    private final ObjectMapper json;
 
    public boolean sameContent(jnpf.model.filecontent.FileContentComparisonForm form) {
        try {
            JsonNode before = json.readTree(form.getOriginal()), after = json.readTree(form.getCandidate());
            if (before == null || after == null || !before.isArray() || !after.isArray()
                    || before.isEmpty() || before.size() != after.size() || before.size() > 100)
                throw new DataException("文件内容比较参数无效");
            for (int i = 0; i < before.size(); i++) {
                if (!digest(before.get(i)).equals(digest(after.get(i)))) return false;
            }
            return true;
        } catch (java.io.IOException invalid) { throw new DataException("文件内容比较参数无效"); }
    }
 
    private String digest(JsonNode file) {
        // Existing local storage resolver rejects arbitrary URL fetching and traversal.
        return reader.withFile(file.path("url").asText(), file.toString(), "内容校验", source -> {
            try (InputStream input = source.getInputStream()) {
                return cn.hutool.crypto.digest.DigestUtil.sha256Hex(input);
            } catch (IOException failure) { throw new DataException("无法读取附件内容,请稍后重试"); }
        });
    }
 
    public FileContentReceipt importContent(FileContentImportForm form) {
        DmsFileUploadRegisterForm stored = reader.withFile(singleFileUrl(form.getFileJson()),
                form.getFileJson(), form.getFileName(), file -> store(form, file));
        FileContentReceipt receipt = new FileContentReceipt();
        BeanUtils.copyProperties(stored, receipt);
        return receipt;
    }
 
    private DmsFileUploadRegisterForm store(FileContentImportForm form, MultipartFile file) {
        String approvalPage = form.getSignaturePageFiles();
        MultipartFile source = isDocx(file) && form.getSignaturePageValues() != null
                && approvalPage != null && !approvalPage.isBlank()
                ? approvalPageMergeService.withoutApprovalPages(file) : file;
        MultipartFile renderedFile = isDocx(file) && form.getSignaturePageValues() != null
                ? qianpiyeDocxWriter.render(source, form.getSignaturePageValues(),
                        form.getApprovalFlowVersionId()) : file;
        if (approvalPage == null || approvalPage.isBlank() || !isDocx(file)) {
            return storeFormal(form, renderedFile);
        }
        return reader.withFile(singleFileUrl(approvalPage), approvalPage, "签批页", page -> {
            MultipartFile rendered = isDocx(page)
                    ? qianpiyeDocxWriter.render(page, form.getSignaturePageValues(),
                            form.getApprovalFlowVersionId()) : page;
            MultipartFile merged = approvalPageMergeService.replaceOrPrepend(renderedFile, rendered);
            return storeFormal(form, merged);
        });
    }
 
    private DmsFileUploadRegisterForm storeFormal(FileContentImportForm form, MultipartFile file) {
        MultipartFile formal = form.isRemoveDocxComments() && isDocx(file) ? commentCleaner.clean(file) : file;
        return storage.storeWorkflowContent(form.getDocumentId(), form.getFileVersionId(),
                withDocumentName(formal, form.getFileName(), file.getOriginalFilename()));
    }
 
    // Rename only formal content, after signature-page merging and comment cleanup.
    // Keep the source extension so a business title cannot change the file format.
    private MultipartFile withDocumentName(MultipartFile file, String documentName, String sourceName) {
        if (documentName == null || documentName.isBlank()) {
            throw new DataException("文件名称不能为空");
        }
        String name = documentName.trim();
        int dot = sourceName == null ? -1 : sourceName.lastIndexOf('.');
        String extension = dot < 0 ? "" : sourceName.substring(dot);
        if (!extension.isEmpty() && !name.toLowerCase(Locale.ROOT).endsWith(extension.toLowerCase(Locale.ROOT))) {
            name += extension;
        }
        return name.equals(file.getOriginalFilename()) ? file : new NamedMultipartFile(file, name);
    }
 
    private record NamedMultipartFile(MultipartFile delegate, String filename) implements MultipartFile {
        @Override public String getName() { return delegate.getName(); }
        @Override public String getOriginalFilename() { return filename; }
        @Override public String getContentType() { return delegate.getContentType(); }
        @Override public boolean isEmpty() { return delegate.isEmpty(); }
        @Override public long getSize() { return delegate.getSize(); }
        @Override public byte[] getBytes() throws IOException { return delegate.getBytes(); }
        @Override public InputStream getInputStream() throws IOException { return delegate.getInputStream(); }
        @Override public void transferTo(File dest) throws IOException { delegate.transferTo(dest); }
        @Override public void transferTo(Path dest) throws IOException { delegate.transferTo(dest); }
    }
 
    private boolean isDocx(MultipartFile file) {
        String name = file == null ? null : file.getOriginalFilename();
        return name != null && name.toLowerCase(java.util.Locale.ROOT).endsWith(".docx");
    }
 
    private String singleFileUrl(String fileJson) {
        try {
            JsonNode value = json.readTree(fileJson);
            if (value != null && value.isArray()) {
                if (value.size() != 1) throw new DataException("每次导入必须有且只有一个物理文件");
                value = value.get(0);
            }
            JsonNode url = value == null ? null : value.get("url");
            if (url == null || !url.isTextual() || url.textValue().isBlank())
                throw new DataException("附件缺少URL");
            return url.textValue();
        } catch (JsonProcessingException e) {
            throw new DataException("附件格式不正确");
        }
    }
}