Files
1818web-hoduan/COS_POST_FORM_UPLOAD_FIXED.md
Claude Workbench e3e6f1f29d first commit
2026-02-13 18:18:20 +08:00

8.0 KiB
Raw Permalink Blame History

COS POST 表单上传 - 完整修复版

问题已修复

已修复 COS POST 表单上传的签名问题:

  • 添加了 q-key-time 字段
  • 添加了 q-sign-time 字段
  • Policy 中包含必需的签名条件
  • 使用正确的 COS 签名算法

📋 前端正确的上传代码

Vue 3 + Element Plus 完整示例

<template>
  <el-upload
    :action="uploadAction"
    :data="uploadData"
    :before-upload="beforeUpload"
    :on-success="handleSuccess"
    :on-error="handleError"
    accept="image/*"
  >
    <el-button type="primary">上传图片</el-button>
  </el-upload>
</template>

<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';

const uploadAction = ref('');
const uploadData = ref({});

// 上传前获取签名
const beforeUpload = async (file) => {
  try {
    // 1. 获取 POST 签名
    const response = await fetch('/user/oss/post-signature', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + localStorage.getItem('token')
      },
      body: JSON.stringify({
        fileName: file.name,
        userId: '123'  // 从登录状态获取
      })
    });

    const result = await response.json();
    if (result.code !== 200) {
      ElMessage.error(result.message);
      return false;
    }

    const data = result.data;
    
    // 2. 设置上传地址
    uploadAction.value = data.host;
    
    // 3. 设置表单数据COS 标准字段)
    uploadData.value = {
      key: data.dir + Date.now() + '_' + file.name,  // 文件路径
      policy: data.policy,                            // Policy
      'q-sign-algorithm': data['q-sign-algorithm'],   // 签名算法
      'q-ak': data['q-ak'],                           // SecretId
      'q-key-time': data['q-key-time'],               // KeyTime必需
      'q-signature': data['q-signature']              // 签名
    };
    
    console.log('上传配置:', uploadData.value);
    return true;
    
  } catch (error) {
    console.error('获取签名失败:', error);
    ElMessage.error('获取上传签名失败');
    return false;
  }
};

const handleSuccess = (response, file) => {
  const fileUrl = uploadAction.value + '/' + uploadData.value.key;
  ElMessage.success('上传成功');
  console.log('文件地址:', fileUrl);
};

const handleError = (error) => {
  console.error('上传失败:', error);
  ElMessage.error('上传失败');
};
</script>

原生 JavaScript 示例

async function uploadFileToCOS(file, userId) {
  try {
    // 1. 获取 POST 签名
    const signResponse = await fetch('/user/oss/post-signature', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + localStorage.getItem('token')
      },
      body: JSON.stringify({
        fileName: file.name,
        userId: userId
      })
    });

    const signResult = await signResponse.json();
    if (signResult.code !== 200) {
      throw new Error(signResult.message);
    }

    const data = signResult.data;
    
    // 2. 构造表单数据COS 标准字段)
    const formData = new FormData();
    const fileKey = data.dir + Date.now() + '_' + file.name;
    
    formData.append('key', fileKey);                          // 文件路径
    formData.append('policy', data.policy);                   // Policy
    formData.append('q-sign-algorithm', data['q-sign-algorithm']); // 签名算法
    formData.append('q-ak', data['q-ak']);                    // SecretId
    formData.append('q-key-time', data['q-key-time']);        // KeyTime必需
    formData.append('q-signature', data['q-signature']);      // 签名
    formData.append('file', file);                            // 文件(必须最后)

    // 3. 上传到 COS
    const uploadResponse = await fetch(data.host, {
      method: 'POST',
      body: formData
    });

    if (!uploadResponse.ok) {
      const errorText = await uploadResponse.text();
      console.error('COS 返回错误:', errorText);
      throw new Error('上传失败: ' + uploadResponse.status);
    }

    // 4. 上传成功
    const fileUrl = data.host + '/' + fileKey;
    console.log('上传成功,文件地址:', fileUrl);
    return fileUrl;

  } catch (error) {
    console.error('上传失败:', error);
    throw error;
  }
}

