Java 上传压缩文件,解压excel数据及照片数据

1、pom文件(Java自带的有些zip无法解压,改用Apache插件)

<!-- 引入 apache commons-compress 依赖 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-compress</artifactId>
            <version>1.26.0</version>
        </dependency>

2、核心代码

I、文件缓存类

import lombok.Data;

import java.util.Date;

/**
 * 文件缓存VO类:存储文件关键信息
 */
@Data
public class ApacheFileCache {
    // 文件名(含路径)
    private String fileName;

    // 文件大小(字节)
    private long fileSize;

    // 文件内容
    private byte[] fileContent;

    // 缓存创建时间
    private Date createTime;

    public String getLastFileName() {
        // 找到最后一个"/"的位置
        int lastIndex = fileName.lastIndexOf("/");

        // 找到最后一个"."的位置
        int lastDotIndex = fileName.lastIndexOf(".");
        if (lastDotIndex < 0) {
            return fileName.substring(lastIndex + 1);
        } else {
            return fileName.substring(lastIndex + 1, lastDotIndex);
        }
    }

    public String getExtName() {
        // 找到最后一个"."的位置
        int lastDotIndex = fileName.lastIndexOf(".");
        if (lastDotIndex > 0) { // 确保存在后缀且不是以.开头的文件(如 .gitignore)
            return fileName.substring(lastDotIndex + 1);
        } else {
            return "";
        }
    }

}

II、文件解压类

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 压缩文件解压与缓存工具类(基于org.apache.commons.compress实现,解决MALFORMED问题)
 */
public class ApacheZipUtil {

    // 本地缓存:key-文件唯一标识(压缩包名+文件名),value-文件信息对象
    private static final Map<String, ApacheFileCache> FILE_CACHE = new ConcurrentHashMap<>();
    // 默认字符编码(解决中文文件名乱码)
    private static final String DEFAULT_ENCODING = "GBK"; // "UTF-8"; // 中文文件名改用GBK
    // 单个文件最大大小(100MB,防止OOM)
    private static final long MAX_FILE_SIZE = 1024 * 1024 * 100;

    /**
     * 解压MultipartFile压缩文件(ZIP),并将文件列表存入缓存
     *
     * @param multipartFile 上传的压缩文件(支持标准ZIP/加密ZIP/高版本ZIP)
     * @return 解压后的文件列表
     * @throws IOException 解压过程中的IO异常
     */
    public static List<ApacheFileCache> unzipAndCache(MultipartFile multipartFile) throws IOException {
        // 1. 基础校验
        if (multipartFile == null || multipartFile.isEmpty()) {
            throw new IllegalArgumentException("上传的压缩文件不能为空");
        }
        String originalFilename = multipartFile.getOriginalFilename();
        if (originalFilename == null || !originalFilename.toLowerCase().endsWith(".zip")) {
            throw new IllegalArgumentException("仅支持上传zip格式的压缩文件");
        }

        // 2. 校验文件是否为有效ZIP(提前检测损坏文件)
        if (!isValidZipFile(multipartFile)) {
            throw new IOException("压缩文件格式损坏,无法解压(非标准ZIP文件)");
        }

        // 3. 使用commons-compress解压(核心改造点)
        List<ApacheFileCache> fileList = new ArrayList<>();
        try (InputStream inputStream = multipartFile.getInputStream();
             // 关键:指定编码,解决文件名乱码和MALFORMED问题
             ZipArchiveInputStream zipInputStream = new ZipArchiveInputStream(
                     inputStream, DEFAULT_ENCODING, false, true)) {

            ZipArchiveEntry zipEntry;
            // 遍历压缩包中的每个条目
            while ((zipEntry = zipInputStream.getNextZipEntry()) != null) {
                // 跳过目录和空条目
                if (zipEntry.isDirectory() || zipEntry.getSize() == 0) {
                    continue;
                }

                String fileName = zipEntry.getName();
                // 校验单个文件大小,防止OOM
                if (zipEntry.getSize() > MAX_FILE_SIZE) {
                    System.err.println("文件[" + fileName + "]大小超过100MB,跳过解压");
                    continue;
                }

                // 读取文件内容(容错处理)
                byte[] fileContent;
                try {
                    fileContent = readZipEntryContent(zipInputStream, zipEntry.getSize());
                } catch (Exception e) {
                    System.err.println("读取文件[" + fileName + "]失败:" + e.getMessage());
                    continue;
                }

                // 构建文件缓存对象
                ApacheFileCache fileCacheVO = new ApacheFileCache();
                fileCacheVO.setFileName(fileName);
                fileCacheVO.setFileSize(zipEntry.getSize());
                fileCacheVO.setFileContent(fileContent);
                fileCacheVO.setCreateTime(new Date());

                // 存入缓存(key=压缩包名+文件名,避免重复)
                String cacheKey = originalFilename + "_" + fileName;
                FILE_CACHE.put(cacheKey, fileCacheVO);
                fileList.add(fileCacheVO);
            }
        } catch (Exception e) {
            throw new IOException("解压ZIP文件失败:" + e.getMessage(), e);
        }

        return fileList;
    }

