zhoulisky 8 months ago
parent
commit
becdc36c27

+ 129 - 26
kd-service/kd-scientific/src/main/java/org/sky/scientific/controller/ArchiveCenterController.java

@@ -5,11 +5,15 @@ import com.alibaba.fastjson.JSON;
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
-import lombok.AllArgsConstructor;
 import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
+import org.apache.commons.compress.utils.IOUtils;
 import org.sky.core.boot.ctrl.KdController;
 import org.sky.core.secure.utils.SecureUtil;
 import org.sky.core.tool.api.R;
@@ -17,14 +21,17 @@ import org.sky.scientific.mapper.DownloadTaskMapper;
 import org.sky.scientific.pojo.entity.ArchiveAttachEntity;
 import org.sky.scientific.pojo.entity.DownloadTask;
 import org.sky.scientific.service.IArchiveService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpStatus;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.context.request.RequestAttributes;
 import org.springframework.web.context.request.RequestContextHolder;
 
 import java.io.*;
+import java.net.URLEncoder;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.zip.Deflater;
 
 /**
  * 科研档案管理 控制器
@@ -32,15 +39,18 @@ import java.io.*;
  * @author Kd
  * @since 2025-07-01
  */
+@Slf4j
 @RestController
-@AllArgsConstructor
 @RequestMapping("/archive")
 @Tag(name = "科研档案管理")
 public class ArchiveCenterController extends KdController {
 
-	private static final Logger log = LoggerFactory.getLogger(ArchiveCenterController.class);
-	private final IArchiveService service;
-	private final DownloadTaskMapper downloadTaskMapper;
+	@Resource
+	private IArchiveService service;
+	@Resource
+	private DownloadTaskMapper downloadTaskMapper;
+	@Value("${archive.dir}")
+	private String archiveDir;
 
 	@GetMapping("fjbczl")
 	@ApiOperationSupport(order = 1)
@@ -82,7 +92,7 @@ public class ArchiveCenterController extends KdController {
 		} else {
 			task.setYearAndMonth(task.getYearAndMonth());
 			task.setTenantId(SecureUtil.getTenantId());
-			int result = downloadTaskMapper.insertDownloadTask( task);
+			int result = downloadTaskMapper.insertDownloadTask(task);
 			RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
 			if (result == 1) {
 				new Thread(() -> {
@@ -107,33 +117,126 @@ public class ArchiveCenterController extends KdController {
 			writer.write(JSON.toJSONString(R.fail("下载任务不存在,请联系管理员")));
 			writer.flush();
 		} else if (task.getStatus() == 2) {
-			// 指定磁盘上已存在的ZIP文件路径
-			File zipFile = new File(task.getZipUrl());
+			String fileName = null;
+			if (task.getType() == 0) {
+				fileName = "全部档案资料";
+			} else if (task.getType() == 1) {
+				fileName = "高新备查资料";
+			} else {
+				fileName = "加计扣除备查资料";
+			}
+			Path folderPath = Paths.get(archiveDir, SecureUtil.getTenantId(), task.getYearAndMonth(), fileName);
 
-			// 检查文件是否存在
-			if (!zipFile.exists()) {
-				response.sendError(HttpStatus.NOT_FOUND.value(), "ZIP文件未找到");
+			File folder = folderPath.toFile();
+			if (!folder.exists() || !folder.isDirectory()) {
+				sendErrorResponse(response, "文件夹不存在或不是目录");
 				return;
 			}
+			setupZipResponseHeaders(response, task.getYearAndMonth() + "年" + fileName + ".zip");
+
+			try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(response.getOutputStream())) {
+				zos.setEncoding("UTF-8"); // 支持中文文件名
+				zos.setLevel(Deflater.BEST_SPEED);
 
-			// 设置响应头
-			response.setContentType("application/zip");
-			response.setHeader("Content-Disposition",
-				"attachment; filename=\"" + zipFile.getName() + "\"");
-			response.setContentLengthLong(zipFile.length());
+				compressFolderWithApache(folder, folder.getName(), zos);
 
-			// 将文件写入响应输出流
-			try (InputStream fileInputStream = new FileInputStream(zipFile);
-				 OutputStream responseOutputStream = response.getOutputStream()) {
+				zos.finish();
+				response.flushBuffer();
 
-				byte[] buffer = new byte[4096];
-				int bytesRead;
-				while ((bytesRead = fileInputStream.read(buffer)) != -1) {
-					responseOutputStream.write(buffer, 0, bytesRead);
+			} catch (IOException e) {
+				log.error("压缩文件夹失败: {}", folderPath, e);
+				if (!response.isCommitted()) {
+					sendErrorResponse(response, "文件压缩失败");
 				}
-				responseOutputStream.flush();
 			}
 
 		}
 	}
+
+	/**
+	 * 使用Apache Commons Compress压缩文件夹
+	 */
+	private void compressFolderWithApache(File folder, String basePath,
+										  ZipArchiveOutputStream zos) throws IOException {
+		File[] files = folder.listFiles();
+		if (files == null) return;
+
+		for (File file : files) {
+			String entryName = basePath + "/" + file.getName();
+
+			if (file.isDirectory()) {
+
+				// 创建目录条目
+				ZipArchiveEntry dirEntry = new ZipArchiveEntry(entryName + "/");
+				zos.putArchiveEntry(dirEntry);
+				zos.closeArchiveEntry();
+
+				// 递归处理子目录
+				compressFolderWithApache(file, entryName, zos);
+			} else {
+				// 创建文件条目
+				ZipArchiveEntry fileEntry = new ZipArchiveEntry(file, entryName);
+				zos.putArchiveEntry(fileEntry);
+
+				try (FileInputStream fis = new FileInputStream(file)) {
+					IOUtils.copy(fis, zos);
+				}
+				zos.closeArchiveEntry();
+			}
+		}
+	}
+
+	/**
+	 * 发送错误JSON响应
+	 */
+	private void sendErrorResponse(HttpServletResponse response, String message) throws IOException {
+		response.setContentType("application/json;charset=UTF-8");
+		response.setCharacterEncoding("UTF-8");
+		response.setStatus(HttpStatus.BAD_REQUEST.value());
+
+		try (PrintWriter writer = response.getWriter()) {
+			writer.write(JSON.toJSONString(R.fail(message)));
+			writer.flush();
+		}
+	}
+
+
+	/**
+	 * 设置ZIP下载响应头
+	 */
+	private void setupZipResponseHeaders(HttpServletResponse response, String fileName) {
+		response.setContentType("application/zip");
+		response.setCharacterEncoding("UTF-8");
+		response.setHeader("Content-Disposition",
+			"attachment; filename=\"" + encodeFileName(fileName) + "\"");
+		response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+	}
+
+
+	/**
+	 * 文件名编码处理(防止中文乱码)
+	 */
+	private String encodeFileName(String fileName) {
+		try {
+			return URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
+		} catch (UnsupportedEncodingException e) {
+			return fileName;
+		}
+	}
+
+	/**
+	 * 文件传输
+	 */
+	private void transferFileToResponse(File file, HttpServletResponse response) throws IOException {
+		try (FileInputStream fileInputStream = new FileInputStream(file);
+			 OutputStream responseOutputStream = response.getOutputStream()) {
+
+			byte[] buffer = new byte[4096];
+			int bytesRead;
+			while ((bytesRead = fileInputStream.read(buffer)) != -1) {
+				responseOutputStream.write(buffer, 0, bytesRead);
+			}
+			responseOutputStream.flush();
+		}
+	}
 }

+ 14 - 20
kd-service/kd-scientific/src/main/java/org/sky/scientific/service/impl/ArchiveServiceImpl.java

@@ -1,8 +1,6 @@
 package org.sky.scientific.service.impl;
 
 import cn.hutool.core.io.FileUtil;
-import cn.hutool.core.io.IORuntimeException;
-import cn.hutool.core.util.ZipUtil;
 import cn.hutool.http.HttpUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
@@ -203,6 +201,11 @@ public class ArchiveServiceImpl implements IArchiveService {
 		data.add(d);
 
 		d = new ZipDir();
+		d.setDirName("研发立项决议文件");
+		configZipDir(d, attach.getD13());
+		data.add(d);
+
+		d = new ZipDir();
 		d.setDirName("年度主要产品(服务)发挥核心支持作用的技术属于《国家重点支持的高新技术领域》规定范围的说明");
 		configZipDir(d, attach.getD4());
 		data.add(d);
@@ -279,14 +282,21 @@ public class ArchiveServiceImpl implements IArchiveService {
 		if (Func.isBlank(year)) {
 			throw new ServiceException("参数year不能为空");
 		}
-
+		String dir;
+		if (task.getType() == 0) {
+			dir = "全部档案资料";
+		} else if (task.getType() == 1) {
+			dir = "高新备查资料";
+		} else {
+			dir = "加计扣除备查资料";
+		}
 		Date date = new Date();
 
 		ArchiveAttachEntity archiveAttach = attachMapper.selectOne(Wrappers.<ArchiveAttachEntity>lambdaQuery().eq(ArchiveAttachEntity::getYearAndMonth, year));
 		if (archiveAttach == null) {
 			archiveAttach = new ArchiveAttachEntity();
 		}
-		Path basePath = Paths.get(archiveDir, SecureUtil.getTenantId(), year);
+		Path basePath = Paths.get(archiveDir, SecureUtil.getTenantId(), year, dir);
 		if (FileUtil.exist(basePath.toString())) {
 			FileUtil.del(basePath.toString());
 		}
@@ -448,24 +458,8 @@ public class ArchiveServiceImpl implements IArchiveService {
 			this.downloadAttachFiles(path28, Collections.singletonList(finalArchiveAttach.getD12()));
 		}));
  		CompletableFuture.allOf(futureList.toArray(new CompletableFuture[futureList.size()])).join();
-		Path zipPath = null;
-		if (task.getType() == 0) {
-			zipPath = Paths.get(archiveDir, SecureUtil.getTenantId(), year + "年全部档案资料.zip");
-		} else if (task.getType() == 1) {
-			zipPath = Paths.get(archiveDir, SecureUtil.getTenantId(), year + "年高新备查资料.zip");
-		} else {
-			zipPath = Paths.get(archiveDir, SecureUtil.getTenantId(), year + "年加计扣除备查资料.zip");
-		}
-		try {
-			ZipUtil.zip(basePath.toString(), zipPath.toString());
-		} catch (IORuntimeException e) {
-			log.error("创建zip文件失败,{}", e.getLocalizedMessage());
-		}
-		long fileSize = FileUtil.size(zipPath.toFile());
 		//设置任务状态为 已完成
 		task.setStatus(2);
-		task.setZipUrl(zipPath.toString());
-		task.setFileSize(fileSize);
 		task.setExpireTime(DateUtil.plusDays(new Date(), 1));
 		downloadTaskMapper.updateById(task);
 		System.out.println("一键导出耗时:" + (new Date().getTime() - date.getTime()) / 1000 + "秒");