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

前端通过Express签名URL直传Google Cloud Storage遇405错误求助

Google Cloud Storage 签名URL 前后端实现方案(解决405错误)

一、405错误核心排查点

  • 签名URL生成时指定的HTTP方法与前端上传方法不匹配
  • 存储桶CORS规则未配置允许对应请求方法、来源域名及响应头
  • Express服务器路由与前端请求方法不兼容(比如后端用GET,前端发POST)

二、后端(Express)实现

1. 安装依赖

npm install @google-cloud/storage cors express

2. 核心代码

const express = require('express');
const { Storage } = require('@google-cloud/storage');
const cors = require('cors');
const app = express();

// 配置CORS,替换为你的前端实际域名
app.use(cors({ origin: 'http://localhost:3000' }));
app.use(express.json());

// 初始化GCS客户端,需提前设置GOOGLE_APPLICATION_CREDENTIALS环境变量
const storage = new Storage();
const bucketName = '你的存储桶名称';

// 生成签名URL接口
app.post('/get-signed-url', async (req, res) => {
  try {
    const { fileName, contentType } = req.body;
    if (!fileName || !contentType) {
      return res.status(400).json({ error: '缺少fileName或contentType参数' });
    }

    // 生成PUT方法的签名URL,对应前端上传动作
    const options = {
      version: 'v4',
      action: 'write',
      expires: Date.now() + 15 * 60 * 1000, // 15分钟过期
      contentType: contentType, // 必须与前端上传的Content-Type一致
    };

    const [url] = await storage.bucket(bucketName).file(fileName).getSignedUrl(options);
    res.status(200).json({ signedUrl: url });
  } catch (err) {
    console.error('生成签名URL失败:', err);
    res.status(500).json({ error: '生成签名URL失败' });
  }
});

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
  console.log(`服务器运行在端口 ${PORT}`);
});

三、前端实现

1. HTML示例

<input type="file" id="videoInput" accept="video/*">
<button id="uploadBtn">上传视频</button>

2. 核心JS代码

const videoInput = document.getElementById('videoInput');
const uploadBtn = document.getElementById('uploadBtn');

uploadBtn.addEventListener('click', async () => {
  const file = videoInput.files[0];
  if (!file) {
    alert('请先选择视频文件');
    return;
  }

  try {
    // 请求后端获取签名URL
    const signResponse = await fetch('http://localhost:5000/get-signed-url', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        fileName: `videos/${Date.now()}-${file.name}`, // 自定义存储路径
        contentType: file.type,
      }),
    });

    const { signedUrl } = await signResponse.json();
    if (!signedUrl) throw new Error('获取签名URL失败');

    // 使用签名URL上传文件,必须用PUT方法
    const uploadResponse = await fetch(signedUrl, {
      method: 'PUT',
      headers: { 'Content-Type': file.type },
      body: file,
    });

    uploadResponse.ok ? alert('上传成功') : throw new Error('上传失败');
  } catch (err) {
    console.error('上传出错:', err);
    alert('上传失败,请重试');
  }
});

四、存储桶CORS配置(解决405的关键)

1. 创建cors.json配置文件

[
  {
    "origin": ["http://localhost:3000"], // 替换为你的前端域名
    "method": ["PUT"], // 允许PUT方法,与签名URL的action对应
    "responseHeader": ["Content-Type"],
    "maxAgeSeconds": 3600
  }
]

2. 应用配置到存储桶

gsutil cors set cors.json gs://你的存储桶名称

五、关键注意事项

  • 签名URL的action与HTTP方法对应:write对应PUT,read对应GET,必须和前端请求方法完全一致
  • 前端上传的Content-Type必须与后端生成签名URL时指定的contentType完全匹配,否则会触发签名验证失败
  • 不要给存储桶设置allUsers写权限,签名URL的作用就是无需公开权限即可安全上传
  • 确保GCS服务账号拥有storage.objects.create权限(默认存储桶管理员角色已包含)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 10:26:09