Selaa lähdekoodia

电子档案代码优化

fuyuchuan 1 päivä sitten
vanhempi
commit
a3a0fc982a

+ 0 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/ArchiveController.java

@@ -6,7 +6,6 @@ import com.usky.vpp.service.VppArchiveService;
 import com.usky.vpp.service.vo.VppFileArchiveRequestVO;
 import com.usky.vpp.service.vo.VppFileArchiveResponseVO;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletResponse;

+ 2 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/mapper/VppContractTemplateMapper.java

@@ -2,9 +2,11 @@ package com.usky.vpp.mapper;
 
 import com.usky.common.mybatis.core.CrudMapper;
 import com.usky.vpp.domain.VppContractTemplate;
+import org.springframework.stereotype.Repository;
 
 /**
  * vpp_contract_template Mapper
  */
+@Repository
 public interface VppContractTemplateMapper extends CrudMapper<VppContractTemplate> {
 }

+ 2 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/mapper/VppFileArchiveMapper.java

@@ -2,9 +2,11 @@ package com.usky.vpp.mapper;
 
 import com.usky.common.mybatis.core.CrudMapper;
 import com.usky.vpp.domain.VppFileArchive;
+import org.springframework.stereotype.Repository;
 
 /**
  * vpp_file_archive Mapper
  */
+@Repository
 public interface VppFileArchiveMapper extends CrudMapper<VppFileArchive> {
 }

+ 0 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppArchiveService.java

@@ -3,7 +3,6 @@ package com.usky.vpp.service;
 import com.usky.common.core.bean.CommonPage;
 import com.usky.vpp.service.vo.VppFileArchiveRequestVO;
 import com.usky.vpp.service.vo.VppFileArchiveResponseVO;
-import org.springframework.http.ResponseEntity;
 
 import javax.servlet.http.HttpServletResponse;
 import java.util.List;

+ 108 - 26
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppArchiveServiceImpl.java

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.file.domain.FilesUpload;
 import com.ruoyi.file.mapper.FilesMapper;
 import com.usky.common.core.bean.CommonPage;
+import com.usky.common.core.exception.BusinessException;
 import com.usky.common.security.utils.SecurityUtils;
 import com.usky.vpp.domain.VppFileArchive;
 import com.usky.vpp.mapper.VppFileArchiveMapper;
