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(); } } }
有些事情,没经历过不知道原理,没失败过不明白奥妙,没痛苦过不了解真谛。临渊羡鱼,不如退而结网!

浙公网安备 33010602011771号