背景
每个OSS的用户都会用到上传服务。Web端常见的上传方法是用户在浏览器或app端上传文件到应用服务器,然后应用服务器再把文件上传到OSS。
和数据直传到OSS相比,以上方法有三个缺点:
- 上传慢。先上传到应用服务器,再上传到OSS,网络传送比直传到OSS多了一倍。如果直传到OSS,不通过应用服务器,速度将大大提升,而且OSS采用BGP带宽,能保证各地各运营商的速度。
- 扩展性差。如果后续用户多了,应用服务器会成为瓶颈。
- 费用高。需要准备多台应用服务器。由于OSS上传流量是免费的,如果数据直传到OSS,不通过应用服务器,那么将能省下几台应用服务器。
目的
Android、IOS端直传实践
Web端直传共有三种方式:
- JavaScript客户端签名直传讲解在客户端通过JavaScript代码完成签名,然后通过表单直传数据到OSS。
- 服务端签名后直传讲解在服务端通过PHP代码完成签名,然后通过表单直传数据到OSS。
- 服务端签名直传并设置上传回调讲解在服务端通过PHP代码完成签名,并且服务端设置了上传后回调,然后通过表单直传数据到OSS。OSS回调完成后,应用服务器再返回结果给客户端。
在客户端通过JavaScript代码完成签名,无需过多配置,即可实现直传,非常方便。但是客户端通过JavaScript把AccesssKeyID 和AccessKeySecret写在代码里面有泄露的风险。
web端向服务端请求签名,然后直接上传,不会对服务端产生压力,而且安全可靠。然而有个问题,就是用户上传了多少文件,上传了什么文件,服务端并不能马上知道,如果想实时了解用户上传了什么文件,可以采用第三种方式。
本文将演示如何通过第三种方式完成文件直传到OSS服务器。
demo
前端
window.onload = function () {
let upload = document.getElementById("upload")
let link = document.getElementById("link")
let obj = {}
// 获取签名地址
// const url = "http://gt-activity.gtdreamlife.com/api/oss/ossSign"
axios.get(url).then(res => {
if (res.data.statusCode === 200) {
// 下面是签名直传服务返回给客户端的消息body内容的示例,这个body的内容将作为客户端上传文件的重要参数。
let {
dirPath,
key,
host,
policy,
Signature,
callback,
OSSAccessKeyId
} = res.data.result
obj.host = host
obj.key = dirPath + key + "${filename}"
obj.policy = policy
obj.Signature = Signature
obj.callback = callback
obj.OSSAccessKeyId = OSSAccessKeyId
console.log(obj)
} else {
alert(res.data.message)
}
})
document.querySelector("#file").onchange = function (e) {
let data = e.target.files[0]
console.log(data)
let formData = new FormData()
for (let key in obj) {
formData.append(key, obj[key])
}
// append 文件必须放在最后,不然会报key错误
formData.append("file", data)
axios.post(obj.host, formData).then(res => {
const result = res.data
document.querySelector("#link").value = result.data
})
}
}
服务端
阿里云OSS配置信息
const config = {
dirPath: 'oss/file/', // oss 文件夹 不存在会自动创建
bucket: '${bucket}', // oss应用名
region: '${region}', // oss节点名
accessKeyId: '${accessKeyId}', // 申请的osskey
accessKeySecret: '${accessKeySecret}', // 申请的osssecret
callbackPath: 'api/oss/ossCallback', // 回调接口
expAfter: 6000, // 签名失效时间
maxSize: 1048576000, // 最大文件大小
};
获取签名接口
服务器返回数据Callback API
// 图片签名
async ossSign() {
const {
bucket,
region,
expAfter,
maxSize,
dirPath,
accessKeyId,
accessKeySecret,
callbackPath,
} = config;
const host = `https://${bucket}.${region}.aliyuncs.com`; // 你的oss完整地址
// 回调接口的主机和端口
const { callbackHost, callbackPort } = this.app.config.oss;
const expireTime = new Date().getTime() + expAfter;
const expiration = new Date(expireTime).toISOString();
const policyString = JSON.stringify({
expiration,
conditions: [
[ 'content-length-range', 0, maxSize ],
[ 'starts-with', '$key', dirPath ],
],
});
const policy = Buffer(policyString).toString('base64');
// 创建签名
const Signature = crypto.createHmac('sha1', accessKeySecret).update(policy).digest('base64');
const callbackBody = {
// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
callbackUrl: `https://${callbackHost}${callbackPort ? `:${callbackPort}` : ''}/${callbackPath}`,
callbackHost: `${callbackHost}`,
// 规定返回数据的格式,当前默认返回图片信息、宽度、高度,可获取更多数据。参考上方链接
callbackBody: '{"filename": ${object},"size": ${size},"width": ${imageInfo.width},"height": ${imageInfo.height}}',
callbackBodyType: 'application/json',
};
const callback = Buffer(JSON.stringify(callbackBody)).toString('base64');
this.ctx.body = ({
statusCode: 200,
message: 'oss签名成功',
result: {
Signature, // 对变量policy签名后的字符串。
policy, // 用户表单上传的策略(Policy),是经过base64编码过的字符串。
host, // 用户要往哪个域名发送上传请求。
OSSAccessKeyId: accessKeyId, // 用户请求的accessid。
key: expireTime, // 唯一标识,添加到上传的文件名中防止重复
success_action_status: 200,
dirPath, // 文件上传地址
callback, // 接口回调
},
});
}
请求回调接口
// 回调
async ossCallback() {
const { body = {} } = this.ctx.request;
// 对应签名接口中定义的返回数据字段
const { filename, height, width } = body;
if (filename) {
this.ctx.body = ({
status: 200,
message: '操作成功',
data: `https://${config.bucket}.${config.region}.aliyuncs.com/${filename}#w=${width}#h=${height}`,
});
} else {
this.ctx.body = {
status: 500,
message: '操作失败',
data: null,
};
}
}