无人机大图压缩包上传

This commit is contained in:
lcj
2025-11-25 12:29:57 +08:00
parent 6b59d264b4
commit fe79bf282c
4 changed files with 166 additions and 0 deletions

View File

@ -59,6 +59,18 @@ public class AsyncConfig implements AsyncConfigurer {
return executor; 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;
}
/** /**
* 异步执行异常处理 * 异步执行异常处理
*/ */

View File

@ -20,6 +20,7 @@ import org.dromara.drone.domain.vo.DroDroneBigPictureVo;
import org.dromara.drone.service.IDroDroneBigPictureService; import org.dromara.drone.service.IDroDroneBigPictureService;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.util.List;
@ -57,6 +58,16 @@ public class DroDroneBigPictureController extends BaseController {
ExcelUtil.exportExcel(list, "无人机大图信息", DroDroneBigPictureVo.class, response); ExcelUtil.exportExcel(list, "无人机大图信息", DroDroneBigPictureVo.class, response);
} }
/**
* 上传zip文件
*/
@SaCheckPermission("drone:droneBigPicture:import")
@Log(title = "无人机大图信息", businessType = BusinessType.IMPORT)
@PostMapping("/uploadZip")
public R<Boolean> uploadZip(@RequestPart("file") MultipartFile file, @Validated(AddGroup.class) DroDroneBigPictureBo bo) {
return R.ok(droDroneBigPictureService.uploadZip(file, bo));
}
/** /**
* 获取无人机大图信息详细信息 * 获取无人机大图信息详细信息
* *

View File

@ -7,7 +7,9 @@ import org.dromara.drone.domain.DroDroneBigPicture;
import org.dromara.drone.domain.bo.DroDroneBigPictureBo; import org.dromara.drone.domain.bo.DroDroneBigPictureBo;
import org.dromara.drone.domain.bo.DroDroneBigPictureProgressVo; import org.dromara.drone.domain.bo.DroDroneBigPictureProgressVo;
import org.dromara.drone.domain.vo.DroDroneBigPictureVo; import org.dromara.drone.domain.vo.DroDroneBigPictureVo;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -125,4 +127,23 @@ public interface IDroDroneBigPictureService extends IService<DroDroneBigPicture>
* @return 是否成功 * @return 是否成功
*/ */
CompletableFuture<Boolean> asyncUpdateCompressPicture(DroDroneBigPicture pic, DroDroneBigPicture oldPic); CompletableFuture<Boolean> 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<List<Long>> unzipAsync(MultipartFile file, String zipName, File tempDir);
} }

View File

@ -2,6 +2,9 @@ package org.dromara.drone.service.impl;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert; 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 cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; 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.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
@ -602,6 +607,123 @@ public class DroDroneBigPictureServiceImpl extends ServiceImpl<DroDroneBigPictur
return CompletableFuture.completedFuture(b); return CompletableFuture.completedFuture(b);
} }
/**
* 上传zip文件
*
* @param file zip文件
* @param bo 无人机大图信息
* @return 是否成功
*/
@Override
public Boolean uploadZip(MultipartFile file, DroDroneBigPictureBo bo) {
if (file == null) {
throw new ServiceException("请选择文件");
}
String zipName = file.getOriginalFilename();
if (StringUtils.isBlank(zipName)) {
throw new ServiceException("请选择文件");
}
File tempDir = new File(System.getProperty("java.io.tmpdir"), "zip_" + System.currentTimeMillis());
if (!tempDir.exists()) {
boolean mkdirs = tempDir.mkdirs();
if (!mkdirs) {
throw new RuntimeException("创建临时目录失败");
}
}
// 异步解压
self.unzipAsync(file, zipName, tempDir)
.thenAccept(result -> {
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<List<Long>> 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<List<Long>> future = new CompletableFuture<>();
future.completeExceptionally(new ServiceException("压缩包解压失败"));
return future;
}
// 找图片
List<File> imageFiles = FileUtil.loopFiles(unzipDir).stream()
.filter(this::isImage)
.toList();
if (CollUtil.isEmpty(imageFiles)) {
// 抛出异常到上层 thenCompose便于处理
CompletableFuture<List<Long>> future = new CompletableFuture<>();
future.completeExceptionally(new ServiceException("压缩包中没有图片"));
return future;
}
// 异步上传任务
List<CompletableFuture<Long>> 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<String> 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是否相同 * 判断两个Ids是否相同
* *