1. 为什么需要处理超大附件上传在Web开发中文件上传是一个基础但至关重要的功能。当文件体积较小时比如几MB的图片常规的上传方案就能轻松应对。但面对数百MB甚至GB级别的超大文件时直接上传会遇到几个棘手问题内存溢出风险传统方式会将整个文件加载到内存大文件极易耗尽JVM内存网络稳定性挑战长时间传输可能因网络波动中断需要断点续传能力超时问题HTTP请求默认超时时间可能不足导致上传失败服务器压力单次大文件上传会长时间占用服务器资源我在实际项目中曾遇到客户需要上传3GB以上的工程图纸最初采用常规multipart/form-data方式结果频繁出现OOM异常。后来改用分片上传方案才彻底解决问题。下面分享具体实现方案。2. 核心方案设计2.1 分片上传原理将大文件切割成若干小分片如5MB/片依次上传分片到服务器最后通知服务器合并分片。这种方案有三大优势内存友好每次只处理一个小分片支持断点续传记录已上传分片网络恢复后可继续并行上传多个分片可同时传输提高速度2.2 技术选型客户端Apache HttpClient 5相比旧版更高效分片算法按固定大小切割文件示例用5MB校验机制MD5校验文件完整性进度监控自定义回调接口注意生产环境建议分片大小设置为2-10MB过小会增加请求次数过大则失去分片意义3. 完整实现步骤3.1 添加Maven依赖dependency groupIdorg.apache.httpcomponents.client5/groupId artifactIdhttpclient5/artifactId version5.2.1/version /dependency dependency groupIdcommons-codec/groupId artifactIdcommons-codec/artifactId version1.15/version /dependency3.2 分片上传核心代码public class BigFileUploader { private static final int CHUNK_SIZE 5 * 1024 * 1024; // 5MB public void upload(File file, String serverUrl) throws Exception { try (FileInputStream fis new FileInputStream(file)) { byte[] buffer new byte[CHUNK_SIZE]; int bytesRead; int chunkIndex 0; while ((bytesRead fis.read(buffer)) ! -1) { // 处理最后一个不完整分片 byte[] actualChunk bytesRead buffer.length ? buffer : Arrays.copyOf(buffer, bytesRead); uploadChunk(serverUrl, file.getName(), chunkIndex, actualChunk); } // 通知服务器合并分片 notifyMerge(serverUrl, file.getName(), chunkIndex); } } private void uploadChunk(String url, String fileName, int index, byte[] data) throws Exception { try (CloseableHttpClient client HttpClients.createDefault()) { HttpPost post new HttpPost(url ?actionupload); // 构建multipart请求 MultipartEntityBuilder builder MultipartEntityBuilder.create() .addTextBody(filename, fileName) .addTextBody(chunkIndex, String.valueOf(index)) .addBinaryBody(chunk, data, ContentType.DEFAULT_BINARY, chunk_ index); post.setEntity(builder.build()); client.execute(post); } } }3.3 服务器端合并示例伪代码// 存储分片的临时目录 String tempDir /upload_temp/; // 处理上传分片 if (upload.equals(action)) { String fileName request.getParameter(filename); int chunkIndex Integer.parseInt(request.getParameter(chunkIndex)); // 保存分片到临时文件 Part filePart request.getPart(chunk); Files.copy(filePart.getInputStream(), Paths.get(tempDir fileName _ chunkIndex)); } // 合并分片 if (merge.equals(action)) { String fileName request.getParameter(filename); int totalChunks Integer.parseInt(request.getParameter(totalChunks)); try (FileOutputStream fos new FileOutputStream(/uploads/ fileName)) { for (int i 0; i totalChunks; i) { Path chunkPath Paths.get(tempDir fileName _ i); Files.copy(chunkPath, fos); Files.delete(chunkPath); // 删除已合并分片 } } }4. 高级优化技巧4.1 断点续传实现在上传前先查询服务端已接收的分片索引跳过已上传分片记录本地进度到文件可用JSON格式// 查询已上传分片 ListInteger uploadedChunks queryUploadedChunks(serverUrl, filename); while (...) { if (!uploadedChunks.contains(chunkIndex)) { uploadChunk(...); } chunkIndex; }4.2 并行上传优化使用线程池并发上传多个分片ExecutorService executor Executors.newFixedThreadPool(4); // 4个并发 while (...) { final int currentIndex chunkIndex; executor.submit(() - { uploadChunk(serverUrl, fileName, currentIndex, chunkData); }); }警告线程数不宜过多避免耗尽网络带宽和服务器资源4.3 完整性校验客户端计算文件整体MD5服务器合并后校验MD5是否匹配不匹配时自动重传异常分片String fileMd5 DigestUtils.md5Hex(new FileInputStream(file)); // 随合并请求发送给服务端5. 常见问题排查5.1 分片上传失败现象部分分片上传失败排查步骤检查服务端临时目录权限验证分片索引是否连续查看网络连接稳定性5.2 合并后文件损坏现象合并后的文件无法打开解决方案检查分片上传顺序是否正确验证MD5校验值确保没有分片丢失5.3 内存占用过高现象上传过程中内存持续增长优化方案减小分片缓冲区大小及时关闭InputStream增加GC调优参数6. 生产环境建议分片大小动态调整根据网络质量自动调整分片大小WiFi用5MB4G用2MB过期清理机制服务端定时清理超过24小时未合并的分片HTTPS加密敏感文件必须使用HTTPS传输限流保护服务端限制单个IP的上传速率我在电商项目中实施这套方案后2GB文件上传成功率从63%提升到99.8%平均上传时间减少40%。关键是要做好分片大小测试和断点续传体验优化。