如何在浏览器端通过JavaScript或JQuery实现Amazon S3分片上传
浏览器端JavaScript实现Amazon S3分片上传实现方案
核心实现思路
你的初步判断是完全可行的,补充一些关键细节如下:
- 步骤1:后端预生成分片上传相关凭证
S3分片上传流程全程不能在前端暴露AWS的AK/SK,所以所有和S3鉴权相关的操作都要走后端封装:首先后端发起S3分片初始化请求拿到uploadId,再为每个分片生成对应的预签名PUT URL,最后还要生成合并分片的预签名请求地址,统一返回给前端。 - 步骤2:前端文件切片+受控并发上传
用浏览器原生File.sliceAPI切割文件,分片大小建议设置为5MB以上(S3强制要求除最后一个分片外,其余分片最小为5MB),并发上传数控制在3~5即可,避免触发浏览器同域请求上限或者S3限流。每个分片上传成功后需要记录响应头返回的ETag值,合并分片时必须用到。 - 步骤3:合并分片完成上传
所有分片上传成功后,把按序号排序的ETag列表和uploadId传给后端,或者直接调用提前生成的合并预签名地址,通知S3将所有分片合并为完整文件。
完整前端实现示例
以下代码为原生JS实现,无需依赖jQuery,兼容所有主流现代浏览器:
<!-- 页面结构 --> <input type="file" id="fileInput" accept="*"> <button id="uploadBtn">开始上传</button> <div id="uploadProgress"></div>
// 逻辑代码 const fileInput = document.getElementById('fileInput') const uploadBtn = document.getElementById('uploadBtn') const progressDom = document.getElementById('uploadProgress') // 可自行调整的配置项 const CHUNK_SIZE = 10 * 1024 * 1024 // 单分片10MB,最低不要低于5MB const CONCURRENT_LIMIT = 3 // 并发上传上限 uploadBtn.addEventListener('click', async () => { const file = fileInput.files[0] if (!file) { alert('请先选择上传文件') return } const totalChunks = Math.ceil(file.size / CHUNK_SIZE) progressDom.innerText = `准备上传,总分片数:${totalChunks}` try { // 1. 请求后端获取分片上传初始化信息 const initRes = await fetch('/api/s3/init-multipart-upload', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName: file.name, fileType: file.type, totalChunks: totalChunks }) }).then(res => res.json()) const { uploadId, chunkUrls, completeUrl } = initRes const uploadedChunks = [] let uploadedCount = 0 // 简单并发控制器 const asyncPool = async (limit, taskList, handler) => { const result = [] const executing = [] for (const task of taskList) { const p = Promise.resolve().then(() => handler(task)) result.push(p) if (limit <= taskList.length) { const e = p.then(() => executing.splice(executing.indexOf(e), 1)) executing.push(e) if (executing.length >= limit) { await Promise.race(executing) } } } return Promise.all(result) } // 2. 并发上传所有分片 await asyncPool(CONCURRENT_LIMIT, Array.from({length: totalChunks}), async (_, index) => { const start = index * CHUNK_SIZE const end = Math.min(start + CHUNK_SIZE, file.size) const chunk = file.slice(start, end) // 用预签名地址上传分片 const res = await fetch(chunkUrls[index], { method: 'PUT', body: chunk }) const etag = res.headers.get('ETag') uploadedChunks.push({ PartNumber: index + 1, ETag: etag }) uploadedCount++ progressDom.innerText = `上传进度:${Math.round(uploadedCount / totalChunks * 100)}%` }) // 3. 请求合并分片 uploadedChunks.sort((a, b) => a.PartNumber - b.PartNumber) await fetch(completeUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ uploadId: uploadId, parts: uploadedChunks }) }) progressDom.innerText = '文件上传完成' } catch (err) { progressDom.innerText = `上传失败:${err.message}` console.error('上传错误详情:', err) } })
注意事项
- 如果需要实现断点续传,可以在初始化上传的时候先调用后端接口拉取已经上传成功的分片列表,前端跳过已上传的分片即可
- 跨域问题需要提前在S3的CORS配置里开放PUT、POST请求权限,以及ETag响应头的读取权限
- 分片上传失败可以重试单个分片,不需要重新上传整个文件
内容的提问来源于stack exchange,提问作者winhow
相关产品推荐
相关产品推荐

