本文介绍如何在微信小程序环境下将文件上传到OSS。

背景信息

小程序是当下比较流行的移动应用,例如大家熟知的微信小程序、支付宝小程序等。它是一种全新的开发模式,无需下载和安装,为终端用户提供更优的用户体验。如何在小程序环境下上传文件到OSS也成为开发者比较关心的一个问题。

JavaScript客户端直传实践的原理相同,小程序上传文件到OSS也是利用OSS提供的PostObject接口来实现表单文件上传到OSS。关于PostObject的详细介绍请参见PostObject

步骤1:配置Bucket跨域访问

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

  1. 登录OSS管理控制台
  2. 单击Bucket列表,然后单击目标Bucket名称。
  3. 在左侧导航栏,选择权限管理 > 跨域设置,然后在跨域设置区域,单击设置
  4. 单击创建规则,配置如下图所示。
    说明 为了您的数据安全,实际使用时,来源建议填写实际允许访问的域名。更多配置信息请参见设置跨域访问

步骤2:微信小程序配置域名白名单

  1. 登录OSS管理控制台
  2. 单击Bucket列表,然后单击目标Bucket名称。
  3. 在存储空间概览页的访问域名区域查看Bucket域名
  4. 登录微信小程序平台,将上传和下载的合法域名填写为Bucket的外网访问域名。
    说明 实际业务中,建议您将OSS提供的外网域名和您自己的域名进行绑定。配置步骤请参见绑定自定义域名

步骤3:获取签名

为了您的数据安全,建议使用签名方式上传文件。OSS提供以下两种签名方式:

服务端签名

使用服务端签名时,您需要先搭建一个签名服务,然后由客户端调用签名服务生成签名。

  • 服务端签名源码
    uploadOssHelper.js代码如下:
    const crypto = require("crypto-js");
    
    class MpUploadOssHelper {
      constructor(options) {
        this.accessKeyId = options.accessKeyId;
        this.accessKeySecret = options.accessKeySecret;
        // 限制参数的生效时间,单位为小时,默认值为1。
        this.timeout = options.timeout || 1; 
        // 限制上传文件的大小,单位为MB,默认值为10。
        this.maxSize = options.maxSize || 10;
      }
    
      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 = Buffer.from(JSON.stringify(policyText));
        return buffer.toString("base64");
      }
    
      signature(policy) {
        return crypto.enc.Base64.stringify(
          crypto.HmacSHA1(policy, this.accessKeySecret)
        );
      }
    }
    
    module.exports = MpUploadOssHelper;
  • 服务端接口示例
    以Express为例,接口代码如下:
    const express = require("express");
    const app = express();
    const MpUploadOssHelper = require("./uploadOssHelper.js");
    
    app.get("/getPostObjectParams", (req, res) => {
      const mpHelper = new MpUploadOssHelper({
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        accessKeyId: "yourAccessKeyId",
        accessKeySecret: "yourAccessKeySecret",
        // 限制参数的生效时间,单位为小时,默认值为1。
        timeout: 1,
        // 限制上传文件大小,单位为MB,默认值为10。
        maxSize: 10,
      });
    
      // 生成参数。
      const params = mpHelper.createUploadParams();
    
      res.json(params);
    });

客户端签名

