问题背景
在我们的项目中,原本采用的文件上传方案是将文件先上传到应用服务器,再由服务器转发至华为云OBS。这种架构在实际运行中暴露了两个关键问题:
- 上传速度严重受限:服务器的带宽成为瓶颈(特别是100MB以上的大文件)
- 服务器压力过大:频繁出现负载过载告警
为解决这些痛点,我们决定改为前端直传OBS方案。技术流程如下:
sequenceDiagram
    participant 前端
    participant 后端
    participant 华为云OBS
    前端->>后端: 1. 初始化上传(initUploadUrl)
    后端-->>前端: uploadId, objectKey
    前端->>后端: 2. 获取临时URL(getTmpUrl)
    后端-->>前端: signedUrl
    前端->>华为云OBS: 3. 直接上传(PUT文件)
    华为云OBS-->>前端: 上传成功
    前端->>后端: 4. 合并分片(mergeChunk)
    后端-->>前端: objectUrl
问题一:跨域策略配置难题
问题描述
在调试过程中,前端控制台频繁出现CORS跨域错误,尤其在以下两种场景:
- 本地开发时:浏览器提示Access-Control-Allow-Origin缺失
- 生产环境上传时:OBS返回403 Forbidden并附带CORS拒绝信息
问题定位
通过分析浏览器网络请求和华为云CORS配置文档(参见官方文档)发现:
- OBS桶默认拒绝所有跨域请求
- 上传请求中包含Content-MD5头,被安全策略拦截
- Content-MD5被视为敏感头,需要显式配置
- Nginx反向代理未正确透传必要头信息
解决方案
本地开发配置(vue.config.js)::
// vue.config.js
module.exports = {
  devServer: {
    proxy: {
      '/base-api': {
        target: 'https://my-obs-endpoint',
        changeOrigin: true,
        pathRewrite: { '^/base-api': '' }
      }
    }
  }
}
生产环境解决方案:
- OBS桶CORS配置:
[
  {
    "AllowedOrigin": ["https://your-domain.com"],
    "AllowedMethod": ["PUT", "GET"],
    "AllowedHeader": ["*"], // 华为云要求必须配置为*
    "ExposeHeader": ["ETag", "x-obs-request-id"],
    "MaxAgeSeconds": 300
  }
]
- Nginx关键配置:
location / {
  add_header 'Access-Control-Allow-Origin' '*';
  add_header 'Access-Control-Allow-Headers' 
             'Content-MD5, Content-Type, Authorization';
}
扩展知识点
- CORS预检机制:浏览器对非简单请求会先发OPTIONS请求验证权限
- 华为云特殊要求:必须配置AllowedHeader: *才能放行Content-MD5等自定义头
- 暴露头信息:需显式配置ETag,否则无法获取文件校验信息
- 调试技巧:使用curl -v -X OPTIONS https://obs-url预检请求验证配置
问题二:Content-MD5校验异常
问题描述
上传时报错:
403 Forbidden - InvalidDigest: The Content-MD5 you specified was invalid
即使MD5值看似正确,OBS仍拒绝请求。
问题定位
经过测试对比发现:
- 比对华为云文档要求:
- 要求格式:Content-MD5: base64(md5Bytes)
- 错误认知:直接传递hex格式md5会导致校验失败
 
- 要求格式:
- 代码排查发现:
- 原有crypto库生成的是hex字符串
- OBS要求Base64编码的字节数组,而非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
});
扩展知识点
- MD5的三种表达形式:
格式 示例 适用场景 HEX e4b0c44298fc1c14...通用校验 Base64 5L2g5aW977yM5Lit...HTTP内容校验 ByteArray [228, 176, 196...]二进制协议 
问题三:历史数据导致的404错误
问题描述
测试发现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% ↓ | 
关键技术总结
- 
直传架构优势: - 省去服务器中转环节,降低系统复杂性
- 直接利用华为云BGP网络提升传输质量
- 支持客户端断点续传(Range PUT)
 
- 
安全关键点: - 临时URL有效期控制在10分钟内
- 敏感操作使用HTTPS+内容校验双重保障
- ACL权限最小化原则(只赋予PutObject权限)
 
- 
扩展能力: graph LR 核心功能-->断点续传 核心功能-->分片上传 核心功能-->秒传校验 进阶能力-->上传进度可视化 进阶能力-->自动重试策略 进阶能力-->弱网自适应
该方案上线后,不仅大幅提升了上传性能,还降低了服务器负载。后续可在此基础上实现分片上传和秒传功能,进一步完善大文件传输体验。
经验提示:云存储服务各厂商实现细节差异较大,建议重点阅读华为云OBS RESTful API文档,特别关注请求头要求和错误码规范(ps: 大厂文档写的一言难尽,反复看反复测试)。