AWS S3分片上传:单分片预签名URL上传报NoSuchUpload错误求助
解决AWS S3分片上传单个预签名URL报错问题
问题背景
已实现批量生成预签名URL的S3分片上传流程,现需调整为每个分片单独生成预签名URL再上传,调整后触发NoSuchUpload(404)错误,提示"指定的上传不存在,上传ID可能无效或已中止/完成"。
错误原因分析
- 上传ID传递错误:前端代码中错误地使用
uploadTempId.UploadId传递上传ID,而原正常流程中uploadTempId本身就是创建multipart时返回的上传ID字符串,多嵌套一层导致ID无效。 - 全局修改axios配置:前端代码中
delete axios.defaults.headers.put["Content-Type"]会全局修改请求头,可能影响其他请求的正确性。 - 缺少参数校验:后端未校验请求参数的完整性,空值可能生成无效的预签名URL。
修正后的实现代码
后端单个分片预签名URL生成接口
export const getSinglePartUploadUrl: RequestHandler< {}, {}, {}, { fileName: string partNumber: number uploadId: string } > = async (req, res, next) => { const { fileName, partNumber, uploadId } = req.query // 校验必要参数,避免空值导致无效请求 if (!fileName || !partNumber || !uploadId) { return res.status(400).json({ error: "缺少fileName、partNumber或uploadId参数" }) } const signedUrl = await s3.getSignedUrlPromise("uploadPart", { Bucket: BUCKET_NAME, Key: fileName, UploadId: uploadId, PartNumber: +partNumber, Expires: 3000, // 设置为50分钟,足够单个分片上传完成 }) return res.status(200).json({ signedUrl: signedUrl, partNumber: +partNumber }) }
前端分片上传逻辑
try { // 1. 创建multipart upload ID const uploadIdRes = await axios.post(`${url}/getUploadId`, { fileName: uniqueFileName }) const uploadId = uploadIdRes.data.uploadId setUploadId(uploadId) // 计算分片信息 const chunkSize = uploadStatus.chunkSize * 1024 * 1024 const chunkCount = Math.floor(fileSize / chunkSize) + 1 // 更新上传状态 setIsUploading({ hasCompleted: false, isUploading: true }) setUploadStatus(prev => ({ ...prev, partsUploaded: 0, totalParts: chunkCount, })) const multiUploadArray = [] const concurrency = 3 // 控制并发上传数量,避免请求过载 let currentIndex = 0 // 单个分片上传逻辑 const uploadChunk = async () => { while (currentIndex < chunkCount) { const uploadCount = currentIndex + 1 currentIndex++ // 切割文件分片 const start = (uploadCount - 1) * chunkSize const end = uploadCount * chunkSize const fileBlob = uploadCount < chunkCount ? file.slice(start, end) : file.slice(start) // 获取当前分片的预签名URL const signedUrlRes = await axios.get(`${url}/getPartUploadUrl`, { params: { fileName: uniqueFileName, partNumber: uploadCount, uploadId: uploadId // 传递正确的上传ID } }) const preSignedUrl = signedUrlRes.data.signedUrl // 上传分片,局部设置请求头,不修改全局配置 const uploadRes = await axios.put(preSignedUrl, fileBlob, { headers: { "Content-Type": file.type } // 显式设置文件类型,匹配S3要求 }) // 收集分片ETag,用于后续合并 const etag = uploadRes.headers["etag"] multiUploadArray.push({ ETag: etag, PartNumber: uploadCount }) // 更新上传进度 setUploadStatus(prev => ({ ...prev, partsUploaded: prev.partsUploaded + 1, })) } } // 启动并发上传 const uploadPromises = Array(concurrency).fill(null).map(() => uploadChunk()) await Promise.all(uploadPromises) // 2. 所有分片上传完成后,调用合并接口 await axios.post(`${url}/completeMultipartUpload`, { fileName: uniqueFileName, uploadId: uploadId, parts: multiUploadArray }) setIsUploading({ hasCompleted: true, isUploading: false }) } catch (error) { console.error("上传失败:", error) // 上传失败时清理S3分片,避免资源浪费 await axios.post(`${url}/abortMultipartUpload`, { fileName: uniqueFileName, uploadId: uploadId }) }
关键注意事项
- 上传ID准确性:确保传递给后端的
uploadId是创建multipart时返回的完整字符串,不要额外嵌套层级。 - 请求头控制:上传分片时显式设置
Content-Type,与创建multipart时的文件类型保持一致,避免S3拒绝请求。 - 并发控制:通过限制并发上传数量(如3-5个)平衡上传效率和S3请求限制。
- 错误清理:上传失败时调用
abortMultipartUpload接口,清理S3上的未合并分片,避免占用存储资源。
内容的提问来源于stack exchange,提问作者Ravi Shankar
相关产品推荐
相关产品推荐

