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

使用createPresignedPost上传MP3报400错误,求排查方案

解决S3预签名POST上传MP3返回400错误的问题

问题根源分析

  1. Content-Type条件配置错误
    预签名生成时的Conditions里错误限制了$Content-Type为multipart/form-data,但实际上传的MP3文件对应的Content-Type应为audio/mpeg,条件不匹配直接触发400错误。

  2. 手动设置请求Content-Type导致格式损坏
    前端上传时手动指定'Content-Type': 'multipart/form-data',但FormData会自动生成包含boundary的正确请求头,手动设置会丢失该关键参数,导致S3无法解析表单数据。

  3. 冗余字段配置冲突
    预签名代码中Fields: { file: '' }属于多余配置,createPresignedPost会自动处理文件字段的签名逻辑,手动添加反而可能引发参数冲突。


修正后的代码

预签名生成代码

import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
import { S3Client } from '@aws-sdk/client-s3'
import { v4 as uuid } from 'uuid'

import { AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY } from '$env/static/private'

const s3 = new S3Client({ 
  region: 'us-east-2',
  credentials: {
    accessKeyId: AWS_ACCESS_KEY_ID,
    secretAccessKey: AWS_SECRET_ACCESS_KEY
  } 
})

const BUCKET = 'thundermp3-songs'

export const GET = async ({ request, url }) => {
  try {
    const key = uuid() + '.mp3'
    const res = await createPresignedPost(s3, {
      Bucket: BUCKET,
      Conditions: [
        ['content-length-range', 0, 1048576 * 16], // 最大16MB
        ['starts-with', '$Content-Type', 'audio/mpeg'] // 匹配MP3正确的Content-Type
      ],
      Expires: 60 * 60 * 24 * 7, // 7天有效期
      Key: key
    })
    const { url: presignedUrl, fields } = res
    return new Response(JSON.stringify({ fields, key, presignedUrl }), { status: 201 })
  } catch(e) {
    return new Response(JSON.stringify({ message: `Error: ${e}` }), { status: 500 })
  }
}

前端上传代码

const handleUpload = async () => {
  if (files.accepted.length === 0) return
  uploading = true
  try {
    const presignedRes = await (await fetch('/api/presign')).json()
    
    const file = files.accepted[0]
    const formData = new FormData()
    // 添加预签名返回的所有签名字段
    Object.entries(presignedRes.fields).forEach(([key, value]) => {
      formData.set(key, value)
    })
    // 添加待上传的MP3文件
    formData.set('file', file)
    
    // 移除手动设置的Content-Type,让浏览器自动生成带boundary的请求头
    const res = await fetch(presignedRes.presignedUrl, {
      method: 'POST',
      body: formData
    })

    // S3成功上传返回204 No Content,不要尝试解析JSON
    if (res.ok) {
      console.log('上传成功')
    } else {
      // 读取响应文本获取S3详细错误信息
      const errorText = await res.text()
      console.error('上传失败:', errorText)
    }
  } catch(e) {
    console.error('上传错误:', e)
  } finally {
    uploading = false
    handleClear()
  }
}

额外排查建议

  • 检查S3存储桶的CORS配置,确保允许POST请求、对应的Origin,以及Content-Type等必要请求头
  • 确认生成预签名的IAM用户拥有s3:PutObject权限
  • 上传前验证文件大小是否在16MB限制内,避免触发content-length-range条件校验失败

内容的提问来源于stack exchange,提问作者Joel Hager

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 09:02:51