使用客户端签名时,您需要先在服务端搭建一个STS服务,然后由客户端获取STS临时授权账号并生成签名。
  1. 服务端搭建STS服务
    const STS = require("ali-oss").STS;
    const express = require("express");
    const app = express();
    
    const stsClient = new STS({
      // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
      accessKeyId: "yourAccessKeyId",
      accessKeySecret: "yourAccessKeySecret",
      // 填写Bucket名称。
      bucket: "YourBucketName",
    });
    
    async function getToken() {
      // 指定角色的ARN,格式为acs:ram::$accountID:role/$roleName。
      const STS_ROLE = "yourStsRole";
      const STSpolicy = {
        Statement: [
          {
            Action: ["oss:*"],
            Effect: "Allow",
            Resource: ["acs:oss:*:*:*"],
          },
        ],
        Version: "1",
      };
      const result = await stsClient.assumeRole(
        STS_ROLE,
        STSpolicy,
        3600 // STS过期时间,单位为秒。
      );
      const { credentials } = result;
    
      return credentials;
    }
    
    app.get("/getToken", async (req, res) => {
      // 获取STS。
      const credentials = await getToken();
      console.log(credentials.AccessKeyId);
      console.log(credentials.AccessKeySecret);
      console.log(credentials.SecurityToken);
      res.json(credentials);
    });
    说明 STSpolicy包含版本号(Version)和授权语句(Statement)。每条授权语句又包含授权效力(Effect)、操作(Action)、资源(Resource)。有关policy中各元素的定义及用法,请参见RAM Policy概述
  2. 客户端获取STS临时账号并生成签名
    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 = {
      expiration: date.toISOString(), // 设置policy过期时间。
      conditions: [
        // 限制上传大小。
        ["content-length-range", 0, 1024 * 1024 * 1024],
      ],
    };
    
    async function getFormDataParams() {
      const credentials = await axios.get('/getToken')
      const policy = Base64.encode(JSON.stringify(policyText)) // policy必须为base64的string。
      const signature = computeSignature(credentials.AccessKeySecret, policy)
      const formData = {
        OSSAccessKeyId: credentials.AccessKeyId,
        signature,
        policy,
        'x-oss-security-token': credentials.SecurityToken 
      }
      return formData
    }

步骤4:使用微信小程序上传

使用uploadFile接口上传文件,示例代码如下:
const host = '<host>';
const signature = '<signatureString>';
const ossAccessKeyId = '<accessKey>';
const policy = '<policyBase64Str>';
const key = '<object name>';
const securityToken = '<x-oss-security-token>'; 
const filePath = '<filePath>'; // 待上传文件的文件路径。
wx.uploadFile({
  url: host, // 开发者服务器的URL。
  filePath: filePath,
  name: 'file', // 必须填file。
  formData: {
    key,
    policy,
    OSSAccessKeyId: ossAccessKeyId,
    signature,
    // 'x-oss-security-token': securityToken // 使用STS签名时必传。
  },
  success: (res) => {
    if (res.statusCode === 204) {
      console.log('上传成功');
    }
  },
  fail: err => {
    console.log(err);
  }
});
参数 说明
host 填写Bucket的访问域名,例如https://examplebucket.oss-cn-zhangjiakou.aliyuncs.com。如果您的Bucket已绑定自定义域名,建议填写您的自定义域名。
signature 填写步骤3中获取到的signature信息。
ossAccessKeyId 填写您的AccessKey ID。如果您是通过STS获取的临时用户,则填写临时用户的AccessKey ID。
policy 填写步骤3中获取到的policy信息。
key 设置文件上传至OSS后的文件路径。例如,您需要将myphoto.jpg上传至test文件夹下,则填写test/myphoto.jpg
securityToken 如果使用STS认证,则填写步骤3中使用客户端签名获取到的SecurityToken。
filePath 填写待上传文件的本地完整路径,例如D:\example.txt

常见问题

  • iOS系统使用微信小程序直传后,返回的结果中,为什么ETag参数名称会变为Etag?

    问题原因:使用微信小程序直传后,OSS会返回标准的ETag。但是iOS系统的微信小程序会将返回的ETag转成了Etag,导致客户端调用ETag参数的业务出现问题。

    解决方案:若您的业务涉及多个系统的微信小程序直传场景,建议您将Header中的所有参数名都转成小写进行兼容。

  • 上传成功后,如何获取文件URL?

    具体操作,请参见如何获取单个或多个文件的URL?

  • PostObject请求时,报错The bucket POST must contain the specified 'key'. If it is specified, please check the order of the fields.

    问题原因:没有指定key表单域。

    解决方案:PostObject请求时必须指定key表单域。详情请参见PostObject

  • 微信小程序是否支持同时上传多个文件?

    不支持,单次调用PostObject接口仅支持上传一个文件。如果您希望通过微信小程序上传多个文件,请循环调用PostObject接口。

  • OSS SDK是否支持微信小程序直传?

    不支持。微信小程序直传基于微信小程序API实现,与OSS SDK无关。