package jnpf.elnService.template;
|
|
import feign.Response;
|
import jnpf.elnEntity.ElnDocumentFileVo;
|
import jnpf.exception.DataException;
|
import jnpf.file.FileApi;
|
import jnpf.file.FileUploadApi;
|
import jnpf.model.upload.UploadFileModel;
|
import jnpf.util.JsonUtil;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
import org.springframework.util.StringUtils;
|
import org.springframework.web.multipart.MultipartFile;
|
|
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayOutputStream;
|
import java.io.File;
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.nio.file.Files;
|
import java.security.MessageDigest;
|
import java.security.NoSuchAlgorithmException;
|
import java.util.Collections;
|
import java.util.List;
|
|
@Slf4j
|
@Service
|
public class ElnTemplateFileService {
|
|
private static final String STORAGE_TYPE = "annex";
|
private static final long MAX_DOCX_BYTES = 50L * 1024L * 1024L;
|
|
private final FileApi fileApi;
|
private final FileUploadApi fileUploadApi;
|
|
public ElnTemplateFileService(FileApi fileApi, FileUploadApi fileUploadApi) {
|
this.fileApi = fileApi;
|
this.fileUploadApi = fileUploadApi;
|
}
|
|
public String copyDocx(String sourceAttachments, String targetTemplateId) {
|
String targetFileId = targetTemplateId + ".docx";
|
try {
|
String sourceFileId = uniqueDocxFileId(sourceAttachments);
|
if (targetFileId.equals(sourceFileId)) {
|
throw new DataException("模板新旧文件不能相同");
|
}
|
byte[] sourceBytes = download(sourceFileId);
|
String folder = fileApi.getPath(STORAGE_TYPE);
|
if (!StringUtils.hasText(folder)) {
|
throw new DataException("模板文件目录不可用");
|
}
|
MultipartFile multipart = new InMemoryMultipartFile(targetFileId, sourceBytes);
|
if (!fileUploadApi.replaceFile(multipart, folder, targetFileId)) {
|
throw new DataException("模板文件复制失败");
|
}
|
byte[] targetBytes = download(targetFileId);
|
if (sourceBytes.length != targetBytes.length
|
|| !MessageDigest.isEqual(sha256(sourceBytes), sha256(targetBytes))) {
|
throw new DataException("模板文件回读校验失败");
|
}
|
ElnDocumentFileVo target = new ElnDocumentFileVo();
|
target.setFileId(targetFileId);
|
target.setName(targetFileId);
|
target.setFileExtension("docx");
|
target.setFileSize((long) targetBytes.length);
|
return JsonUtil.getObjectToString(Collections.singletonList(target));
|
} catch (DataException e) {
|
log.error("模板文件复制失败,targetTemplateId={}, targetFileId={}",
|
targetTemplateId, targetFileId);
|
throw e;
|
} catch (RuntimeException e) {
|
log.error("模板文件复制失败,targetTemplateId={}, targetFileId={}",
|
targetTemplateId, targetFileId);
|
throw new DataException("模板文件复制失败");
|
}
|
}
|
|
private String uniqueDocxFileId(String attachments) {
|
try {
|
List<ElnDocumentFileVo> files = JsonUtil.getJsonToList(attachments, ElnDocumentFileVo.class);
|
if (files == null) {
|
throw new DataException("模板附件无效");
|
}
|
String fileId = null;
|
for (ElnDocumentFileVo file : files) {
|
if (file != null && "docx".equalsIgnoreCase(file.getFileExtension())
|
&& StringUtils.hasText(file.getFileId())) {
|
if (fileId != null) {
|
throw new DataException("模板必须且只能包含一个 DOCX 附件");
|
}
|
fileId = file.getFileId().trim();
|
}
|
}
|
if (fileId == null) {
|
throw new DataException("模板必须且只能包含一个 DOCX 附件");
|
}
|
return fileId;
|
} catch (DataException e) {
|
throw e;
|
} catch (RuntimeException e) {
|
throw new DataException("模板附件无效");
|
}
|
}
|
|
private byte[] download(String fileId) {
|
if (!fileId.toLowerCase().endsWith(".docx")) {
|
throw new DataException("模板文件扩展名必须为 docx");
|
}
|
try (Response response = fileUploadApi.downFile(new UploadFileModel(STORAGE_TYPE, fileId))) {
|
if (response == null || response.status() < 200 || response.status() >= 300
|
|| response.body() == null) {
|
throw new DataException("模板文件下载失败");
|
}
|
Integer declaredLength = response.body().length();
|
if (declaredLength != null && (declaredLength == 0 || declaredLength > MAX_DOCX_BYTES)) {
|
throw new DataException("模板文件大小无效");
|
}
|
try (InputStream input = response.body().asInputStream();
|
ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
byte[] buffer = new byte[8192];
|
int total = 0;
|
int read;
|
while ((read = input.read(buffer)) != -1) {
|
total += read;
|
if (total > MAX_DOCX_BYTES) {
|
throw new DataException("模板文件超过 50MB 限制");
|
}
|
output.write(buffer, 0, read);
|
}
|
if (total == 0) {
|
throw new DataException("模板文件不能为空");
|
}
|
return output.toByteArray();
|
}
|
} catch (DataException e) {
|
throw e;
|
} catch (IOException | RuntimeException e) {
|
throw new DataException("模板文件下载失败");
|
}
|
}
|
|
private static byte[] sha256(byte[] bytes) {
|
try {
|
return MessageDigest.getInstance("SHA-256").digest(bytes);
|
} catch (NoSuchAlgorithmException e) {
|
throw new IllegalStateException("JVM 不支持 SHA-256", e);
|
}
|
}
|
|
private static final class InMemoryMultipartFile implements MultipartFile {
|
private final String fileName;
|
private final byte[] content;
|
|
private InMemoryMultipartFile(String fileName, byte[] content) {
|
this.fileName = fileName;
|
this.content = content;
|
}
|
|
@Override
|
public String getName() {
|
return "multipartFile";
|
}
|
|
@Override
|
public String getOriginalFilename() {
|
return fileName;
|
}
|
|
@Override
|
public String getContentType() {
|
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
}
|
|
@Override
|
public boolean isEmpty() {
|
return content.length == 0;
|
}
|
|
@Override
|
public long getSize() {
|
return content.length;
|
}
|
|
@Override
|
public byte[] getBytes() {
|
return content;
|
}
|
|
@Override
|
public InputStream getInputStream() {
|
return new ByteArrayInputStream(content);
|
}
|
|
@Override
|
public void transferTo(File destination) throws IOException {
|
Files.write(destination.toPath(), content);
|
}
|
}
|
}
|