项目日记|uniapp开发小程序上传对接阿里云oss

690 阅读5分钟

背景


某天客户在群里说

客户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对接采用前端直传方案。

官方建议某些参数最好是通过后端来完成,但是因为需要快速完成就让前端独自完成。

image.png

实现


  1. 首先需要在阿里云管理控制台中创建 OSS 存储桶,并获取相关的配置信息
  • endpoint: oss-cn-zhangjiakou.aliyuncs.com (region)
  • key-id: xxx (accessKeyId)
  • key-secret: xxx (accessKeySecret)
  • bucket-name: xxx (bucket)
  1. 生成签名和直传策略

如果从安全性(官方推荐方案)角度来做的话,这一步应该是后端生成的。

废话不多说了,直接上完整代码。

// 先安装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);