    /**
     * 校验文件是否为有效ZIP(通过魔数检测,兼容更多ZIP格式)
     *
     * @param multipartFile 上传的压缩文件
     * @return true-有效,false-无效
     */
    private static boolean isValidZipFile(MultipartFile multipartFile) {
        try (InputStream is = multipartFile.getInputStream()) {
            // ZIP文件魔数:0x50 0x4B 0x03 0x04(标准ZIP)或 0x50 0x4B 0x05 0x06(空ZIP)或 0x50 0x4B 0x07 0x08(分卷ZIP)
            byte[] header = new byte[4];
            int read = is.read(header);
            if (read != 4) {
                return false;
            }
            return (header[0] == 0x50 && header[1] == 0x4B)
                    && (header[2] == 0x03 || header[2] == 0x05 || header[2] == 0x07)
                    && (header[3] == 0x04 || header[3] == 0x06 || header[3] == 0x08);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 读取ZipArchiveEntry的文件内容为字节数组(commons-compress版本)
     *
     * @param zipInputStream ZipArchive输入流
     * @param fileSize       文件大小(用于提前初始化缓冲区,提升性能)
     * @return 文件内容字节数组
     * @throws IOException 读取异常
     */
    private static byte[] readZipEntryContent(ZipArchiveInputStream zipInputStream, long fileSize) throws IOException {
        // 根据文件大小初始化缓冲区,减少IO次数
        int bufferSize = fileSize > 0 && fileSize < 4096 ? (int) fileSize : 4096;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(bufferSize);
        byte[] buffer = new byte[bufferSize];
        int len;
        long totalRead = 0;

        while ((len = zipInputStream.read(buffer)) != -1) {
            totalRead += len;
            // 二次校验文件大小,防止压缩包中文件大小标识错误导致OOM
            if (totalRead > MAX_FILE_SIZE) {
                throw new IOException("单个文件超过100MB,拒绝读取");
            }
            outputStream.write(buffer, 0, len);
        }
        return outputStream.toByteArray();
    }

    // ========== 缓存操作辅助方法(与原代码一致) ==========
    public static ApacheFileCache getFileFromCache(String cacheKey) {
        return FILE_CACHE.get(cacheKey);
    }

    public static void clearCache() {
        FILE_CACHE.clear();
    }

    public static Collection<ApacheFileCache> getAllCachedFiles() {
        return FILE_CACHE.values();
    }

}

III、调用示例

import xxx.ApacheFileCache;
import xxx.ApacheZipUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@RestController
@Api("File")
@Slf4j
public class FileController {

    @PostMapping("/upload/zip")
    @ApiOperation("zip")
    public String uploadAndUnzip(@RequestParam("file") MultipartFile file) throws Exception {
        try {
            // 解压并缓存文件
            List<ApacheFileCache> fileList = ApacheZipUtil.unzipAndCache(file);

            // 示例:输出解压后的文件信息
            StringBuilder sb = new StringBuilder();
            sb.append("解压成功,共解压 ").append(fileList.size()).append(" 个文件:\n");
            for (ApacheFileCache fileVO : fileList) {
                sb.append("文件名:").append(fileVO.getFileName())
                        .append(",大小:").append(fileVO.getFileSize()).append(" 字节\n");
                log.info(fileVO.getLastFileName());
                log.info(fileVO.getExtName());
            }
            return sb.toString();
        } catch (Exception e) {
            return "处理失败:" + e.getMessage();
        }
    }
}

 

posted @ 2026-03-09 08:46  都是城市惹的祸  阅读(0)  评论(0)    收藏  举报