和数据直传到 OSS 相比,以上方法有三个缺点:
- 上传慢:用户数据需先上传到应用服务器,之后再上传到OSS。网络传输时间比直传到OSS多一倍。如果用户数据不通过应用服务器中转,而是直传到OSS,速度将大大提升。而且OSS采用BGP带宽,能保证各地各运营商之间的传输速度。
- 扩展性差:如果后续用户多了,应用服务器会成为瓶颈。
- 费用高:需要准备多台应用服务器。由于OSS上传流量是免费的,如果数据直传到OSS,不通过应用服务器,那么将能省下几台应用服务器。
- 在客户端通过JavaScript代码完成签名,无需过多配置,即可实现直传,非常方便。但是客户端通过JavaScript把AccesssKeyID和AccessKeySecret写在代码里面有泄露的风险,建议采用服务端签名后直传。
先来实现这种不需要回调的方式
需要如下几个参数
OSSAccessKeyId
policy
Signature
key
success_action_status
file
点这里是原文链接
前端代码(用的Jquery)
使用formData上传数据
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
hh
<input type="file" id="upload" />
<a href="https://www.bilibili.com" target="black">百度</a>
<script src="/jquery.js"></script>
<script>
document.getElementById("upload").onchange = function() {
var file = this.files[0];
$.ajax({
url: "/policy",
data: "",
type: "get",
dataType: "json",
success: function(data) {
let param = new FormData(); // 创建form对象
console.log(data.callbackbody);
param.append("OSSAccessKeyId", data.OSSAccessKeyId);
param.append("policy", data.policy);
param.append("key", data.startsWith + data.saveName + file.name);
param.append("success_action_status", 200);
param.append("signature", data.signature);
param.append("file", file, data.saveName);
const xhr = new XMLHttpRequest();
xhr.open("post", data.host, true);
xhr.send(param);
}
});
};
</script>
</body>
</html>
后端代码(用koa脚手架创建的)
从这获取你的OSS域名
从这获取你的ID和密匙
const crypto = require("crypto");
const path = require("path");
var router = require("koa-router")();
let oss = {
OSSAccessKeyId: "你的AccessKey ID",
secret: "你的Access Key Secret",
host: "你的oss外网域名"
};
router.get("/", function*(next) {
yield this.render("index");
});
router.get("/policy", function*(next) {
const dirPath = "images/"; //你的bucket 项目里的文件路径
const { OSSAccessKeyId, host, secret } = oss;
let end = new Date().getTime() + 360000; //设置过期时间
let expiration = new Date(end).toISOString();
/*Post policy中必须包含expiration和conditions。
Expiration
Expiration项指定了policy的过期时间,以ISO8601 GMT时间表示。例如2014-12-01T12:00:00.000Z指定了Post请求必须发生在2014年12月1日12点之前。
Conditions
Conditions是一个列表,可以用于指定Post请求的表单域的合法值。
这里详细点上面的链接看官方文档
*/
let policyString = {
expiration,
conditions: [
{ bucket: "你的bucket" },
["content-length-range", 0, 1048576000],
["starts-with", "$key", dirPath]
]
};
policyString = JSON.stringify(policyString);
const policy = Buffer(policyString).toString("base64");
//policy为一段经过UTF-8和base64编码的JSON文本,声明了Post请求必须满足的条件(文档)
const signature = crypto
.createHmac("sha1", secret)
.update(policy)
.digest("base64");
this.body = {
OSSAccessKeyId: OSSAccessKeyId,
host,
policy,
signature,
key: dirPath + end,
saveName: end,
startsWith: dirPath
};
});
module.exports = router;
最后npm start 浏览器输入localhost:3000
这里随便发张图
这里可以看到控制台自己的那个OSS域名发来响应200
再去自己的bucket看到这张图,这就OK了!
最后实现返回回调这种方式
官方给的流程图
前端代码类似,只是FormData多了一个参数(callback)
这里可以去看官方详细解释
https://help.aliyun.com/document_detail/31927.html?spm=a2c4g.11186623.6.1466.7787b81e6FJzPm
https://help.aliyun.com/document_detail/31989.html?spm=a2c4g.11186623.2.16.1219b81eZVD571
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
hh
<input type="file" id="upload" />
<script src="/jquery.js"></script>
<script>
document.getElementById("upload").onchange = function() {
var file = this.files[0];
$.ajax({
url: "/policy",
data: "",
type: "get",
dataType: "json",
success: function(data) {
let param = new FormData(); // 创建form对象
console.log(data.callbackbody);
param.append("OSSAccessKeyId", data.OSSAccessKeyId);
param.append("policy", data.policy);
param.append("key", data.startsWith + data.saveName + file.name);
param.append("success_action_status", 200);
param.append("callback", data.callbackbody); //base64编码
param.append("signature", data.signature);
param.append("file", file, data.saveName);
param.append("dir", "images/");
const xhr = new XMLHttpRequest();
xhr.open("post", data.host, true);
xhr.send(param);
}
});
};
</script>
</body>
</html>
后端(也是koa框架)
如果是本地测试,要先准备内网穿透工具。
我这里使用的穷人版的花生壳,主要将外网代理到本地koa服务器设置的端口上。
1.这里的回调函数函数返回给OSS的结果要以下格式
https://help.aliyun.com/document_detail/31989.html?spm=a2c4g.11186623.2.16.1219b81eZVD571
router.post("/detail", function*(next) {
console.log(this.request.body);
this.status = 200;
this.set("Content-Type", "application/json");
this.body = {
status: 200,
value: "ok"
};
});
2.callbackbody(base64)字符串
这里加了callbackhost:“你的oss外网域名” 会报502错误,具体官方文档我也不是很明白。
https://help.aliyun.com/document_detail/31989.html?spm=a2c4g.11186623.2.16.1219b81eZVD571
let callbackString = {
callbackUrl: "http://229p895l44.iask.in/detail", //花生壳设置的外网域名 detail为callback
callbackBody: "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}",
callbackBodyType: "application/x-www-form-urlencoded"
};
callbackString = JSON.stringify(callbackString);
let callbackbody = Buffer.from(callbackString).toString("base64");
完整的代码如下
const crypto = require("crypto");
const path = require("path");
var router = require("koa-router")();
let oss = {
OSSAccessKeyId: "你的AccessKey ID",
secret: "你的Access Key Secret",
host: "你的oss外网域名"
};
router.get("/", function*(next) {
yield this.render("index");
});
router.post("/detail", function*(next) {
console.log(this.request.body);
this.status = 200;
this.set("Content-Type", "application/json");
this.body = {
status: 200,
value: "ok"
};
});
router.get("/policy", function*(next) {
const dirPath = "images/"; //bucket 项目里的文件路径
const { OSSAccessKeyId, host, secret } = oss;
let end = new Date().getTime() + 360000;
let expiration = new Date(end).toISOString();
let policyString = {
expiration,
conditions: [
{ bucket: "zyh2020img" },
["content-length-range", 0, 1048576000],
["starts-with", "$key", dirPath]
]
};
policyString = JSON.stringify(policyString);
const policy = Buffer.from(policyString).toString("base64");
let callbackString = {
callbackUrl: "http://229p895l44.iask.in/detail", //花生壳设置的外网域名(内网穿透)
callbackBody: "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}",
callbackBodyType: "application/x-www-form-urlencoded"
};
callbackString = JSON.stringify(callbackString);
let callbackbody = Buffer.from(callbackString).toString("base64");
const signature = crypto
.createHmac("sha1", secret)
.update(policy)
.digest("base64");
this.body = {
OSSAccessKeyId: OSSAccessKeyId,
host,
policy,
signature,
callbackbody,
key: dirPath + end,
saveName: end,
startsWith: dirPath
};
});
module.exports = router;
npm run dev,打开浏览器输入你配置的花生壳域名,打开主页
选择一张图片上传
这里可以看到自己OSS域名返回结果了
这是自己的koa服务器返回给OSS,然后OSS返回给客户端的响应
这里我的vscode也打印出从OSS拿到了上传的文件信息了
最后看OSS里面也有刚刚上传的图片
如果出现OSS相应客户端203(OSS接收到了图片,但是没有向服务器发送回调),状态码为400
官方链接 https://help.aliyun.com/document_detail/31989.html?spm=a2c4g.11186623.2.16.1219b81eZVD571
如果状态码为502,除了我之前说的加了callbackhost,还有可能其他原因
其他错误,可以这篇文档的末尾 https://www.cnblogs.com/softidea/p/7217201.html