三语言实现大文件分片上传实战
文件上传功能实战Java、Python、PHP 实现大文件分片上传在现代 Web 应用开发中大文件上传是一个常见且复杂的场景。传统一次性上传方式在面对 100MB 以上文件时容易因网络波动、超时或内存溢出导致失败。分片上传Chunked Upload通过将大文件拆分为多个小块并逐块上传显著提升了上传成功率与用户体验。本文将以 Java、Python、PHP 三种主流语言为例详细实现一套完整的分片上传系统涵盖前端控制、后端处理与断点续传逻辑。一、分片上传核心原理分片上传的核心思想是将大文件按固定大小如 5MB切分为若干数据块每个块独立上传并在服务端记录上传状态。上传完成后服务端合并所有分片生成完整文件。关键点包括分片大小设定建议 2~10MB唍验唯一标识如文件指纹md5上传进度追踪断点续传支持二、前端实现基于 JavaScript 的分片上传!DOCTYPE html html langzh head meta charsetUTF-8 / title大文件分片上传/title /head body input typefile idfileInput / button onclickuploadFile()开始上传/button div idprogress/div script const CHUNK_SIZE 5 * 1024 * 1024; // 5MB let fileBlob, chunks, totalChunks; document.getElementById(fileInput).addEventListener(change, e { fileBlob e.target.files[0]; totalChunks Math.ceil(fileBlob.size / CHUNK_SIZE); chunks []; for (let i 0; i totalChunks; i) { chunks.push(fileBlob.slice(i * CHUNK_SIZE, (i 1) * CHUNK_SIZE)); } }); async function uploadFile() { const formData new FormData(); const fileHash await getFileHash(fileBlob); // 生成文件指纹 formData.append(fileHash, fileHash); formData.append(totalChunks, totalChunks); const uploadedChunks await getUploadedChunks(fileHash); const pendingChunks chunks.filter((_, idx) !uploadedChunks.includes(idx)); const progressDiv document.getElementById(progress); let successCount 0; for (const [idx, chunk] of pendingChunks.entries()) { formData.set(chunkIndex, idx); formData.set(chunk, chunk); try { const res await fetch(/api/upload/chunk, { method: POST, body: formData, }); const result await res.json(); if (result.success) { successCount; progressDiv.innerHTML 已上传 ${successCount}/${pendingChunks.length} 个分片; } } catch (err) { console.error(上传失败:, err); } } // 所有分片上传完成触发合并 if (successCount pendingChunks.length) { await mergeChunks(fileHash); alert(上传完成); } } // 简化版文件哈希计算实际可用 crypto-js async function getFileHash(file) { return new Promise((resolve) { const reader new FileReader(); reader.onload () { const array new Uint8Array(reader.result); let hash 0; for (let i 0; i array.length; i) { hash ((hash 5) - hash) array[i]; hash | 0; } resolve(hash.toString(16)); }; reader.readAsArrayBuffer(file.slice(0, 1024)); // 取前1KB计算 }); } async function getUploadedChunks(fileHash) { const res await fetch(/api/upload/status?fileHash${fileHash}); const data await res.json(); return data.uploaded || []; } async function mergeChunks(fileHash) { await fetch(/api/upload/merge?fileHash${fileHash}, { method: POST }); } /script /body /html三、Java 后端实现Spring Boot使用 Spring Boot 搭建分片上传接口核心逻辑如下RestController RequestMapping(/api/upload) public class ChunkUploadController { private final String UPLOAD_DIR /tmp/uploads/; PostMapping(/chunk) public ResponseEntityMapString, Object uploadChunk( RequestParam(fileHash) String fileHash, RequestParam(chunkIndex) int chunkIndex, RequestParam(chunk) MultipartFile chunk) { Path dirPath Paths.get(UPLOAD_DIR fileHash); if (!Files.exists(dirPath)) { try { Files.createDirectories(dirPath); } catch (IOException e) { return ResponseEntity.status(500).body(Map.of(error, 创建目录失败)); } } Path filePath dirPath.resolve(String.format(%d.dat, chunkIndex)); try { Files.write(filePath, chunk.getBytes()); } catch (IOException e) { return ResponseEntity.status(500).body(Map.of(error, 保存分片失败)); } // 记录已上传分片 ListInteger uploaded getUploadedChunks(fileHash); uploaded.add(chunkIndex); saveUploadedChunks(fileHash, uploaded); return ResponseEntity.ok(Map.of(success, true, chunkIndex, chunkIndex)); } GetMapping(/status) public ResponseEntityMapString, Object getStatus(RequestParam(fileHash) String fileHash) { ListInteger uploaded getUploadedChunks(fileHash); return ResponseEntity.ok(Map.of(uploaded, uploaded)); } PostMapping(/merge) public ResponseEntityMapString, Object mergeChunks(RequestParam(fileHash) String fileHash) { Path dirPath Paths.get(UPLOAD_DIR fileHash); if (!Files.exists(dirPath)) { return ResponseEntity.badRequest().body(Map.of(error, 分片不存在)); } ListPath sortedFiles Files.list(dirPath) .sorted(Comparator.comparing(p - Integer.parseInt(p.getFileName().toString().split(\\.)[0]))) .toList(); Path targetPath Paths.get(UPLOAD_DIR fileHash .final); try (OutputStream out Files.newOutputStream(targetPath)) { for (Path file : sortedFiles) { Files.copy(file, out, StandardCopyOption.REPLACE_EXISTING); } } catch (IOException e) { return ResponseEntity.status(500).body(Map.of(error, 合并失败)); } // 清理临时分片 try { Files.walk(dirPath).forEach(path - { try { if (!path.equals(dirPath)) Files.delete(path); } catch (IOException ignored) {} }); } catch (IOException ignored) {} return ResponseEntity.ok(Map.of(success, true, filename, fileHash .final)); } private ListInteger getUploadedChunks(String fileHash) { // 从缓存或数据库读取 return new ArrayList(Arrays.asList(0, 1, 3)); // 示例 } private void saveUploadedChunks(String fileHash, ListInteger chunks) { // 实际应写入 Redis / DB } }四、Python 后端实现Flaskfrom flask import Flask, request, jsonify import hashlib import os import shutil app Flask(__name__) UPLOAD_DIR /tmp/uploads/ app.route(/api/upload/chunk, methods[POST]) def upload_chunk(): file_hash request.form[fileHash] chunk_index int(request.form[chunkIndex]) chunk request.files[chunk] dir_path os.path.join(UPLOAD_DIR, file_hash) if not os.path.exists(dir_path): os.makedirs(dir_path) chunk_path os.path.join(dir_path, f{chunk_index}.dat) chunk.save(chunk_path) # 记录已上传分片可替换为 Redis uploaded_file os.path.join(dir_path, .uploaded) with open(uploaded_file, a) as f: f.write(f{chunk_index}\n) return jsonify({success: True, chunkIndex: chunk_index}) app.route(/api/upload/status) def get_status(): file_hash request.args.get(fileHash) dir_path os.path.join(UPLOAD_DIR, file_hash) uploaded [] uploaded_file os.path.join(dir_path, .uploaded) if os.path.exists(uploaded_file): with open(uploaded_file, r) as f: uploaded [int(line.strip()) for line in f.readlines()] return jsonify({uploaded: uploaded}) app.route(/api/upload/merge, methods[POST]) def merge_chunks(): file_hash request.args.get(fileHash) dir_path os.path.join(UPLOAD_DIR, file_hash) target_path os.path.join(UPLOAD_DIR, f{file_hash}.final) files [f for f in os.listdir(dir_path) if f.endswith(.dat)] sorted_files sorted(files, keylambda x: int(x.split(.)[0])) with open(target_path, wb) as target: for fname in sorted_files: with open(os.path.join(dir_path, fname), rb) as src: target.write(src.read()) # 清理临时分片 shutil.rmtree(dir_path) return jsonify({success: True, filename: f{file_hash}.final}) if __name__ __main__: app.run(port5000)五、PHP 后端实现原生?php $uploadDir /tmp/uploads/; $fileHash $_POST[fileHash] ?? ; $chunkIndex (int)$_POST[chunkIndex]; $chunk $_FILES[chunk]; if (!$fileHash || !$chunk[size]) { http_response_code(400); echo json_encode([error 参数缺失]); exit; } $dirPath $uploadDir . $fileHash; if (!is_dir($dirPath)) { mkdir($dirPath, 0755, true); } $filePath $dirPath . / . $chunkIndex . .dat; move_uploaded_file($chunk[tmp_name], $filePath); // 记录已上传分片 $uploadedFile $dirPath . /.uploaded; file_put_contents($uploadedFile, $chunkIndex . \n, FILE_APPEND | LOCK_EX); echo json_encode([success true, chunkIndex $chunkIndex]); ?六、总结与优化建议安全性对上传路径进行白名单校验防止路径穿越。性能使用 Redis 缓存上传状态避免频繁磁盘操作。扩展性引入任务队列如 RabbitMQ处理大文件合并。日志监控记录上传时间、大小、失败原因便于排查。分片上传不仅是技术实现更是对高并发、高可靠系统的考验。掌握这套方案可轻松应对企业级文件传输需求。

