You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AWS S3分片上传:单分片预签名URL上传报NoSuchUpload错误求助

解决AWS S3分片上传单个预签名URL报错问题

问题背景

已实现批量生成预签名URL的S3分片上传流程,现需调整为每个分片单独生成预签名URL再上传,调整后触发NoSuchUpload(404)错误,提示"指定的上传不存在,上传ID可能无效或已中止/完成"。

错误原因分析

  1. 上传ID传递错误:前端代码中错误地使用uploadTempId.UploadId传递上传ID,而原正常流程中uploadTempId本身就是创建multipart时返回的上传ID字符串,多嵌套一层导致ID无效。
  2. 全局修改axios配置:前端代码中delete axios.defaults.headers.put["Content-Type"]会全局修改请求头,可能影响其他请求的正确性。
  3. 缺少参数校验:后端未校验请求参数的完整性,空值可能生成无效的预签名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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 10:18:26