在我们的项目中,原本采用的文件上传方案是将文件先上传到应用服务器,再由服务器转发至华为云OBS。这种架构在实际运行中暴露了两个关键问题:
为解决这些痛点,我们决定改为前端直传OBS方案。技术流程如下:
在调试过程中,前端控制台频繁出现CORS跨域错误,尤其在以下两种场景:
Access-Control-Allow-Origin
缺失403 Forbidden
并附带CORS拒绝信息通过分析浏览器网络请求和华为云CORS配置文档(参见官方文档)发现:
Content-MD5
头,被安全策略拦截本地开发配置(vue.config.js)::
// vue.config.js
module.exports = {
devServer: {
proxy: {
'/base-api': {
target: 'https://my-obs-endpoint',
changeOrigin: true,
pathRewrite: { '^/base-api': '' }
}
}
}
}
生产环境解决方案:
[
{
"AllowedOrigin": ["https://your-domain.com"],
"AllowedMethod": ["PUT", "GET"],
"AllowedHeader": ["*"], // 华为云要求必须配置为*
"ExposeHeader": ["ETag", "x-obs-request-id"],
"MaxAgeSeconds": 300
}
]
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Headers'
'Content-MD5, Content-Type, Authorization';
}
AllowedHeader: *
才能放行Content-MD5等自定义头curl -v -X OPTIONS https://obs-url
预检请求验证配置上传时报错:
403 Forbidden - InvalidDigest: The Content-MD5 you specified was invalid
即使MD5值看似正确,OBS仍拒绝请求。
经过测试对比发现:
Content-MD5: base64(md5Bytes)
crypto
库生成的是hex字符串优化后的MD5计算方案:
import SparkMD5 from 'spark-md5';
async function calculateObsMD5(file) {
const spark = new SparkMD5.ArrayBuffer();
const chunkSize = 5 * 1024 * 1024; // 5MB分片
let offset = 0;
while (offset < file.size) {
const chunk = file.slice(offset, offset + chunkSize);
const buffer = await chunk.arrayBuffer();
spark.append(buffer);
offset += chunkSize;
}
// 关键转换:十六进制 → 字节数组 → Base64
const hexHash = spark.end();
const rawHash = new Uint8Array(32);
for (let i = 0; i < 32; i += 2) {
rawHash[i/2] = parseInt(hexHash.substr(i, 2), 16);
}
// 浏览器安全转换Base64
return btoa(String.fromCharCode(...rawHash));
}
// 上传请求使用示例
const md5 = await calculateObsMD5(file);
fetch(signedUrl, {
method: 'PUT',
headers: {
'Content-Type': contentType,
'Content-MD5': md5 // 形如:MD5:4XvB3tbNTN+tIEVa0/fGaQ==
},
body: file
});
格式 | 示例 | 适用场景 |
---|---|---|
HEX | e4b0c44298fc1c14... |
通用校验 |
Base64 | 5L2g5aW977yM5Lit... |
HTTP内容校验 |
ByteArray | [228, 176, 196...] |
二进制协议 |
测试发现bug:上传文件时偶发OBS返回404 Not Found
错误,但相同文件在测试环境可正常上传。
通过日志比对和版本分析发现:
{version1}/{userid}/{filename}
{version2}/{date}/{uuid}.ext
解决方案:
# 使用obsutil迁移历史数据
obsutil cp obs://old-bucket/projectA/ obs://new-bucket/tenant01/projectA/ -r
指标 | 旧方案(服务器中转) | 新方案(直传OBS) | 提升幅度 |
---|---|---|---|
100MB文件上传 | 12.8s | 3.2s | 300% ↑ |
服务器CPU负载 | 峰值85% | 稳定在30% | 65% ↓ |
失败率(网络波动) | 14.2% | 2.1% | 85% ↓ |
直传架构优势:
安全关键点:
扩展能力:
该方案上线后,不仅大幅提升了上传性能,还降低了服务器负载。后续可在此基础上实现分片上传和秒传功能,进一步完善大文件传输体验。
经验提示:云存储服务各厂商实现细节差异较大,建议重点阅读华为云OBS RESTful API文档,特别关注请求头要求和错误码规范(ps: 大厂文档写的一言难尽,反复看反复测试)。