基于阿里云 OSS 文件上传统一服务
Opened this issue · 0 comments
tsui66 commented
当一个相同的项目功能存在于三个以上的项目的时候,我们就应该重新考虑是否该把他独立成单服务,比如我们最常见的阿里云 OSS 文件上传服务。本文就「基于阿里云 OSS 文件上传统一服务」相对简单的工程做一个介绍。效率工程的探索我们应该跳出代码、框架聚焦于约定,这些实现都需要多方配合,如前端、后端以及运维人员。本文仅提供参考,细节上会有很多瑕疵。你可以从本文学到:
- 阿里云 OSS STS 临时授权访问机制实现
- 前后端组件化思路
- 代码工程实现
STS 原理
阿里云 OSS 可以通过阿里云 STS(Security Token Service)进行临时授权访问。通过 STS,您可以为第三方应用或子用户(即用户身份由您自己管理的用户)颁发一个自定义时效和权限的访问凭证。
服务实现
凭证服务
// 创建临时 oss policy, 用户客户端直传凭证
createSTSPolicy(expiration, accessKeyId, accessKeySecret, region, bucket) {
const dirPath = `${bucket}/`;
let policyString = {
expiration, // 设置该Policy的失效时间,超过这个失效时间之后,就没有办法通过这个policy上传文件了
conditions: [
[ 'content-length-range', 0, 1048576000 ],
[ 'starts-with', '$key', dirPath ],
],
};
policyString = JSON.stringify(policyString);
const policy = new Buffer(policyString).toString('base64');
const signature = crypto.createHmac('sha1', accessKeySecret).update(policy).digest('base64');
const new_multipart_params = {
OSSAccessKeyId: accessKeyId,
host: `https://${bucket}.${region}`,
policy,
signature,
expiration,
startsWith: dirPath,
};
return new_multipart_params;
}
// 假设调用该服务 API 为: http://127.0.0.1:7001/api/oss/credentials?bucket=xxx
// bucket 一个消费方服务对应一个
使用方式
import { Injectable, HttpService } from '@nestjs/common';
import { ConfigService } from 'nestjs-config';
@Injectable()
export class OssService {
constructor(
private readonly httpService: HttpService,
private readonly configService: ConfigService
) {}
async findAll(): Promise<any> {
const res = await this.httpService.get(`http://127.0.0.1:7001/api/oss/credentials?bucket=workshop`).toPromise();
return res.data;
}
}
// 返回值说明, 以下字段阿里云文档都有说明,传送门在文末「引用」
{
"code": 200,
"data": {
"OSSAccessKeyId": "STS.NTr85byvDqhXVNYej3HASP7v8",
"host": "https://${bucket}.oss-cn-hangzhou.aliyuncs.com",
"policy": "eyJleHBpcmF0aW9uIjoiMjAyMS0wNS0xNVQxMDoxMjoyNVoiLCJjb25kaXRpb25zIjpbWyJjb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsImRjLXdvcmtzaG9wLyJdXX0=",
"signature": "WpkqHH4ori4NI1Du1U6lV37xcWI=",
"expiration": "2021-05-15T10:12:25Z",
"startsWith": "${bucket}/",
"SecurityToken": "CAIS/QF1q6Ft5B2yfSjIr5fHc4/WlKllxqqzVGjogWpmRM5/v/Ld1Tz2IHhIfXdtB+wYsv8+nmxS6/oTlqp6U4cdtbUh+TU3vPpt6gqET9frma7ctM4p6vCMHWyUFGSIvqv7aPn4S9XwY+qkb0u++AZ43br9c0fJPTXnS+rr76RqddMKRAK1QCNbDdNNXGtYpdQdKGHaOITGUHeooBKJXBMx5lIj0D4it/Xvk5PM0HeE0g2mkN1yjp/qP52pY/NrOJpCSNqv1IR0DPGZjHcOtkYWqvst3fYYo2qb78vuCl1Q8giANPHP7tpsIQl2a643AadYq+Lmkvl1qhwBQyxuTeUmGoABaRdUV6l+4Ec/rgFXFl7vVvrLzmjukcGoSNkdxH3Eee9UfZvfruEcciRXy6vQAM8rMjeFsNUYIvtBmW5W3OztoG3TCscQAYH0x7YGCF/dygdnGjh4sD67C9S3B0R0fVD5LI1OQPbQd+JR1ePjAJfPpDrjSaBOvZX+fEsnPr8eLU4="
},
"message": "ok"
}
前端实现
async ({ filename, file, onSuccess, onError }) => {
const { data } = await getOssCredentials();
const { host, policy, signature, startsWith, OSSAccessKeyId, SecurityToken } = data;
const key = `${
startsWith + String(new Date().getTime()) + parseInt(String(Math.random() * 100), 10)
}.png`;
const formdata = new FormData();
formdata.append('name', filename);
formdata.append('key', key);
formdata.append('policy', policy);
formdata.append('OSSAccessKeyId', OSSAccessKeyId);
formdata.append('success_action_status', 200);
formdata.append('Signature', signature);
formdata.append('x-oss-security-token', SecurityToken);
formdata.append('file', file);
request.post(`${host}`, { body: formdata }).then(() => {
const url = `${host}/${key}`; // url 即为文件链接
const { onChange } = this.props;
onSuccess({ url });
if (onChange) {
onChange(url, file);
}
}, onError);
};