小程序直传 OSS

882 阅读8分钟

背景:在小程序中经常会遇到文件上传的业务需求,比如上传图片或视频,上传的方法也有多种,其中就包含小程序oss直传。

一、关于 OSS

OSS 是阿里云对象存储(Object Storage Service)缩写,是一款海量、安全、低成本、高可靠的云存储服务,提供最高可达 99.9% 的服务可用性。多种存储类型供选择,全面优化存储成本。

二、文件上传到 OSS

OSS 给我们提供了很多便捷的方法供我们使用,它包含了运行于各端的sdk和自定义方法,比如:

  1. 在浏览器中 oss 提供了 browser.js sdk
  2. 在服务端中提供了针对各种服务端语言的 sdk 方法
  3. OSS 也给我们提供了请求接口的方式进行 ,如在浏览器中使用 Ajax post 请求的方法把文件当成参数转换成 formData 对象上传(在小程序中正是使用了接口请求的方式直传 oss)

三、小程序直传 oss

经过上面的介绍我们简单的了解了 oss ,那么它具体是怎么上传的呢:

  • 作为开发者我们更加关注如何使用小程序直传 oss,接下来看一下简单的示例

    dd.chooseImage({ success: res => { const path = res.apFilePaths[0]; dd.uploadFile({ url: host, fileName: 'file', filePath: path, formData: { key, policy, OSSAccessKeyId: ossAccessKeyId,
    signature, }, success: (res) => { console.log('xxxxx') }, }); } });

相信使用使用过浏览器上传的同学会发现使用起来代码更加简洁,无需初始化 OSS 实例,和钉钉小程序选择图片的 api 无缝连接,无需关注图片处理,那么如何使用呢?下面跟着文章详细地介绍上传过程吧

1、配置 bucket 桶的跨域访问(已配置过的可忽略此步骤)

客户端进行表单直传到 OSS 时,会从浏览器向 OSS 发送带有 Origin 的请求消息。OSS 对带有 Origin 头的请求消息会进行跨域规则(CORS)的验证。因此需要为 Bucket 设置跨域规则以支持 Post 方法。(可找运维(@宋俊)配置)

2、获取 OSS 配置信息

因 oss 的基本信息不能直接裸露在前端,有极高的安全风险,需要通过服务端接口的方式返回给到前端:如接口/oss-base-info示例

axios.get('/oss-base-info').then(res => {
  const {
    bucketName = 'fe-oss-files',
    endpoint = 'http://oss-cn-shanghai.aliyuncs.com',
    accessKeyId,
    accessKeySecret,
    host
  } = res.data;
  const ossBaseInfo = {
    accessKeyId,  
    accessKeySecret,
    bucketName,
    endpoint,
    host
  };
});

3、数据安全签名校验

