无人机大图压缩包上传
This commit is contained in:
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 异步执行异常处理
|
* 异步执行异常处理
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取无人机大图信息详细信息
|
* 获取无人机大图信息详细信息
|
||||||
*
|
*
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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是否相同
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user