阿里云OSS实战:上传优化与性能调优经验分享
又踩坑了,OSS上传文件跨域报错
上周在给一个项目加图片上传功能,前端直接用阿里云OSS的Web直传,结果一上传就报CORS错误。控制台红了一片,Access to XMLHttpRequest at 'https://xxx.oss-cn-hangzhou.aliyuncs.com' from origin 'http://localhost:3000' has been blocked by CORS policy。我一开始以为是后端没配好,结果折腾了半天发现根本不是那么回事。
先说结论:OSS的CORS配置要手动开,而且不能只靠后端
我第一反应是让后端同事检查签名接口,是不是返回的policy或者callback有问题。他查了下说没问题,签名逻辑跑通了,测试脚本也能上传。那问题肯定出在前端请求上。我就开始翻阿里云文档,越看越迷糊——文档里一堆“服务端签名”“临时凭证”“STS”,但没一句说清楚CORS怎么配。
后来试了下发现,阿里云OSS默认是关闭CORS的!也就是说,哪怕你前端代码写得再漂亮,只要Bucket没显式开启CORS规则,浏览器直接拦掉请求,压根不会发到OSS。这和我以前用七牛云不一样,七牛新建空间默认就开了CORS,阿里云得自己手动配。
折腾半天才找到正确的CORS配置姿势
登录阿里云控制台,进OSS管理,找到对应Bucket,点“权限管理” → “跨域设置” → “创建规则”。这里有几个坑:
- 来源(Allowed Origins)不能只写
*,虽然文档说支持通配符,但实测必须写具体的域名,比如http://localhost:3000、https://your-app.com。我一开始偷懒写了*,结果还是报错。 - 允许Methods必须包含
PUT和POST,因为直传用的是PUT方法(或者POST,看你用哪种方式)。 - 允许Headers建议写
*,或者至少包含x-oss-date、x-oss-signature、Content-Type这几个。我漏了Content-Type,导致预检请求失败。 - 暴露Headers(Expose Headers)可以留空,除非你要读取OSS返回的ETag之类的。
配完等了几分钟(据说有缓存),刷新页面,CORS错误终于没了。但新的问题来了:上传进度卡在100%,但文件没传上去,也没报错。
核心代码就这几行,但细节决定成败
我用的是最简单的Web直传方案:后端生成policy和signature,前端用XMLHttpRequest直接PUT到OSS。关键代码如下:
function uploadToOSS(file, { host, policy, signature, key }) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('PUT', ${host}/${key}, true);
// 必须设置这些header,否则OSS会拒绝
xhr.setRequestHeader('Content-Type', file.type || 'application/octet-stream');
xhr.setRequestHeader('x-oss-date', new Date().toUTCString());
xhr.setRequestHeader('x-oss-signature', signature);
xhr.setRequestHeader('x-oss-policy', policy);
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
console.log(上传进度: ${percent.toFixed(2)}%);
}
};
xhr.onload = () => {
if (xhr.status === 200) {
resolve(${host}/${key});
} else {
reject(new Error(上传失败: ${xhr.status}));
}
};
xhr.onerror = () => reject(new Error('网络错误'));
xhr.send(file);
});
}
这里注意几个细节:
host必须是完整的OSS外网Endpoint,比如https://my-bucket.oss-cn-hangzhou.aliyuncs.com,不能漏掉协议头。key是文件在OSS里的路径,比如uploads/2024/05/avatar.jpg,要确保后端生成policy时允许这个前缀。- 所有自定义Header(
x-oss-xxx)必须小写!虽然HTTP规范说header名不区分大小写,但某些浏览器或代理可能有问题,统一小写最安全。
我之前卡在100%就是因为没设 Content-Type,OSS默认当成binary/octet-stream处理,但我的policy里限制了只能上传image/*,所以被拒绝了。加上这一行就好了。
后端签名那块也别掉以轻心
虽然问题主要在前端,但后端生成的policy如果太严格也会导致上传失败。比如我同事一开始写的policy只允许特定文件名,结果我前端动态生成的key不符合规则,OSS直接403。正确的policy应该类似这样(Node.js示例):
const policy = {
expiration: new Date(Date.now() + 30 * 60 * 1000).toISOString(), // 30分钟后过期
conditions: [
['content-length-range', 0, 10 * 1024 * 1024], // 限制10MB以内
['starts-with', '$key', 'uploads/'], // 只允许上传到uploads目录
['starts-with', '$Content-Type', 'image/'] // 只允许图片
]
};
const base64Policy = Buffer.from(JSON.stringify(policy)).toString('base64');
const signature = crypto.createHmac('sha1', OSS_ACCESS_KEY_SECRET)
.update(base64Policy)
.digest('base64');
注意 conditions 里的 starts-with 是个好东西,既能限制目录,又能防止用户乱传文件类型。但如果你前端key是随机字符串(比如UUID),就得改成 ['starts-with', '$key', ''] 放宽限制。
还有一个小问题:本地开发时localhost的端口经常变
每次换端口都得去阿里云改CORS配置?太麻烦了。我的临时方案是:在CORS规则里多加几条,比如 http://localhost:3000、http://localhost:8080、http://127.0.0.1:5173。虽然不优雅,但省事。生产环境当然只留正式域名。
另外,有些团队会用内网穿透工具(比如ngrok)把本地服务映射到公网,这样就可以用固定域名调试,顺便避开CORS问题——不过这是另一个话题了。
总结一下踩过的坑
- OSS默认关闭CORS,必须手动配置,且来源不能只写
* - 前端请求必须带齐
Content-Type和所有x-oss-xxxheader - 后端policy的
conditions要和前端key、Content-Type匹配 - 本地开发时提前把常用端口加到CORS规则里
其实整个流程跑通后发现,OSS Web直传比想象中简单,核心就三步:后端签policy、前端带签名发PUT、OSS验证通过后存文件。但中间任何一个环节配错,都会导致静默失败(比如进度100%但没文件),排查起来特别耗时间。
以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。比如现在也有用STS临时凭证的方式,安全性更高,不过对小项目来说可能有点重。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

暂无评论