// 使用示例
document.getElementById('fileInput').addEventListener('change', async (e) => {
  const file = e.target.files[0];
  if (file) {
    try {
      const fileUrl = await uploadFileToCOS(file, '123');
      alert('上传成功: ' + fileUrl);
    } catch (error) {
      alert('上传失败: ' + error.message);
    }
  }
});

📡 后端返回的签名数据

{
  "code": 200,
  "message": "POST签名生成成功",
  "data": {
    "policy": "eyJleHBpcmF0aW9uIjoi...",
    "q-sign-algorithm": "sha1",
    "q-ak": "AKIDVY1HLBnDZhbHkz0mLhgT3TgePXHNErLC",
    "q-key-time": "1733472660;1733476260",
    "q-sign-time": "1733472660;1733476260",
    "q-signature": "7758dc9a832e9d301dca704cacbf9d9f8172abcd",
    "host": "https://oss-1818ai-user-img-1302947942.cos.ap-guangzhou.myqcloud.com",
    "dir": "user_img/",
    "fileName": "avatar.jpg",
    "fileType": "image",
    "maxFileSize": 10485760,
    "maxFileSizeMB": 10
  }
}

🔑 必需的表单字段

前端提交表单时必须包含以下字段:

字段名 说明 示例值
key 文件路径 user_img/1733472660_avatar.jpg
policy Base64 编码的 Policy eyJleHBpcmF0aW9uIjoi...
q-sign-algorithm 签名算法 sha1
q-ak SecretId AKIDVY1HLBnDZhbHkz0mLhgT3TgePXHNErLC
q-key-time 密钥有效时间 1733472660;1733476260
q-signature 签名 7758dc9a832e9d301dca704cacbf9d9f8172abcd
file 文件内容 (二进制数据,必须最后)

⚠️ 常见错误和解决方案

1. SignatureDoesNotMatch - q-key-time is required

错误信息:

<Error>
  <Code>SignatureDoesNotMatch</Code>
  <Message>form field q-key-time is required,but not found or empty.</Message>
</Error>

原因: 表单中缺少 q-key-time 字段

解决: 确保表单包含:

formData.append('q-key-time', data['q-key-time']);

2. InvalidPolicyDocument - q-sign-time is required

错误信息:

<Error>
  <Code>InvalidPolicyDocument</Code>
  <Message>policy condition q-sign-time is required,but not found.</Message>
</Error>

原因: Policy 中缺少 q-sign-time 条件

解决: 后端已修复,重新编译部署即可


3. SignatureDoesNotMatch - 签名不匹配

原因: 签名计算错误或字段值不匹配

解决:

  1. 确保 q-key-timeq-sign-time 的值相同
  2. 确保 key 字段以 dir 开头
  3. 确保所有字段值与后端返回的完全一致

4. CORS 错误

解决: 在腾讯云 COS 控制台配置 CORS

  • 来源:* 或具体域名
  • 方法:GET, POST, PUT, HEAD
  • Allow-Headers*

🧪 测试上传

使用 curl 测试

# 1. 获取签名
curl -X POST http://localhost:8083/user/oss/post-signature \
  -H "Content-Type: application/json" \
  -d '{"userId":"123","fileName":"test.jpg"}'

# 2. 使用返回的签名上传(替换实际值)
curl -X POST "https://oss-1818ai-user-img-1302947942.cos.ap-guangzhou.myqcloud.com/" \
  -F "key=user_img/test.jpg" \
  -F "policy=<返回的policy>" \
  -F "q-sign-algorithm=sha1" \
  -F "q-ak=<返回的q-ak>" \
  -F "q-key-time=<返回的q-key-time>" \
  -F "q-signature=<返回的q-signature>" \
  -F "file=@/path/to/test.jpg"

📚 参考文档


总结

修复后的签名已经完全符合 COS 规范:

  • 包含所有必需字段
  • 签名算法正确
  • Policy 格式正确
  • 前端代码简单明了

重新编译部署后即可正常使用!