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

如何通过JavaScript使用预签名URL向S3私有存储桶上传音频文件?

问题排查与解决方案

核心错误分析

  • Promise未解析:obtainURL()是异步函数,直接赋值给endpoint得到的是Promise对象而非实际URL,导致请求错误路径/[object%20Promise],触发501 Unsupported method ('PUT')。
  • PUT请求体格式错误:预签名PUT请求不需要用FormData包装,FormData会自动添加边界头,与预签名签名规则不匹配,引发网络错误。
  • 无效请求头:formData.length为undefined,手动设置Content-Length会导致请求头无效。
  • 密钥泄露风险:前端硬编码AWS密钥是严重安全问题,任何人可通过浏览器源码获取并滥用你的AWS资源。

修复步骤

1. 正确解析异步URL

将表单提交事件改为异步函数,用await获取预签名URL,避免直接使用Promise对象。

2. 调整PUT请求体

直接上传文件原始内容,无需FormData包装,保证请求体与预签名规则一致。

3. 移除错误请求头

删除手动设置的Content-Length,由浏览器自动计算;Content-Type使用文件自身的MIME类型,确保与预签名参数匹配。

4. 替换前端密钥方案

前端禁止直接存储AWS密钥,改用后端API生成预签名URL:后端通过IAM角色/环境变量安全配置AWS SDK,提供接口给前端获取上传地址。

修正后的代码

index.js 前端代码

const BUCKET_NAME = 'XYZ';

// 后端API接口,用于安全获取预签名PUT URL
const obtainURL = async (fileName, contentType) => {
    try {
        const response = await fetch('/api/get-signed-url', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                bucket: BUCKET_NAME,
                key: fileName,
                contentType
            })
        });
        const data = await response.json();
        return data.signedUrl;
    } catch (err) {
        console.error('获取上传地址失败:', err);
    }
};

const myForm = document.getElementById("myForm");
const inputFile = document.getElementById("inputFile");

// 异步处理表单提交
myForm.addEventListener("submit", async e => {
    e.preventDefault();

    const file = inputFile.files[0];
    if (!file) {
        alert('请选择音频文件');
        return;
    }

    const endpoint = await obtainURL(file.name, file.type);
    if (!endpoint) {
        alert('获取上传地址失败');
        return;
    }

    try {
        const response = await fetch(endpoint, {
            method: "PUT",
            body: file,
            headers: { "Content-Type": file.type }
        });

        response.ok ? alert('上传成功') : alert(`上传失败: ${await response.text()}`);
    } catch (error) {
        console.error('网络错误:', error);
        alert('上传失败,网络异常');
    }
});

后端示例(Node.js/Express)

const AWS = require('aws-sdk');
const express = require('express');
const app = express();

// 后端通过IAM角色或环境变量配置AWS SDK(禁止硬编码密钥)
const s3 = new AWS.S3({ 
    apiVersion: '2006-03-01', 
    signatureVersion: 'v4' 
});

app.use(express.json());

// 生成预签名URL的接口
app.post('/api/get-signed-url', (req, res) => {
    const { bucket, key, contentType } = req.body;
    const params = {
        Bucket: bucket,
        Key: key,
        Expires: 5 * 60,
        ContentType: contentType
    };

    s3.getSignedUrl('putObject', params, (err, url) => {
        if (err) {
            console.error('生成预签名URL失败:', err);
            return res.status(500).json({ error: '生成上传地址失败' });
        }
        res.json({ signedUrl: url });
    });
});

app.listen(3000, () => console.log('后端服务运行在端口3000'));

额外配置要求

  • CloudFront设置:确保分发允许PUT方法,Origin Access Control (OAC) 配置正确,允许CloudFront访问私有S3存储桶。
  • S3 CORS规则:存储桶需配置允许PUT请求的CORS策略:
<CORSConfiguration>
    <CORSRule>
        <AllowedOrigin>你的前端域名</AllowedOrigin>
        <AllowedMethod>PUT</AllowedMethod>
        <AllowedHeader>*</AllowedHeader>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
    </CORSRule>
</CORSConfiguration>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 05:35:18