相关新闻

自组织映射SOM原理与Python实现:从竞争学习到拓扑保持

自组织映射SOM原理与Python实现:从竞争学习到拓扑保持

简介:这份资源是面向机器学习初学者与数据科学学习者的SOM自组织映射神经网络Python实现项目,聚焦无监督学习中的数据降维、聚类与可视化需求。SOM通过竞争学习将高维输入映射到低维拓扑空间,是理解神经网络竞争机制的经典入门算法&#xff0…

2026/10/18 8:14:12 阅读更多 →
多步流程中的越权漏洞剖析与修复实践

多步流程中的越权漏洞剖析与修复实践

前阵子做一次授权安全评估,某项目的后台审批流程让我印象很深。这个系统有一条"发起申请 → 业务审批 → 办结归档"的三步链路,前两步做得挺规范,登录校验、状态控制、角色校验都到位了。问题恰恰出在最后一步:审批接口…

2026/10/18 8:14:12 阅读更多 →
开发岗没出路?四条转型路线与实操避坑指南

开发岗没出路?四条转型路线与实操避坑指南

做开发这些年,我越来越常听见身边人问:“开发岗是不是没出路了?”起因无外乎三种:投了一轮简历回复寥寥,面了几轮被源码级问题问到怀疑人生,或者眼看部门收缩、项目砍掉,今天还在维护老系统&…

