|
@@ -91,6 +91,7 @@ import javax.servlet.http.HttpServletResponse;
|
|
|
import java.io.*;
|
|
|
import java.math.BigDecimal;
|
|
|
import java.net.URL;
|
|
|
+import java.net.URLEncoder;
|
|
|
import java.nio.file.Files;
|
|
|
import java.nio.file.Path;
|
|
|
import java.nio.file.Paths;
|
|
@@ -102,6 +103,9 @@ import java.util.concurrent.ExecutorService;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
import java.util.stream.Collectors;
|
|
|
+import java.util.zip.Deflater;
|
|
|
+import java.util.zip.ZipEntry;
|
|
|
+import java.util.zip.ZipOutputStream;
|
|
|
|
|
|
/**
|
|
|
* 服务实现类
|
|
@@ -3701,82 +3705,127 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
|
|
|
@Override
|
|
|
public void batchDownloadFileToZip(String ids, HttpServletResponse response) {
|
|
|
List<Long> longs = Func.toLongList(ids);
|
|
|
- //获取档案下的文件
|
|
|
+ // 获取档案下的文件
|
|
|
List<ArchiveFile> result = baseMapper.batchSearchArchiveFile(longs);
|
|
|
- //判断是否存在文件
|
|
|
- if (result != null && result.size() > 0) {
|
|
|
- //获取选择的案卷,只要id和文件提名字段
|
|
|
- Map<Long, String> nameMap = baseMapper.getArchives(longs).stream().filter(l -> StringUtils.isNotBlank(l.getName())).collect(Collectors.toMap(l -> l.getId(), l -> l.getName()));
|
|
|
- //默认下载地址
|
|
|
- String defaultDir = "/www/wwwroot/Users/hongchuangyanfa/Desktop/archiveDownload";
|
|
|
- //项目下地址
|
|
|
- String projectDir = defaultDir + "/" + result.get(0).getProjectId();
|
|
|
- File file = new File(projectDir);
|
|
|
- //获取项目名称
|
|
|
- ProjectInfo projectInfo = projectClient.getById(result.get(0).getProjectId());
|
|
|
- //判断文件夹是否存在,存在则该项目正在下载打包,不存在则生成
|
|
|
- if (file.exists()) {
|
|
|
- throw new ServiceException("当前项目正在下载档案,请稍后再试");
|
|
|
- } else {
|
|
|
- file.mkdir();
|
|
|
+
|
|
|
+ if (CollectionUtils.isEmpty(result)) {
|
|
|
+ throw new ServiceException("未找到可下载的文件");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取选择的案卷名称映射
|
|
|
+ Map<Long, String> nameMap = baseMapper.getArchives(longs).stream()
|
|
|
+ .filter(l -> StringUtils.isNotBlank(l.getName()))
|
|
|
+ .collect(Collectors.toMap(l -> l.getId(), l -> l.getName()));
|
|
|
+
|
|
|
+ // 使用临时目录而不是固定目录,避免并发问题
|
|
|
+ String tempDir = System.getProperty("java.io.tmpdir") + "/archive_download_" + UUID.randomUUID();
|
|
|
+ String projectDir = tempDir + "/" + result.get(0).getProjectId();
|
|
|
+ File projectDirFile = new File(projectDir);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 创建项目目录
|
|
|
+ if (!projectDirFile.mkdirs()) {
|
|
|
+ throw new ServiceException("无法创建临时目录");
|
|
|
}
|
|
|
- //删除掉pdfUrl为空的数据
|
|
|
+
|
|
|
+ // 获取项目信息
|
|
|
+ ProjectInfo projectInfo = projectClient.getById(result.get(0).getProjectId());
|
|
|
+ String zipFileName = projectInfo.getId() + ".zip";
|
|
|
+
|
|
|
+ // 过滤掉无PDF的文件
|
|
|
result.removeIf(query -> StringUtils.isEmpty(query.getPdfFileUrl()));
|
|
|
- //按照档案id分组
|
|
|
- Map<Long, List<ArchiveFile>> map = result.stream().collect(Collectors.groupingBy(ArchiveFile::getArchiveId));
|
|
|
- try {
|
|
|
- //为每个档案分别设置
|
|
|
- for (Long archiveId : map.keySet()) {
|
|
|
- String initName = nameMap.get(archiveId);
|
|
|
- String[] split = initName.split(")");
|
|
|
- String name = split[split.length - 1];
|
|
|
- String archiveDir = projectDir + "/" + name;
|
|
|
- //创建档案文件夹
|
|
|
- File file2 = new File(archiveDir);
|
|
|
- file2.mkdir();
|
|
|
- //组卷,下载PDF
|
|
|
+
|
|
|
+ // 按照档案id分组
|
|
|
+ Map<Long, List<ArchiveFile>> archiveFilesMap = result.stream()
|
|
|
+ .collect(Collectors.groupingBy(ArchiveFile::getArchiveId));
|
|
|
+
|
|
|
+ // 设置响应头 - 提前设置以便流式传输
|
|
|
+ response.setContentType("application/zip");
|
|
|
+ response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(zipFileName, "UTF-8"));
|
|
|
+
|
|
|
+ // 使用ZipOutputStream直接流式写入响应,避免生成临时zip文件
|
|
|
+ try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
|
|
|
+ // 设置压缩级别
|
|
|
+ zos.setLevel(Deflater.BEST_SPEED);
|
|
|
+
|
|
|
+ // 为每个档案处理文件
|
|
|
+ for (Map.Entry<Long, List<ArchiveFile>> entry : archiveFilesMap.entrySet()) {
|
|
|
+ Long archiveId = entry.getKey();
|
|
|
+ List<ArchiveFile> files = entry.getValue();
|
|
|
+ String archiveName = getArchiveName(nameMap.get(archiveId));
|
|
|
+
|
|
|
+ // 处理合并的档案PDF
|
|
|
String mergeArchivesFile = this.getMergeArchivesFile(archiveId);
|
|
|
- InputStream file_out2 = CommonUtil.getOSSInputStream(mergeArchivesFile);
|
|
|
- if (file_out2 != null) {
|
|
|
- CommonUtil.inputStreamToFile(file_out2, new File(archiveDir + "/" + name + ".pdf"));
|
|
|
- file_out2.close();
|
|
|
+ if (StringUtils.isNotBlank(mergeArchivesFile)) {
|
|
|
+ addFileToZip(zos, archiveName + "/" + archiveName + ".pdf", mergeArchivesFile);
|
|
|
}
|
|
|
|
|
|
- //下载卷内文件
|
|
|
- String archiveFileDir = archiveDir + "/" + "卷内文件";
|
|
|
- File file3 = new File(archiveFileDir);
|
|
|
- file3.mkdir();
|
|
|
- List<ArchiveFile> files = map.get(archiveId);
|
|
|
+ // 处理卷内文件
|
|
|
+ String innerDir = archiveName + "/卷内文件/";
|
|
|
for (ArchiveFile archiveFile : files) {
|
|
|
- String fileUrl = archiveFile.getPdfFileUrl();
|
|
|
- String initFileName = archiveFile.getFileName();
|
|
|
- if (initFileName.length() > 100) {
|
|
|
- initFileName = initFileName.substring(0, 100);
|
|
|
- }
|
|
|
- String fileName = "/" + initFileName + ".pdf";
|
|
|
- InputStream file_out = CommonUtil.getOSSInputStream(fileUrl);
|
|
|
- if (file_out != null) {
|
|
|
- CommonUtil.inputStreamToFile(file_out, new File(archiveFileDir + fileName));
|
|
|
- file_out.close();
|
|
|
- }
|
|
|
+ String fileName = getSafeFileName(archiveFile.getFileName()) + ".pdf";
|
|
|
+ addFileToZip(zos, innerDir + fileName, archiveFile.getPdfFileUrl());
|
|
|
}
|
|
|
}
|
|
|
- //下载完成,打包文件
|
|
|
- this.packageZip2(defaultDir, projectDir, projectInfo.getId());
|
|
|
- String zipFile = defaultDir + "/" + projectInfo.getId() + ".zip";
|
|
|
- Path path = Paths.get(zipFile);
|
|
|
- response.setContentType("application/zip");
|
|
|
- response.setHeader("Content-Disposition", "attachment;filename=" + projectInfo.getId() + "2.zip");
|
|
|
- // 获取文件内容流并写入响应
|
|
|
- Files.copy(path, response.getOutputStream());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new ServiceException("文件下载失败: " + e.getMessage());
|
|
|
+ } finally {
|
|
|
+ // 清理临时目录
|
|
|
+ deleteDirectory(new File(tempDir));
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- } catch (Exception e) {
|
|
|
- throw new ServiceException(e.getMessage());
|
|
|
- } finally {
|
|
|
- //删除文件与zip文件
|
|
|
- this.deleteFile(defaultDir, projectInfo.getId());
|
|
|
+ // 辅助方法:添加文件到zip流(分片下载)
|
|
|
+ private void addFileToZip(ZipOutputStream zos, String entryName, String fileUrl) throws IOException {
|
|
|
+ if (StringUtils.isBlank(fileUrl)) return;
|
|
|
+
|
|
|
+ ZipEntry zipEntry = new ZipEntry(entryName);
|
|
|
+ zos.putNextEntry(zipEntry);
|
|
|
+
|
|
|
+ try (InputStream in = CommonUtil.getOSSInputStream(fileUrl)) {
|
|
|
+ if (in == null) return;
|
|
|
+
|
|
|
+ byte[] buffer = new byte[8192]; // 8KB缓冲区
|
|
|
+ int len;
|
|
|
+ while ((len = in.read(buffer)) > 0) {
|
|
|
+ zos.write(buffer, 0, len);
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ zos.closeEntry();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 辅助方法:安全获取档案名称
|
|
|
+ private String getArchiveName(String initName) {
|
|
|
+ if (StringUtils.isBlank(initName)) return "未命名档案";
|
|
|
+ String[] split = initName.split(")");
|
|
|
+ return split[split.length - 1];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 辅助方法:安全文件名
|
|
|
+ private String getSafeFileName(String fileName) {
|
|
|
+ if (StringUtils.isBlank(fileName)) return "未命名文件";
|
|
|
+ // 限制文件名长度并移除非法字符
|
|
|
+ return fileName.substring(0, Math.min(100, fileName.length()))
|
|
|
+ .replaceAll("[\\\\/:*?\"<>|]", "");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 辅助方法:递归删除目录
|
|
|
+ private void deleteDirectory(File directory) {
|
|
|
+ if (directory == null || !directory.exists()) return;
|
|
|
+
|
|
|
+ File[] files = directory.listFiles();
|
|
|
+ if (files != null) {
|
|
|
+ for (File file : files) {
|
|
|
+ if (file.isDirectory()) {
|
|
|
+ deleteDirectory(file);
|
|
|
+ } else {
|
|
|
+ file.delete();
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+ directory.delete();
|
|
|
}
|
|
|
|
|
|
@Override
|