@@ -13,17 +14,19 @@ import com.usky.vpp.service.VppArchiveService;
 import com.usky.vpp.service.vo.VppFileArchiveRequestVO;
 import com.usky.vpp.service.vo.VppFileArchiveResponseVO;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
 import org.springframework.stereotype.Service;
 import org.springframework.web.client.RestTemplate;
 
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletResponse;
-import java.io.ByteArrayOutputStream;
+import java.io.BufferedOutputStream;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
@@ -31,9 +34,16 @@ import java.nio.charset.StandardCharsets;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
@@ -48,6 +58,37 @@ public class VppArchiveServiceImpl implements VppArchiveService {
     @Autowired
     private VppFileArchiveMapper vppFileArchiveMapper;
 
+    /**
+     * 连接池化的 RestTemplate,避免每次创建新连接
+     */
+    private final RestTemplate restTemplate;
+
+    /**
+     * 并行下载线程池
+     */
+    private static final ExecutorService DOWNLOAD_EXECUTOR = new ThreadPoolExecutor(
+            4, 8, 60L, TimeUnit.SECONDS,
+            new LinkedBlockingQueue<>(200),
+            r -> {
+                Thread t = new Thread(r, "archive-download-");
+                t.setDaemon(true);
+                return t;
+            },
+            new ThreadPoolExecutor.CallerRunsPolicy()
+    );
+
+    {
+        // 配置 HttpClient 连接池(最大连接数、超时时间)
+        CloseableHttpClient httpClient = HttpClientBuilder.create()
+                .setMaxConnTotal(50)
+                .setMaxConnPerRoute(20)
+                .build();
+        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
+        factory.setConnectTimeout(10_000);
+        factory.setReadTimeout(30_000);
+        this.restTemplate = new RestTemplate(factory);
+    }
+
     @Override
     public Boolean create(VppFileArchiveRequestVO vo) {
         String username = SecurityUtils.getUsername();
@@ -126,7 +167,7 @@ public class VppArchiveServiceImpl implements VppArchiveService {
     @Override
     public Boolean upload(VppFileArchiveRequestVO vo) {
         if (vo.getId() == null) {
-            throw new RuntimeException("主键ID不能为空!");
+            throw new BusinessException("主键ID不能为空!");
         }
 
         LambdaQueryWrapper<VppFileArchive> queryWrapper = new LambdaQueryWrapper<>();
@@ -136,7 +177,7 @@ public class VppArchiveServiceImpl implements VppArchiveService {
         VppFileArchive existingRecord = vppFileArchiveMapper.selectOne(queryWrapper);
 
         if (existingRecord == null) {
-            throw new RuntimeException("档案记录不存在或已被删除!");
+            throw new BusinessException("档案记录不存在或已被删除!");
         }
 
         // 根据 name + url 关联 FilesUpload 表,同步更新文件信息
@@ -153,7 +194,7 @@ public class VppArchiveServiceImpl implements VppArchiveService {
                 existingRecord.setFileType(filesUpload.getType());
                 existingRecord.setFileSize(filesUpload.getSize());
             } else {
-                throw new RuntimeException("未找到匹配的文件记录,请确认文件名与路径是否正确!");
+                throw new BusinessException("未找到匹配的文件记录,请确认文件名与路径是否正确!");
             }
         }
 
@@ -223,6 +264,25 @@ public class VppArchiveServiceImpl implements VppArchiveService {
             return;
         }
 
+        // ========== 预解析文件名(去重处理,必须在并行下载之前完成)==========
+        List<DownloadTask> tasks = new ArrayList<>();
+        Set<String> usedNames = new HashSet<>();
+        for (VppFileArchive archive : archives) {
+            String fileUrl = archive.getFileUrl();
+            String fileName = archive.getFileName();
+            if (StringUtils.isBlank(fileUrl) || StringUtils.isBlank(fileName)) {
+                continue;
+            }
+            String zipEntryName = resolveDuplicateName(fileName, usedNames);
+            usedNames.add(zipEntryName);
+            tasks.add(new DownloadTask(zipEntryName, fileUrl));
+        }
+
+        if (tasks.isEmpty()) {
+            writeErrorResponse(response, HttpStatus.BAD_REQUEST.value(), "没有可下载的文件!");
+            return;
+        }
+
         // ========== 构造 ZIP 文件名 ==========
         String zipFileName = "电子档案_"
                 + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))
@@ -233,36 +293,43 @@ public class VppArchiveServiceImpl implements VppArchiveService {
         response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
                 "attachment; filename=" + encodeFileName(zipFileName));
 
-        // ========== 生成 ZIP 并写入响应流 ==========
-        RestTemplate restTemplate = new RestTemplate();
-        Set<String> usedNames = new HashSet<>();
+        // ========== 并行下载所有文件 ==========
+        List<CompletableFuture<DownloadTask>> futures = tasks.stream()
+                .map(task -> CompletableFuture.supplyAsync(() -> {
+                    try {
+                        byte[] bytes = restTemplate.getForObject(task.fileUrl, byte[].class);
+                        task.fileBytes = bytes;
+                    } catch (Exception e) {
+                        task.error = e.getMessage();
+                    }
+                    return task;
+                }, DOWNLOAD_EXECUTOR))
+                .collect(Collectors.toList());
 
+        // ========== 生成 ZIP 并写入响应流 ==========
         try (ServletOutputStream sos = response.getOutputStream();
-             ZipOutputStream zos = new ZipOutputStream(sos)) {
-
-            for (VppFileArchive archive : archives) {
-                String fileUrl = archive.getFileUrl();
-                String fileName = archive.getFileName();
-
-                if (StringUtils.isBlank(fileUrl) || StringUtils.isBlank(fileName)) {
-                    continue;
-                }
-
-                String zipEntryName = resolveDuplicateName(fileName, usedNames);
-                usedNames.add(zipEntryName);
+             BufferedOutputStream bos = new BufferedOutputStream(sos, 8192);
+             ZipOutputStream zos = new ZipOutputStream(bos)) {
 
+            for (CompletableFuture<DownloadTask> future : futures) {
+                DownloadTask task = future.join(); // 阻塞等待当前文件下载完成
                 try {
-                    byte[] fileBytes = restTemplate.getForObject(fileUrl, byte[].class);
-                    if (fileBytes != null && fileBytes.length > 0) {
-                        ZipEntry zipEntry = new ZipEntry(zipEntryName);
+                    if (task.error != null) {
+                        // 下载失败,写入错误信息
+                        ZipEntry errorEntry = new ZipEntry(task.zipEntryName + ".error.txt");
+                        zos.putNextEntry(errorEntry);
+                        zos.write(("下载失败: " + task.error).getBytes(StandardCharsets.UTF_8));
+                        zos.closeEntry();
+                    } else if (task.fileBytes != null && task.fileBytes.length > 0) {
+                        ZipEntry zipEntry = new ZipEntry(task.zipEntryName);
                         zos.putNextEntry(zipEntry);
-                        zos.write(fileBytes);
+                        zos.write(task.fileBytes);
                         zos.closeEntry();
                     }
                 } catch (Exception e) {
-                    ZipEntry errorEntry = new ZipEntry(zipEntryName + ".error.txt");
+                    ZipEntry errorEntry = new ZipEntry(task.zipEntryName + ".error.txt");
                     zos.putNextEntry(errorEntry);
-                    zos.write(("下载失败: " + e.getMessage()).getBytes(StandardCharsets.UTF_8));
+                    zos.write(("写入ZIP失败: " + e.getMessage()).getBytes(StandardCharsets.UTF_8));
                     zos.closeEntry();
                 }
             }
@@ -277,6 +344,21 @@ public class VppArchiveServiceImpl implements VppArchiveService {
         }
     }
 
+    /**
+     * 下载任务内部类,承载文件名、URL、下载结果
+     */
+    private static class DownloadTask {
+        final String zipEntryName;
+        final String fileUrl;
+        volatile byte[] fileBytes;
+        volatile String error;
+
+        DownloadTask(String zipEntryName, String fileUrl) {
+            this.zipEntryName = zipEntryName;
+            this.fileUrl = fileUrl;
+        }
+    }
+
     /**
      * 处理 ZIP 内重名文件,自动添加编号后缀
      * @param fileName  原始文件名