1.1 为什么使用客户端直传?
前端直接将文件上传至oss,不经过网站服务器中转,可以减轻服务器压力,提高文件上传的速度。
实际使用:原先使用5M带宽的阿里云服务器,上传8M图片(高拍仪拍摄)需要花费近20s,非常慢,改用阿里云oss直传后,速度为6s左右,提升很大。
1.2 表单上传方式
流程:
- 客户端先请求获取签名和密钥(AccessKey)
- 再使用form-data方式上传文件
限制:上传文件大小不能超过5GB(超过考虑使用分片上传)
优点:不依赖OSS SDK
缺点:
- 后端返回的签名和密钥是长期有效的,别人如果盗取了,就能往我们的oss随意存储数据
- 此方案不支持基于分片上传大文件、基于分片断点续传的场景
注意事项:
- 文件名添加uuid,防止覆盖(或请求头携带参数x-oss-forbid-overwrite: true)
- 最终上传到oss的图片地址通过拼接得到
(示例代码不再给出,文档已经很清楚了)
参考文档:表单上传方式、OSS设置跨域资源共享CORS
1.3 STS上传方式
流程:
- 服务端使用STS SDK获取STS临时访问凭证
- 客户端请求拿到临时访问凭证
- 使用OSS SDK直接上传文件,后端返回文件的临时url(不影响前端展示,因为会更新)
优点:
- 安全
- 支持分片上传、分片断点续传
缺点:依赖OSS SDK
注意事项:
-
频繁地调用STS服务会引起限流,因此需要对STS临时凭证做缓存处理,并在有效期前刷新
-
后台跨域规则中要允许PUT方法,否则使用put上传文件时,会报跨域错误!
- (坑)put方法上传成功后,res.size为0!获取文件大小使用file.size,而不是返回的size
参考文档:服务端生成STS临时访问凭证的上传方式、安装使用OSS SDK
具体代码:
先安装SDK开发包(源码):npm install ali-oss --save
/** STS临时凭证 */
let credentials = null
/**
* 判断临时凭证是否到期。
**/
function isCredentialsExpired(credentials) {
if (!credentials) {
return true
}
// credentials.expiration返回是秒
const expireDate = new Date().getTime() + credentials.expiration * 1000
const now = new Date().getTime()
// 如果有效期不足十分钟,视为过期
return expireDate - now <= 10 * 60 * 1000
}
/**
* 阿里云STS上传方案
* @param options
* file: 文件
* fileName: 文件名
* uploadDir: 上传目录
*/
function ossStsUpload(options) {
let { file, fileName, uploadDir } = options || {}
// 阿里云存储图片的目录(读取文件名时,需要再拆开读取)
if (!uploadDir) {
uploadDir = `${formatDate({ targetFormat: '{0}-{1}-{2}' })}`
}
if (!fileName) {
fileName = `${uploadDir}/${uid()}_${file.name}`
} else {
fileName = `${uploadDir}/${fileName}`
}
return new Promise(async (resolve, reject) => {
// 临时凭证过期时,才重新获取,减少对STS服务的调用
if (isCredentialsExpired(credentials)) {
const res = await API.getStsToken({})
credentials = res.returnValue
}
// 初始化SDK
const client = new OSS({
bucket: credentials.bucketName,
region: 'oss-cn-hangzhou',
accessKeyId: credentials.accessKeyId,
accessKeySecret: credentials.accessKeySecret,
stsToken: credentials.securityToken,
})
try {
// 调用SDK上传文件到OSS
const uploadResult = await client.put(fileName, file)
console.log('上传成功:', uploadResult)
// const getResult = await client.get('object')
// console.log('获取文件成功:', getResult)
resolve(uploadResult)
} catch (error) {
console.error('发生错误:', error)
reject(error)
}
})
}
1.4 微信小程序直传
因为微信小程序是基于微信的uploadFile接口上传文件,所以无法使用OSS SDK。
但依旧支持STS上传方式,只是步骤变了,还需要客户端生成签名。
参考文档:微信小程序直传实践
注意事项:
-
需要在小程序后台配置uploadFile和downloadFile的合法域名,为oss的Host地址
-
微信控制台访问oss资源403解决方法:
(1)oss后台防盗链配置增加:servicewechat.com
(2)小程序image组件、video组件、wx.previewMedia增加:referrerPolicy="origin"
具体代码:
import crypto from 'crypto-js';
import { Base64 } from 'js-base64';
/**
* 微信小程序上传阿里云oss
* filePath: 要上传的文件地址
* fileSuffix: 要上传的文件地址后缀,默认png
*/
function uploadOss(filePath, fileSuffix = 'png') {
return new Promise((resolve, reject) => {
http.getData('/base/getOssStsToken', {}, (res) => {
const data = res.returnValue;
// 客户端获取STS临时访问凭证并生成签名
function computeSignature(accessKeySecret, canonicalString) {
return crypto.enc.Base64.stringify(crypto.HmacSHA1(canonicalString, accessKeySecret));
}
const date = new Date();
// date.setHours(date.getHours() + 1);
date.setMinutes(date.getMinutes() + 30);
const policyText = {
expiration: date.toISOString(), // 设置policy过期时间
conditions: [
// 限制上传大小,目前是10MB
['content-length-range', 0, 10 * 1024 * 1024],
],
};
async function getFormDataParams() {
// const credentials = await axios.get('/getToken')
const policy = Base64.encode(JSON.stringify(policyText)); // policy必须为base64的string。
const signature = computeSignature(data.accessKeySecret, policy);
const formData = {
// 上传目录为当前日期,并且采用当前的时间戳 + 150内的随机数来给图片命名
key:
commonFormatDate({ targetFormat: '{0}-{1}-{2}' }) +
'/' +
new Date().getTime() +
Math.floor(Math.random() * 150) +
'.' +
fileSuffix,
OSSAccessKeyId: data.accessKeyId,
signature,
policy,
'x-oss-security-token': data.securityToken,
};
return formData;
}
getFormDataParams().then((formData) => {
const host = 'https://xxx-static.oss-cn-hangzhou.aliyuncs.com';
wx.uploadFile({
url: host,
filePath,
name: 'file',
formData,
success: (res) => {
if (res.statusCode === 204) {
console.log('上传成功', res);
resolve(host + '/' + formData.key);
}
},
fail: (err) => {
console.log(err);
reject(err);
},
});
});
});
});
}
1.5 结束
2024/10/26:发布