first commit
This commit is contained in:
312
COS_POST_FORM_UPLOAD_FIXED.md
Normal file
312
COS_POST_FORM_UPLOAD_FIXED.md
Normal file
@@ -0,0 +1,312 @@
|
||||
# COS POST 表单上传 - 完整修复版
|
||||
|
||||
## ✅ 问题已修复
|
||||
|
||||
已修复 COS POST 表单上传的签名问题:
|
||||
- ✅ 添加了 `q-key-time` 字段
|
||||
- ✅ 添加了 `q-sign-time` 字段
|
||||
- ✅ Policy 中包含必需的签名条件
|
||||
- ✅ 使用正确的 COS 签名算法
|
||||
|
||||
---
|
||||
|
||||
## 📋 前端正确的上传代码
|
||||
|
||||
### Vue 3 + Element Plus 完整示例
|
||||
|
||||
```vue
|
||||
<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 示例
|
||||
|
||||
```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);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📡 后端返回的签名数据
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
**错误信息:**
|
||||
```xml
|
||||
<Error>
|
||||
<Code>SignatureDoesNotMatch</Code>
|
||||
<Message>form field q-key-time is required,but not found or empty.</Message>
|
||||
</Error>
|
||||
```
|
||||
|
||||
**原因:** 表单中缺少 `q-key-time` 字段
|
||||
|
||||
**解决:** 确保表单包含:
|
||||
```javascript
|
||||
formData.append('q-key-time', data['q-key-time']);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. InvalidPolicyDocument - q-sign-time is required
|
||||
|
||||
**错误信息:**
|
||||
```xml
|
||||
<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-time` 和 `q-sign-time` 的值相同
|
||||
2. 确保 `key` 字段以 `dir` 开头
|
||||
3. 确保所有字段值与后端返回的完全一致
|
||||
|
||||
---
|
||||
|
||||
### 4. CORS 错误
|
||||
|
||||
**解决:** 在腾讯云 COS 控制台配置 CORS:
|
||||
- 来源:`*` 或具体域名
|
||||
- 方法:`GET, POST, PUT, HEAD`
|
||||
- Allow-Headers:`*`
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试上传
|
||||
|
||||
### 使用 curl 测试
|
||||
|
||||
```bash
|
||||
# 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 POST Object 官方文档](https://cloud.tencent.com/document/product/436/14690)
|
||||
- [COS 请求签名算法](https://cloud.tencent.com/document/product/436/7778)
|
||||
|
||||
---
|
||||
|
||||
## ✅ 总结
|
||||
|
||||
修复后的签名已经完全符合 COS 规范:
|
||||
- ✅ 包含所有必需字段
|
||||
- ✅ 签名算法正确
|
||||
- ✅ Policy 格式正确
|
||||
- ✅ 前端代码简单明了
|
||||
|
||||
**重新编译部署后即可正常使用!**
|
||||
Reference in New Issue
Block a user