前情介绍:
为了解决项目中的多图片上传,对以下两种方式对比分析:
方式1:多图片上传+多线程处理
方式2:图片压缩上传+多线程处理(后台解压,压缩文件与源文件大小变化不大)
测试结果:经过两者对比测试,以用户体验耗时为准,图片大小1.5G左右。两者时间相差3s左右,最终决定使用方式1图片上传。
下面是两组测试对比结果:
(1)图片大小:2.04G,对比结果:
方式1 ---- 93s
方式2 ---- 94s
(2)图片大小:1.52G,对比结果:
方式1 ---- 74s
方式2 ---- 77s
1、Controller层:
方法1:uploadPic
处理方式:多图片上传+多线程
方法2:uploadPicByZip
处理方式:压缩图片上传+多线程
package com.company.projectname.controller;
import com.company.auth.controller.AuthBaseController;
import com.company.common.core.baseweb.domain.AjaxResult;
import com.company.common.core.utils.file.UnPackeUtil;
import com.company.common.framework.minio.MinioUtil;
import com.company.pvinspection.service.impl.ComponentAssetServiceImpl;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 图片上传Controller
* @author kally
*/
@RestController
@RequestMapping("/pv/bigFile")
public class TestBigFileUploadController extends AuthBaseController {
private static final Logger log = LoggerFactory.getLogger(ComponentAssetServiceImpl.class);
@Autowired
private MinioUtil minioUtil;
// 线程池大小
private static final int THREAD_POOL_SIZE = 10;
private final static String ZIP_FILE = "application/zip";
private final static String RAR_FILE = "application/vnd.rar";
@ApiOperation("多图片上传")
@PostMapping("/upload1")
public AjaxResult uploadPic(List<MultipartFile> multipartFiles) throws InterruptedException {
long startTime = System.currentTimeMillis() / 1000;
log.info("图片上传多线程--开始时间:" + startTime);
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
for (MultipartFile multipartFile : multipartFiles) {
executorService.execute(() -> {
String originalFilename = multipartFile.getOriginalFilename();
minioUtil.putObjectStream(multipartFile);
log.info("图片上传多线程--文件名:".concat(originalFilename));
});
}
// 关闭线程池
executorService.shutdown();
try {
// 等待所有任务执行完毕
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true) {
log.info("图片上传多线程--线程结束了吗?");
Thread.sleep(500);
if (executorService.isTerminated()) {
log.info("图片上传多线程--线程都结束了!");
break;
}
}
long endTime = System.currentTimeMillis() / 1000;
log.info("图片上传多线程--结束时间:" + endTime);
log.info("图片上传多线程--总耗时:" + (endTime - startTime));
return toAjax(1);
}
@ApiOperation("压缩文件上传")
@PostMapping("/upload2")
public AjaxResult uploadPicByZip(MultipartFile multipartFile) throws Exception {
long startTime = System.currentTimeMillis() / 1000;
log.info("压缩文件多线程上传--开始时间:" + startTime);
String packFilePath = uploadPack(multipartFile);
log.info("压缩文件多线程上传--解压成功,解压目录:" + packFilePath);
uploadImagesByThread(packFilePath);
long endTime = System.currentTimeMillis() / 1000;
log.info("压缩文件多线程上传--结束时间:" + endTime);
log.info("压缩文件多线程上传--总耗时:" + (endTime - startTime));
return toAjax(1);
}
/**
* 步骤1:获取上传的压缩文件
*
* @param uploadFile
*/
public String uploadPack(MultipartFile uploadFile) throws Exception {
boolean isZipPack = true;
// 解压目录
String packFileStr = "D:\\zip";
// 不存在则创建
File packFile = new File(packFileStr);
if (!packFile.exists()) {
boolean mkdirs = packFile.mkdirs();
}
if (uploadFile == null) {
throw new RuntimeException("请上传文件");
}
String contentType = uploadFile.getContentType();
String filename = uploadFile.getOriginalFilename();
// 将压缩包保存在指定路径
String packFilePath = packFileStr + File.separator + filename;
if (ZIP_FILE.equals(contentType)) {
// zip解压缩处理
} else if (RAR_FILE.equals(contentType)) {
// rar解压缩处理
isZipPack = false;
} else {
throw new RuntimeException("上传的压缩包格式不正确,仅支持rar和zip压缩文件!");
}
File file = new File(packFilePath);
try {
uploadFile.transferTo(file);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("压缩文件到:" + packFileStr + " 失败!");
}
if (isZipPack) {
// zip压缩包
UnPackeUtil.unPackZip(file, null, packFileStr);
} else {
// rar压缩包
UnPackeUtil.unPackRar(file, packFileStr);
}
// 获取压缩包名称
filename = filename.substring(0, filename.lastIndexOf("."));
// 可以根据解压路径、压缩包名称、文件名称,取出对应文件进行操作
packFileStr = packFileStr.concat("\\").concat(filename);
return packFileStr;
}
/**
* 步骤2:多线程上传文件
*/
public void uploadImagesByThread(String folderPath) throws InterruptedException {
File folder = new File(folderPath);
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
// 多个文件上传
for (File image : folder.listFiles()) {
if (image.isFile()) {
executorService.execute(() -> {
minioUtil.uploadObjectAndObjectUrl(image.getName(), image.getPath());
log.info("压缩文件多线程上传--文件名:".concat(image.getName()));
});
}
}
// 关闭线程池
executorService.shutdown();
try {
// 等待所有任务执行完毕
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true) {
log.info("压缩文件多线程上传--结束了吗?");
Thread.sleep(500);
if (executorService.isTerminated()) {
log.info("压缩文件多线程上传--都结束了!");
break;
}
}
log.info("压缩文件多线程上传--结束时间1:" + (System.currentTimeMillis() / 1000));
}
/**
* 步骤3:上传图片
*
* @param folder
*/
private void uploadImages(File folder) {
File[] files = folder.listFiles((dir, name) -> name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".png"));
for (File file : files) {
System.out.println("Uploading: " + file.getName() + "filePath: " + file.getPath());
minioUtil.uploadObjectAndObjectUrl(file.getName(), file.getPath());
}
}
}
2、工具类,实现对rar、zip压缩包的解压操作
package com.company.common.core.utils.file;
import net.lingala.zip4j.core.ZipFile;
import net.sf.sevenzipjbinding.IInArchive;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.RandomAccessFile;
/**
* 编写工具类,实现对rar、zip压缩包的解压操作
* @author kally
* @date 2023/5/26
*/
public class UnPackeUtil {
private static final Logger logger = LoggerFactory.getLogger(UnPackeUtil.class);
/**
* zip文件解压
*
* @param destPath 解压文件路径
* @param zipFile 压缩文件
* @param password 解压密码(如果有)
*/
public static void unPackZip(File zipFile, String password, String destPath) {
try {
ZipFile zip = new ZipFile(zipFile);
/*zip4j默认用GBK编码去解压,这里设置编码为GBK的*/
zip.setFileNameCharset("GBK");
("begin unpack zip file....");
zip.extractAll(destPath);
// 如果解压需要密码
if (password != null) {
if (zip.isEncrypted()) {
zip.setPassword(password);
}
}
} catch (Exception e) {
logger.error("解压失败:", e.getMessage(), e);
}
}
/**
* rar文件解压(不支持有密码的压缩包)
*
* @param rarFile rar压缩包
* @param destPath 解压保存路径
*/
public static void unPackRar(File rarFile, String destPath) throws Exception {
RandomAccessFile randomAccessFile = null;
IInArchive inArchive = null;
// 第一个参数是需要解压的压缩包路径,第二个参数参考JdkAPI文档的RandomAccessFile
randomAccessFile = new RandomAccessFile(rarFile.getPath(), "r");
inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile));
int[] in = new int[inArchive.getNumberOfItems()];
for (int i = 0; i < in.length; i++) {
in[i] = i;
}
// 使用回调函数
inArchive.extract(in, false, new ExtractCallback(inArchive, destPath));
}
}
3、pom.xml
<dependencies>
<!-- 引入解压相关压缩包 -->
<dependency>
<groupId>net.lingala</groupId>
<artifactId>zip4j</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>net.sf.sevenzipjbinding</groupId>
<artifactId>sevenzipjbinding</artifactId>
<version>16.02-2.01</version>
</dependency>
</dependencies>
4:前端页面【两种方法】
方法1:选择文件夹下的所有图片
对应调用接口:Controller的的uploadPic方法【多图片上传】
方式2:选择压缩文件
对应调用接口:Controller的的uploadPicByZip方法【压缩图片上传】