From fe79bf282c9d7153096a18b0ee45c90fc7c959cc Mon Sep 17 00:00:00 2001 From: lcj <2331845269@qq.com> Date: Tue, 25 Nov 2025 12:29:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=A0=E4=BA=BA=E6=9C=BA=E5=A4=A7=E5=9B=BE?= =?UTF-8?q?=E5=8E=8B=E7=BC=A9=E5=8C=85=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/core/config/AsyncConfig.java | 12 ++ .../DroDroneBigPictureController.java | 11 ++ .../service/IDroDroneBigPictureService.java | 21 +++ .../impl/DroDroneBigPictureServiceImpl.java | 122 ++++++++++++++++++ 4 files changed, 166 insertions(+) diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java index d39652a0..5e5f8dab 100644 --- a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java @@ -59,6 +59,18 @@ public class AsyncConfig implements AsyncConfigurer { return executor; } + // 解压线程池(大文件操作,不需要太多线程) + @Bean("unzipExecutor") + public Executor unzipExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(2); + executor.setMaxPoolSize(4); + executor.setQueueCapacity(10); + executor.setThreadNamePrefix("unzip-"); + executor.initialize(); + return executor; + } + /** * 异步执行异常处理 */ diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/controller/DroDroneBigPictureController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/controller/DroDroneBigPictureController.java index b3d49584..0b63ca60 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/controller/DroDroneBigPictureController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/controller/DroDroneBigPictureController.java @@ -20,6 +20,7 @@ import org.dromara.drone.domain.vo.DroDroneBigPictureVo; import org.dromara.drone.service.IDroDroneBigPictureService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -57,6 +58,16 @@ public class DroDroneBigPictureController extends BaseController { ExcelUtil.exportExcel(list, "无人机大图信息", DroDroneBigPictureVo.class, response); } + /** + * 上传zip文件 + */ + @SaCheckPermission("drone:droneBigPicture:import") + @Log(title = "无人机大图信息", businessType = BusinessType.IMPORT) + @PostMapping("/uploadZip") + public R uploadZip(@RequestPart("file") MultipartFile file, @Validated(AddGroup.class) DroDroneBigPictureBo bo) { + return R.ok(droDroneBigPictureService.uploadZip(file, bo)); + } + /** * 获取无人机大图信息详细信息 * diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/IDroDroneBigPictureService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/IDroDroneBigPictureService.java index 05ab367c..d688de7a 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/IDroDroneBigPictureService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/IDroDroneBigPictureService.java @@ -7,7 +7,9 @@ import org.dromara.drone.domain.DroDroneBigPicture; import org.dromara.drone.domain.bo.DroDroneBigPictureBo; import org.dromara.drone.domain.bo.DroDroneBigPictureProgressVo; import org.dromara.drone.domain.vo.DroDroneBigPictureVo; +import org.springframework.web.multipart.MultipartFile; +import java.io.File; import java.util.Collection; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -125,4 +127,23 @@ public interface IDroDroneBigPictureService extends IService * @return 是否成功 */ CompletableFuture asyncUpdateCompressPicture(DroDroneBigPicture pic, DroDroneBigPicture oldPic); + + /** + * 上传zip文件 + * + * @param file zip文件 + * @param bo 无人机大图信息 + * @return 是否成功 + */ + Boolean uploadZip(MultipartFile file, DroDroneBigPictureBo bo); + + /** + * 解压zip文件 + * + * @param file zip文件 + * @param zipName zip文件名 + * @param tempDir 临时目录 + * @return 解压后的文件的对象存储Ids + */ + CompletableFuture> unzipAsync(MultipartFile file, String zipName, File tempDir); } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/impl/DroDroneBigPictureServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/impl/DroDroneBigPictureServiceImpl.java index 43e07de3..15f2eb7e 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/impl/DroDroneBigPictureServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/drone/service/impl/DroDroneBigPictureServiceImpl.java @@ -2,6 +2,9 @@ package org.dromara.drone.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ZipUtil; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -39,7 +42,9 @@ import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import java.io.File; import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; @@ -602,6 +607,123 @@ public class DroDroneBigPictureServiceImpl extends ServiceImpl { + String fileStr = result.stream() + .map(String::valueOf) + .collect(Collectors.joining(",")); + bo.setSmallPic(fileStr); + this.insertByBo(bo); + }) + .exceptionally(ex -> { + // 统一处理解压失败(包括 ZipUtil 等异常) + log.error("压缩包处理失败: {}", ex.getMessage(), ex); + // 保存数据 + DroDroneBigPicture picture = new DroDroneBigPicture(); + BeanUtils.copyProperties(bo, picture); + picture.setStatus("8"); + picture.setRemark(ex.getMessage()); + this.save(picture); + return null; + }).whenComplete((r, ex) -> { + // 不管成功失败,都删除临时文件 + FileUtil.del(tempDir); + }); + return true; + } + + /** + * 解压zip文件 + * + * @param file zip文件 + * @param zipName zip文件名 + * @param tempDir 临时目录 + * @return 解压后的文件的对象存储Ids + */ + @Async("unzipExecutor") + @Override + public CompletableFuture> unzipAsync(MultipartFile file, String zipName, File tempDir) { + // 保存 ZIP + File zipFile = new File(tempDir, zipName); + File unzipDir; + try { + file.transferTo(zipFile); + // 解压操作非常耗时 + unzipDir = ZipUtil.unzip(zipFile); + } catch (IOException e) { + // 记录错误日志 + log.error("解压失败:{}", zipFile.getAbsolutePath(), e); + // 抛出异常到上层 thenCompose,便于处理 + CompletableFuture> future = new CompletableFuture<>(); + future.completeExceptionally(new ServiceException("压缩包解压失败")); + return future; + } + // 找图片 + List imageFiles = FileUtil.loopFiles(unzipDir).stream() + .filter(this::isImage) + .toList(); + if (CollUtil.isEmpty(imageFiles)) { + // 抛出异常到上层 thenCompose,便于处理 + CompletableFuture> future = new CompletableFuture<>(); + future.completeExceptionally(new ServiceException("压缩包中没有图片")); + return future; + } + // 异步上传任务 + List> uploadFutures = new ArrayList<>(); + for (File img : imageFiles) { + SysOssVo upload = ossService.upload(img); + uploadFutures.add(CompletableFuture.completedFuture(upload.getOssId())); + } + return CompletableFuture.allOf(uploadFutures.toArray(new CompletableFuture[0])) + .thenApply(v -> uploadFutures.stream() + .map(CompletableFuture::join) + .toList()); + } + + /** + * 判断文件是否是图片 + * + * @param file 文件 + * @return 是否是图片 + */ + private boolean isImage(File file) { + // 方式 1:判断扩展名 + String ext = FileUtil.extName(file).toLowerCase(); + List imageExt = Arrays.asList("jpg", "jpeg", "png", "bmp", "gif", "webp"); + if (imageExt.contains(ext)) { + return true; + } + + // 方式 2:判断真实文件类型(更安全) + String type = FileTypeUtil.getType(file); + return type != null && (type.equals("jpg") || type.equals("png") + || type.equals("jpeg") || type.equals("gif") || type.equals("bmp")); + } + /** * 判断两个Ids是否相同 *