在小程序直传中为了文件的安全,我们还需要需要使用签名(policy 和 signature )

  • **policy:**验证请求的合法性,Policy 为一段经过 UTF-8 和 Base64 编码的 JSON 文本,以下生成 policy 的描述文本

    const policyText = { "expiration": "2023-04-28T12:00:00.000Z", // 设置过期时间 "conditions": [ // 设置请求的合法性 {"bucket": "fe-oos-files" }, // 桶必须是fe-oos-files ["content-length-range", 1, 10], // 上传Object的最小和最大允许大小,单位为字节。 ["eq", "successactionstatus","201"],//上传成功后的状态码["startswith","success_action_status", "201"], // 上传成功后的状态码 ["starts-with", "key", "user/eric/"], // 上传的文件路径必须是user/eric/ ["in", "contenttype",["image/jpg"]],//上传的文件类型必须是符合数组里面的["notin","content-type", ["image/jpg"]], // 上传的文件类型必须是符合数组里面的 ["not-in", "cache-control", ["no-cache"]] // 上传中的参数是否需要缓存 ] }

  • Signature:签名的字符串,根据 accesskey 和 policy 计算的签名信息,OSS验证该签名信息从而验证该 Post 请求的合法性

  • policysignature 生成的方法

  • 请求服务端接口获取,可以在获取 oss 基本信息的时候返回。如下面 node 服务的代码片段;

    const config = { accessKeyId: '', accessKeySecret: '', bucketName: '', endpoint: '', } app.get("/oos-base-info", async (req, res) => { // new OSS 对象 const client = new OSS(config);

    // 设置过期时间 1 天 const date = new Date(); date.setDate(date.getDate() + 1);

    // policy 描述信息 const policyText = { expiration: date.toISOString(), conditions: [ ["content-length-range", 0, 1048576000],
    ], };

    // 设置请求信息头 res.set({ "Access-Control-Allow-Origin": req.headers.origin || "*", "Access-Control-Allow-Methods": "PUT,POST,GET", });

    const formData = await client.calculatePostSignature(policy);

    const host = http://${config.bucket}.${ (await client.getBucketLocation()).location }.aliyuncs.com.toString(); const params = { expire: moment().add(1, "days").unix().toString(), policy: formData.policy, // 从OSS服务器获取到的Policy。 signature: formData.Signature, // 从OSS服务器获取到的Signature。 accessid: formData.OSSAccessKeyId,// 从OSS服务器获取到的OSS AccessKeyId。 host, // 格式为bucketname.endpoint,例如https://bucket-nam… }; res.json(params); });

  • 客户端生成,前提生成签名需要服务端返回 STS 临时授权账号 securityToken,然后通过客户端进行签名算法

    import crypto from 'crypto-js'; import { Base64 } from 'js-base64';

    // 计算签名。 function computeSignature(accessKeySecret, canonicalString) { return crypto.enc.Base64.stringify(crypto.HmacSHA1(canonicalString, accessKeySecret)); }

    const date = new Date(); date.setHours(date.getHours() + 1); const policyText = { // 设置policy过期时间。 expiration: date.toISOString(), conditions: [ // 限制上传的文件大小。 ["content-length-range", 0, 1024 * 1024 * 1024], ], };

    async function getFormDataParams() { // STS临时授权账号 const res = await axios.get('/oss-info') const policy = Base64.encode(JSON.stringify(policyText)) const signature = computeSignature(res.AccessKeySecret, policy) const formData = { OSSAccessKeyId: res.AccessKeyId, signature, policy, 'x-oss-security-token': res.SecurityToken } return formData }

4、调用上传钉钉小程序上传 API

因为 dd.uploadFile 的 api 天然支持 fromData 的上传方式,故使用 oss 的请求接口的方式[PostObject](https://help.aliyun.com/document_detail/31988.html?spm=a2c4g.173882.0.0.3fe5774ftZ6Yjb)上传是最佳的一种方式,

所以我们使用 dd.uploadFile 进行上传,代码如下:

dd.chooseImage({
  chooseImage: 1,
  success: res => {
     // 获取本地图片虚拟地址,上传中会转化成文件的形式
    const path = res.apFilePaths[0]; 
    dd.uploadFile({
        url: host,                             // 配置桶和节点生成的url,注意是https
        fileType: 'image', 										 // 文件类型
        fileName: 'file',  										 // 文件名称
        filePath: path,     									 // 本地文件存储的虚拟地址
        formData: {
            key,            								   // 上传文件的路径
            policy,         									 // 客户端生成的policy 
            OSSAccessKeyId: ossAccessKeyId,    // 接口信息获取的 OSSAccessKeyId
            signature,
            success_action_status: '200',      // 上传成功后设置响应的状态码
            'x-oss-security-token': xstoken    // 使用STS临时账号,必填
        },
        success: (res) => {
          if (res.statusCode === 200) {
             const {url} =  res.data;          // 返回的oss临时url
          }
        },
        fail: err => {
          console.log(err);
        }
    });
  }
});

代码示例仓库地址:gitlab.sto.cn/ued-project…

5、注意事项

5.1、上传成功后返回的url不能直接预览或打开

oss 上传完成后 url 并不能直接预览或打开,因为在访问资源的时候,oss 的配置不能设置成公开访问,公开访问会导致资源被盗链,或资源被截取;

  • 资源被盗链:资源每次被访问都是有流量产生或访问次数产生。不管 oss 是按流量收费,还是访问次数收费,显然增加额外的费用。
  • 资源被截取:通过下载或者保存导致资源泄露

解决方案

1、通过后端接口把上传返回的 url 传给后端服务,后端服务通过 oss sdk 的预览方法返回给前端后进行预览打开

2、前端客户端根据 oss sdk 的预览方法

四、小程序 OSS 直传对比 h5 使用 OSS 上传的优点

1、h5 上传

  • h5 中上传,通常是使用 input 标签选择文件后后上传。文件选择后,该文件的信息就存储在内存当中。

以下时web浏览器系统制定的内存的标准管理

  • 新生代内存:64 位操作系统 32M,32 位操作系统 16M

  • 老生代内存:64 位操作系统 1.4G,32 位操作系统 700M

  • 当选取的文件大于 32M 立马会把文件内存移动老生代内存进行标记整理,上传完后若该文件有被引用没有主动释放,内存就会变得爆满,导致内存不够应用崩溃

2、钉钉小程序上传

小程序上传是钉钉客户端 app 上传,不同于网页上传,钉钉小程序通过调用 dd.uploadFile上传,该api 通过钉钉客户端应用选择的图片或视频的虚拟地址进行上传,该地址仅是一个字符串,并不会占用应用的内存。故不管选择多大的文件对于应用来说都没有增加多少内存,在上传的时候钉钉客户端应用解析该地址通过文件操作的方式进行上传

综上小程序上传占用内存小,可以上传很多很大的图片。交互体验也能更贴近客户端使用,操作也更加方便

附录:OSS 名称注解

1、对象(Object)

对象是 OSS 存储数据的基本单位。 它称为 OSS 对象,也称为 OSS 文件。 我们可以简单地将 Object 理解为文件夹中的文件

2、Bucket(存储桶)

Bucket是用户用来管理存储的对象的存储空间。 每个 OSS 用户可以有多个存储桶。 桶名称在 OSS 范围内必须是全局唯一的。 创建后,名称将无法更改。 存储桶中的对象数量没有限制。 我们可以简单地将存储桶理解为本地计算机上的文件夹。

3、区域(Region)

Region 表示 OSS 的数据中心所在的区域,经纬度位置。一般来说,距离用户更近的 Region 访问速度更快。目前已经开通的Region有杭州、上海、深圳、北京、青岛、香港、美国和新加坡。Region 是在创建 Bucke t的时候指定的,一旦指定之后就不允许更改,该Bucket下所有的Object都存储在对应的数据中心,目前不支持 Object 级别的Region设置。我们可以理解为 OSS 的存储地域。

4、端点(Endpoint)

下面说一下Endpoint,Endpoint 表示 OSS 对外服务的访问域名。OSS以HTTP REST API的形式对外提供服务,当访问不同Region的时候,需要不同的域名

5、AccessKey

AccessKey 简称 AK,指的是访问身份验证中用到的 AccessKeyId 和 AccessKeySecret。OSS 通过使用 AccessKeyId和 AccessKeySecret 对称加密的方法来验证某个请求的发送者的身份。

AccessKeyId用于标示用户,AccessKeySecret是用户用于加密签名字符串和OSS用来验证签名字符串的密钥,其中AccessKeySecret必须保密。AccessKey可以理解为OSS被访问时验证身份的钥匙。