2026/10/18 8:14:12 阅读更多 →

最新新闻

2026高性价比AI API聚合平台评测:六家服务实测排名与选择指南

2026高性价比AI API聚合平台评测:六家服务实测排名与选择指南

为了写这篇测评,笔者花两个月把市面主流的六家平台轮着用了一遍,统一在腾讯云上海节点发起调用,累计接口调用超过五十万次,再叠加团队几个在跑项目的真实表现,得出下面的结论。评测维度就盯开发者最关心的五项&#xf…

2026/10/18 9:16:27 阅读更多 →
ITextRenderer实战:Java服务端HTML转PDF工具类封装与避坑指南

ITextRenderer实战:Java服务端HTML转PDF工具类封装与避坑指南

简介:在不同业务系统中,经常需要把包含样式的网页文本转成 PDF,以便打印、存档或离线阅读。对于 Java 后端开发者,ITextRenderer 是一种常见选择;这份资源围绕它在 Java 环境下的使用,整理出一套可直接借鉴…

2026/10/18 9:16:27 阅读更多 →
AgentScope Java 11个Spring Boot Starter自动装配全解析

AgentScope Java 11个Spring Boot Starter自动装配全解析

1. 从手动装配到自动装配的思维转变做过Java企业级开发的人都有一个共识:依赖注入和组件管理是框架的核心价值。但当你把AgentScope这样的智能体框架引入Spring Boot项目时,第一反应往往是——我该怎么把AgentScope的那些核心组件注册成Bean?…

2026/10/18 9:16:27 阅读更多 →
AI界的GitHub:Hugging Face核心组件与实战应用指南

AI界的GitHub:Hugging Face核心组件与实战应用指南

Hugging Face,国内圈子更习惯叫它“抱抱脸”。这几年它的名字在人工智能领域出现的频率实在太高了,从新手教程到大厂技术分享,几乎绕不开这个开源平台。很多人第一次认识它,是因为要从上面下载模型权重,但接触久了会发…

2026/10/18 9:16:27 阅读更多 →
HTML转PDF工具类实战:ITextRenderer中文字体注册与CSS边界

HTML转PDF工具类实战:ITextRenderer中文字体注册与CSS边界

简介:面向 Java 开发者的 HTML 转 PDF 实用工具包,基于 ITextRenderer 封装了一个可直接调用的转换工具类,解决报告导出、打印留存、离线阅读等常见需求。资源共 23 个文件,压缩包大小 28.74MB,内含 10 个依赖 jar、2 …

2026/10/18 9:16:27 阅读更多 →
H5 Canvas 画布地图:不依赖 SDK 的 GeoJSON 编辑交互实现指南

H5 Canvas 画布地图:不依赖 SDK 的 GeoJSON 编辑交互实现指南

简介:面向需要构建交互式地图应用的开发者,这份rar压缩包整合了HTML5 Canvas与ECharts的地图可视化方案。核心思路是利用Canvas的fillRect、strokeRect、beginPath、moveTo、lineTo等API绘制地图基础轮廓,再借助ECharts地图系列加载GeoJSON数…

2026/10/18 9:15:26 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →