浅谈oss图片上传

起因

源于一次窥屏,之前工作时,看到同事漂亮的记事本,动了邪念。日常记录,编辑器这么好看?

image-20210412183124708

日常安利 - Typora

便于读写、简单并强大、易用、一键换肤,切换 md 语法,便于使用 md 文件。Typora 官网^1

终于在近期11月推出1.0正式版,15刀激活正版,或者beta版本的免费使用。

image-20210412183404233

md 如何插图?

玩着玩着,发现一个问题,如何便捷的插图呢?

网上的妖路子:

  1. 直接保存在本地

  2. 自行上传(七牛云,阿里云,搜狐图库,github,gitee等等);

  3. 使用其他博客编辑图片的外链;

  4. 直接保存在自己的服务器上;

  5. 以及网上其他各种图库;

而 typora 推荐的几种图片处理方式,也是使用比较知名的几款app。

image-20210412183920657

picgo 是什么?

12k star,一个用于快速上传图片并获取图片 URL 链接的工具,支持mac和windows端。有多种图床可供选择,使用electron开发的跨端应用。

picgo-2.0

其中他的上传的内核就是使用PicGo-Core

PicGo-Core

PicGo-Core 是 PicGo2.0 版本里将会预置的底层核心组件。它可以看做是一个简单的上传流程系统。Picgo-Core官网^2

flow

4 个部件
  1. Input(输入):接受来自外部的图片输入,默认是通过路径或者完整的图片 base64 信息

  2. Transformer(转换器):把输入转换成可以被上传器上传的图片对象(包含图片尺寸、base64、图片名等信息)

  3. Uploader(上传器):将来自转换器的输出上传到指定的地方,默认的上传器将会是 SM.MS

  4. Output(输出):输出上传的结果,通常可以在输出的 imgUrl 里拿到结果

3 个生命周期钩子
  1. beforeTransform:可以获取 input 信息

  2. beforeUpload:可以获取通过转换器的 output 信息

  3. afterUpload:可以获取最终的 output 信息

PicGo-Core 源码浅析

PicGo-Core-dev\src\plugins\uploader\aliyun.ts第19行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const postOptions = (options: IAliyunConfig, fileName: string, signature: string, image: Buffer): Options => {
return {
method: 'PUT',
url: `https://${options.bucket}.${options.area}.aliyuncs.com/${encodeURI(options.path)}${encodeURI(fileName)}`,
headers: {
Host: `${options.bucket}.${options.area}.aliyuncs.com`,
Authorization: signature,
Date: new Date().toUTCString(),
'content-type': mime.lookup(fileName)
},
body: image,
resolveWithFullResponse: true
}
}

可以看到,picgo中aliyun的解决方案就主要使用的aliyun oss,配置相关的参数并发送请求,属于前端直传方式。其中IAliyunConfig来源于初始化的配置,在跟目下picgo的一个json文件中。

Ali OSS是什么?

对象存储(Object Storage Service,简称 OSS),是阿里云对外提供的海量、安全和高可靠的云存储服务。

常见使用场景

使用 Antd Upload 组件,上传图片等。

image-20210413121552135

OSS 两种上传方式

JavaScript 客户端签名直传,服务端签名后直传。

直传

JS 直传 Demo:

此处参考aliyun js直传帮助文档^3和ant-design的issue^4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import React from 'react';
import { Upload, message, Button } from 'antd';
import oss from 'ali-oss';
import moment from 'moment';

function getBase64(img, callback) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
}

const client = self => {
const { token } = self.state;

return new oss({
accessKeyId: token.access_key_id,
accessKeySecret: token.access_key_secret,
region: 'oss-cn-hangzhou', //
bucket: token.bucket, //
});
};

const uploadPath = (path, file) => {
// 上传文件的路径,使用日期命名文件目录
return `peanutone/${moment().format('YYYYMMDD')}_${
file.name.split('.')[0]
}-zhichuan.${file.type.split('/')[1]}`;
};

const UploadToOss = (self, path, file) => {
const url = uploadPath(path, file);
return new Promise((resolve, reject) => {
client(self)
.multipartUpload(url, file)
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});
};

class UploadJS extends React.Component {
state = {
loading: false,
token: {
access_key_id: '', // oss的key_id
access_key_secret: '', // oss的secret
host: 'oss-cn-hangzhou.aliyuncs.com', // 自己oss服务器的配置信息
bucket: 'peanutone-img-bed', // 自己oss服务器的配置信息
},
};
handleChange = info => {
if (info.file.status === 'uploading') {
this.setState({ loading: true });
return;
}
if (info.file.status === 'done') {
// Get this url from response in real world.
getBase64(info.file.originFileObj, imageUrl =>
this.setState({
imageUrl,
loading: false,
}),
);
}
};
beforeUpload = file => {
// 检查图片类型
const isJPG = file.type === 'image/jpeg';
const isPNG = file.type === 'image/png';
const isBMP = file.type === 'image/bmp';
const isGIF = file.type === 'image/gif';
const isWEBP = file.type === 'image/webp';
const isPic = isJPG || isPNG || isBMP || isGIF || isWEBP;
if (!isPic) {
message.error('请选择图片');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('图片不能大于2M');
}
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => {
// 使用ossupload覆盖默认的上传方法
UploadToOss(this, '上传路径oss配置信息', file).then(data => {
console.log(data.res.requestUrls);

this.setState({ imageUrl: data.res.requestUrls });
});
};
return false; // 不调用默认的上传方法
};

render() {
return (
<Upload
name="avatar"
beforeUpload={this.beforeUpload}
onChange={this.handleChange}
>
<Button type="primary">上传图片</Button>
</Upload>
);
}
}
export default UploadJS;

STS 策略

OSS 可以通过阿里云 STS(Security Token Service)进行临时授权访问^5。通过 STS,您可以为第三方应用或子用户(即用户身份由您自己管理的用户)颁发一个自定义时效和权限的访问凭证。

p983

STS demo:

这里使用koa初始化一个项目,只贴比较关键的逻辑部分。此处参考aliyun nodejs sdk帮助文档^6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 这里的aliyun就是对应accessKeyId,accessKeySecret,bucket等信息
const { aliyun } = require("./config");
const crypto = require("crypto");
const StsClient = require("@alicloud/sts-sdk");

class Medias {
async uploadImages(ctx) {
const { accessKeyId, host, accessKeySecret } = aliyun;
const dirPath = "peanutone/";

// 当前时间
let nowTime = new Date().getTime();
let expiration = new Date(nowTime + 30 * 1000).toISOString();

// 请求STS
const sts = new StsClient({
endpoint: "sts.cn-hangzhou.aliyuncs.com", // check this from sts console
accessKeyId, // check this from aliyun console
accessKeySecret, // check this from aliyun console
});

// 这里需要在aliyun控制台配置一个角色,给予对应的权限
const res1 = await sts.assumeRole(
`acs:ram::你的role得值:role/peanutonetest`,
"PeanutoneTest"
);

let policyString = {
expiration,
conditions: [{ bucket: "peanutone-img-bed" }],
};
policyString = JSON.stringify(policyString);

// 策略和签名
const policy = new Buffer(policyString).toString("base64");
const signature = crypto
.createHmac("sha1", res1.Credentials.AccessKeySecret)
.update(policy)
.digest("base64");
const saveName = nowTime + "_";

// 设置返回的body
ctx.body = {
token: res1.Credentials.SecurityToken,
accessKeyId: res1.Credentials.AccessKeyId,
accessKeySecret: res1.Credentials.AccessKeySecret,
policy,
Signature: signature,
key: dirPath + saveName,
};
}
}
module.exports = new Medias();

优化空间
  1. 通过云服务商获取access相关的环境变量;

  2. 自建CDN

  3. 加密

业务中的使用

Snipaste_2021-04-09_16-05-09

Snipaste_2021-04-09_16-05-31

Snipaste_2021-04-09_16-05-45

可以看到这里使用的就是第二种即STS策略,这也是相对最为保险的OSS使用策略。

参考链接:

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

给阿姨来一杯卡普基诺~

支付宝
微信