|
|
@@ -2,25 +2,25 @@ package com.lc.ibps.cloud.file.provider;
|
|
|
|
|
|
import java.io.*;
|
|
|
import java.net.URL;
|
|
|
-import java.util.HashMap;
|
|
|
-import java.util.List;
|
|
|
+import java.net.URLEncoder;
|
|
|
import java.util.Map;
|
|
|
import java.util.Scanner;
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
|
+import java.util.zip.Deflater;
|
|
|
+import java.util.zip.ZipEntry;
|
|
|
+import java.util.zip.ZipOutputStream;
|
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
|
|
|
-import ch.qos.logback.core.joran.util.StringToObjectConverter;
|
|
|
-import cn.hutool.json.JSONArray;
|
|
|
import cn.hutool.json.JSONObject;
|
|
|
import cn.hutool.json.JSONUtil;
|
|
|
-import com.google.gson.JsonObject;
|
|
|
import com.lc.ibps.base.core.util.*;
|
|
|
import com.lc.ibps.base.framework.id.UniqueIdUtil;
|
|
|
import com.lc.ibps.cloud.redis.utils.RedisUtil;
|
|
|
import com.lc.ibps.common.file.persistence.entity.AttachmentPo;
|
|
|
-import org.apache.commons.io.FileUtils;
|
|
|
import org.jodconverter.OfficeDocumentConverter;
|
|
|
import org.jodconverter.office.DefaultOfficeManagerBuilder;
|
|
|
import org.jodconverter.office.OfficeException;
|
|
|
@@ -88,76 +88,74 @@ public class DownloadProvider extends GenericUploadProvider implements IDownload
|
|
|
public void downloadZip(
|
|
|
@ApiParam(name = "attachmentIds", value = "附件ID数组", required = true)
|
|
|
@RequestParam(name = "attachmentIds", required = true) String[] attachmentIds) {
|
|
|
- String realFilePath = null;
|
|
|
- String zipFilePath = null;
|
|
|
- Map<String, Integer> fileNameCounter = new HashMap<>(); // 用于处理重复文件名
|
|
|
+ // 初始化计数器用于重复文件名处理
|
|
|
+ Map<String, AtomicInteger> fileNameCounter = new ConcurrentHashMap<>();
|
|
|
try {
|
|
|
- // 创建临时目录
|
|
|
- String rootRealPath = AppFileUtil.getRealPath("/"+AppFileUtil.TEMP_PATH);
|
|
|
- String uuid = UniqueIdUtil.getId();
|
|
|
- String folderName = "downloads_" + uuid;
|
|
|
- realFilePath = rootRealPath + File.separator + folderName;
|
|
|
- File targetDir = new File(realFilePath);
|
|
|
- if (!targetDir.exists()) {
|
|
|
- targetDir.mkdirs();
|
|
|
- }
|
|
|
- // 循环处理每个文件
|
|
|
- this.getUploadService();
|
|
|
- for (String attachmentId : attachmentIds) {
|
|
|
- try {
|
|
|
- FileInfo fileInfo = uploadService.downloadFile(attachmentId.trim());
|
|
|
- if (fileInfo == null || fileInfo.getFileBytes() == null) {
|
|
|
- logger.warn("文件不存在: {}", attachmentId);
|
|
|
- continue;
|
|
|
- }
|
|
|
- // 生成唯一文件名
|
|
|
- String originalName = UploadUtil.getFileName(fileInfo.getFileName(),fileInfo.getExt());
|
|
|
- String safeName = generateUniqueName(originalName, fileNameCounter);
|
|
|
- // 写入临时文件
|
|
|
- File outputFile = new File(targetDir, safeName);
|
|
|
- FileUtils.writeByteArrayToFile(outputFile, fileInfo.getFileBytes());
|
|
|
- } catch (Exception e) {
|
|
|
- logger.error("文件处理失败: {}", attachmentId, e);
|
|
|
+ // 设置响应头(优化点1:使用RFC 5987标准编码)
|
|
|
+ HttpServletResponse response = this.getResponse();
|
|
|
+ response.setContentType("application/zip");
|
|
|
+ String folderName = "downloads_" + UniqueIdUtil.getId() + ".zip";
|
|
|
+ String encodedFileName = URLEncoder.encode(folderName, "UTF-8").replaceAll("\\+", "%20"); // 空格特殊处理
|
|
|
+ response.setHeader("Content-Disposition","attachment; filename*=UTF-8''" + encodedFileName);
|
|
|
+ // 直接使用ZIP流输出(优化点2:内存流式处理)
|
|
|
+ try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
|
|
|
+ zos.setMethod(ZipOutputStream.DEFLATED); // 启用压缩
|
|
|
+ zos.setLevel(Deflater.BEST_SPEED); // 平衡速度与压缩率
|
|
|
+ // 遍历处理每个附件(优化点3:边下载边压缩)
|
|
|
+ for (String attachmentId : attachmentIds) {
|
|
|
+ processAttachment(attachmentId.trim(), zos, fileNameCounter);
|
|
|
}
|
|
|
}
|
|
|
- // 压缩目录
|
|
|
- ZipUtil.zip(realFilePath, true); // 自动删除原目录
|
|
|
- String zipFileName = folderName + ".zip";
|
|
|
- zipFilePath = rootRealPath + File.separator + zipFileName;
|
|
|
- // 发送压缩包
|
|
|
- RequestUtil.downLoadFile(this.getRequest(),this.getResponse(),zipFilePath,zipFileName);
|
|
|
} catch (Exception e) {
|
|
|
- logger.error("/upload/downloadUse", e);
|
|
|
- } finally {
|
|
|
- // 清理残留文件(双重保障)
|
|
|
- if (realFilePath != null) {
|
|
|
- FileUtil.deleteDir(new File(realFilePath));
|
|
|
- }
|
|
|
- if (zipFilePath != null) {
|
|
|
- FileUtil.deleteFile(zipFilePath);
|
|
|
+ logger.error("压缩包生成失败", e);
|
|
|
+ this.getResponse().setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理单个附件并写入ZIP流
|
|
|
+ */
|
|
|
+ private void processAttachment(String attachmentId,ZipOutputStream zos,Map<String, AtomicInteger> fileNameCounter) {
|
|
|
+ try {
|
|
|
+ this.getUploadService();
|
|
|
+ FileInfo fileInfo = uploadService.downloadFile(attachmentId);
|
|
|
+ if (fileInfo == null || fileInfo.getFileBytes() == null) {
|
|
|
+ logger.warn("附件不存在: {}", attachmentId);
|
|
|
+ return;
|
|
|
}
|
|
|
+ // 生成安全文件名(优化点4:线程安全计数器)
|
|
|
+ String originalName = UploadUtil.getFileName(fileInfo.getFileName(), fileInfo.getExt());
|
|
|
+ String safeName = generateUniqueName(originalName, fileNameCounter);
|
|
|
+ // 创建ZIP条目并写入数据(优化点5:内存流处理)
|
|
|
+ ZipEntry entry = new ZipEntry(safeName);
|
|
|
+ entry.setComment("AttachmentID: " + attachmentId); // 可选元数据
|
|
|
+ zos.putNextEntry(entry);
|
|
|
+ zos.write(fileInfo.getFileBytes());
|
|
|
+ zos.closeEntry();
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("附件处理失败: {}", attachmentId, e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 生成唯一文件名(带序号)
|
|
|
*/
|
|
|
- private String generateUniqueName(String originalName, Map<String, Integer> counter) {
|
|
|
- // 处理特殊字符
|
|
|
+ private String generateUniqueName(String originalName,Map<String, AtomicInteger> counter) {
|
|
|
+ // 清理非法字符
|
|
|
String safeName = originalName.replaceAll("[\\\\/:*?\"<>|]", "_");
|
|
|
- // 处理重复文件名
|
|
|
- int count = counter.getOrDefault(safeName, 0);
|
|
|
- counter.put(safeName, count + 1);
|
|
|
- if (count == 0) {
|
|
|
+ // 原子操作保证线程安全
|
|
|
+ AtomicInteger count = counter.computeIfAbsent(safeName, k -> new AtomicInteger(0));
|
|
|
+ int currentCount = count.getAndIncrement();
|
|
|
+ if (currentCount == 0) {
|
|
|
return safeName;
|
|
|
} else {
|
|
|
int dotIndex = safeName.lastIndexOf('.');
|
|
|
if (dotIndex > 0) {
|
|
|
- String base = safeName.substring(0, dotIndex);
|
|
|
- String ext = safeName.substring(dotIndex);
|
|
|
- return base + "(" + count + ")" + ext;
|
|
|
+ return safeName.substring(0, dotIndex)
|
|
|
+ + "(" + currentCount + ")"
|
|
|
+ + safeName.substring(dotIndex);
|
|
|
}
|
|
|
- return safeName + "(" + count + ")";
|
|
|
+ return safeName + "(" + currentCount + ")";
|
|
|
}
|
|
|
}
|
|
|
|