前情介绍:

为了解决项目中的多图片上传,对以下两种方式对比分析:
方式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方法【多图片上传】

ali cloud oss java 上传重复图片_上传

方式2:选择压缩文件

对应调用接口:Controller的的uploadPicByZip方法【压缩图片上传】

ali cloud oss java 上传重复图片_压缩文件_02