背景
某天客户在群里说
客户A:小程序里面,视频上传,最多支持多大的?为啥十二秒的我都传不上去呀
收到消息的后端同事一顿操作猛如虎的查询问题,发现是限制的上传大小只有20M,于是在客户群里进行反馈。
后端A:目前接口最大支持上传20M的文件
客户看到反馈后立马就不乐意了
客户A:这个限制太狠了 几秒都传不了啊
客户B:业务需求是1-3分钟的视频
客户C:最长得要3分钟,我刚才看了一下我手机的录制视频,2分14秒的原视频要350M ....
于是乎群里热闹非凡。
最终得到一个结论需求变更,最大得支持500M视频上传。
后端同学查看服务器,发现只是一台最基本的服务器,磁盘空间也没剩下多少了,带宽也不是很高。
最终建议客户花钱使用oss吧!
于是乎群里又是热闹非凡,最终同意了此方案。
什么是oss?
OSS 指的是 Object Storage Service,即对象存储服务。它是一种基于对象存储的云服务,主要用于存储和管理大量的非结构化数据,比如图片、视频、备份文件等。OSS 提供了高可用性、高可靠性和弹性的存储服务,用户可以通过 RESTful API 或 SDK 进行上传、下载和管理数据。
为什么要使用oss?
1. 弹性和可扩展性
OSS 可以按需扩展,用户无需预先分配存储空间,存储容量可以根据需求自动增长。这对于需要存储大量数据(如图片、视频、备份)的企业或应用非常实用。
2. 高可用性和可靠性
对象存储通常通过多副本存储和冗余设计,确保数据在任何硬件故障情况下都不会丢失。云服务商一般承诺 99.9% 以上的可用性 SLA(服务水平协议)。
3. 全球访问
OSS 提供全球 CDN 加速,用户在全球范围内都可以快速访问存储在 OSS 中的数据。这对于需要向全球用户提供内容的应用(如视频点播、文件下载)非常重要。
4. 低成本
相比传统的存储方式(如购买物理服务器或硬盘),使用 OSS 可以按需付费。用户只需为实际使用的存储空间和数据流量付费,省去了购买和维护硬件的成本。
5. 简单易用
OSS 提供简单的 RESTful API 和 SDK,支持常见的编程语言和工具。这使得开发人员可以轻松将对象存储集成到应用中,实现文件的上传、下载和管理。
6. 安全性和权限控制
OSS 支持精细的权限管理,用户可以为不同的对象设置不同的访问权限(如公开访问、私有访问或仅特定用户访问)。此外,许多对象存储服务还支持加密和身份验证,以保证数据安全。
7. 支持大文件存储
对象存储非常适合存储大量非结构化数据,如图片、视频、日志文件等,特别是大文件,提供了高效的分块上传功能。
8. 自动备份和版本管理 有些对象存储服务支持对象版本管理和自动备份功能,用户可以方便地恢复数据的早期版本,进一步增强了数据安全性
上述可自行百度
既然好处那么多,那就用呗。
官方文档一顿查询,摒弃为客户快速解决问题为前提,本次oss对接采用前端直传方案。
官方建议某些参数最好是通过后端来完成,但是因为需要快速完成就让前端独自完成。
实现
- 首先需要在阿里云管理控制台中创建 OSS 存储桶,并获取相关的配置信息
- endpoint: oss-cn-zhangjiakou.aliyuncs.com (region)
- key-id: xxx (accessKeyId)
- key-secret: xxx (accessKeySecret)
- bucket-name: xxx (bucket)
- 生成签名和直传策略
如果从安全性(官方推荐方案)角度来做的话,这一步应该是后端生成的。
废话不多说了,直接上完整代码。
// 先安装crypto-js依赖
import hmacSHA1 from "crypto-js/hmac-sha1";
import base64 from "crypto-js/enc-base64";
// 此处注意上传到oss接口地址是 bucket + endpoint 拼接起来的
export const options = {
accessKeyId: "xxx",
accessKeySecret: "xxx",
url: "https://xxx.oss-cn-zhangjiakou.aliyuncs.com",
};
class MpUploadOssHelper {
constructor(options) {
this.url = options.url;
this.path = options.path || new Date().toLocaleDateString("zh-CN");
this.accessKeyId = options.accessKeyId;
this.accessKeySecret = options.accessKeySecret;
this.timeOut = options.timeout || 1; // 限制参数的生效时间(单位:小时)。
this.maxSize = options.maxSize || 10; // 限制上传文件大小(单位:Mb)。
}
createKey(type) {
const key = this.path + "/" + this.randomFileName() + "." + type;
return key;
}
createUploadParams() {
const policy = this.getPolicyBase64();
const signature = this.signature(policy);
return {
OSSAccessKeyId: this.accessKeyId,
policy: policy,
signature: signature,
};
}
getPolicyBase64() {
let date = new Date();
// 设置policy过期时间。
date.setHours(date.getHours() + this.timeOut);
let srcT = date.toISOString();
const policyText = {
expiration: srcT,
conditions: [
// 限制上传文件大小。
["content-length-range", 0, this.maxSize * 1024 * 1024],
],
};
const buffer = new Buffer(JSON.stringify(policyText));
return buffer.toString("base64");
}
signature(policy) {
return base64.stringify(hmacSHA1(policy, this.accessKeySecret));
}
// 随机文件名称
randomFileName(length) {
const data = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
];
length = length || 30;
let nums = "";
for (let i = 0; i < length; i++) {
const randomStr = (Math.random() * 61).toString();
const r = parseInt(randomStr, 10);
nums += data[r].toString();
}
return nums;
}
upload(uploadParams) {
return new Promise((resolve, reject) => {
const params = {
key: this.createKey(uploadParams.type),
success_action_status: "200",
...this.createUploadParams(),
};
uni.showLoading({
title: "上传中...",
mask: true,
});
uni.uploadFile({
url: this.url,
fileType: "image",
filePath: uploadParams.url,
fileName: params.key,
formData: params,
name: "file",
success: (res) => {
uni.hideLoading();
// this.url + "/" + params.key就是文件在oss的访问地址,将这个地址传给后端进行存储。
resolve(this.url + "/" + params.key);
},
fail: (err) => {
uni.hideLoading();
uni.showToast({
title: err.message,
icon: "error",
});
},
});
});
}
}
export default new MpUploadOssHelper(options);