更新时间:2024-10-17 GMT+08:00

小程序直传OBS

背景信息

微信小程序作为当下流行的移动应用,具有广泛的应用场景。如何通过微信小程序上传文件至对象存储服务OBS成为了一个热点问题,本文将通过一个示例程序进行演示。

注意事项

  • 客户端计算签名时依赖引用“crypto-js”及“js-base64”两个开源组件,因此需要在微信小程序项目中设置使用NPM模块。
  • 在微信小程序中进行编译时,如果在引入“crypto-js”包时出现“Maximum call stack size exceed”报错,请升级微信小程序开发客户端至最新版本。
  • 上传过程中返回405时,请检查指定的endpoint是否为对应上传桶的桶域名。

操作步骤

  1. 设置桶的跨域访问权限。

    微信小程序基于BrowerJS进行开发,受同源安全策略的要求,不同域间的网站脚本和内容如需交互,需要配置跨域资源共享(CORS)规范。华为云对象存储服务OBS支持CORS规范,允许跨域访问OBS中的资源,具体配置步骤请参见配置跨域资源共享

    CORS规则配置项建议:

    表1 CORS规则

    参数

    说明

    配置建议

    允许的来源

    必选参数,指定允许的跨域请求的来源,即允许来自该域名下的请求访问该桶。

    允许多条匹配规则,以回车换行为间隔。每个匹配规则允许使用最多一个“*”通配符。例如:

    http://rds.example.com
    https://*.vbs.example.com

    *

    允许的方法

    必选参数,指定允许的跨域请求方法,即桶和对象的几种操作类型。包括:Get、Post、Put、Delete、Head。

    全选

    允许的头域

    可选参数,指定允许的跨域请求的头域。只有匹配上允许的头域中的配置,才被视为是合法的CORS请求。

    允许的头域可设置多个,多个头域之间换行隔开,每行最多可填写一个*符号,不支持&、:、<、空格以及中文字符。

    *

    补充头域

    可选参数,指CORS响应中带的补充头域,给客户端提供额外的信息。

    补充头域可设置多个,多个头域之间换行隔开,不支持*、&、:、<、空格以及中文字符。

    • ETag
    • x-obs-request-id
    • x-obs-api
    • Content-Type
    • Content-Length
    • Cache-Control
    • Content-Disposition
    • Content-Encoding
    • Content-Language
    • Expires
    • x-obs-id-2
    • x-reserved-indicator
    • x-obs-version-id
    • x-obs-copy-source-version-id
    • x-obs-storage-class
    • x-obs-delete-marker
    • x-obs-expiration
    • x-obs-website-redirect-location
    • x-obs-restore
    • x-obs-version
    • x-obs-object-type
    • x-obs-next-append-position

    缓存时间

    必选参数,请求来源的客户端可以缓存的CORS响应时间,以秒为单位,默认为100秒。

    根据实际业务设置。

  2. 配置小程序上传域名白名单。

    微信小程序利用白名单机制管理跨域访问,想要实现数据上传,需要在微信小程序平台域名白名单中配置桶的访问域名。

    1. 获取桶的访问域名。

      在桶列表单击待操作的桶,进入对象页面后单击“概览”。在“域名信息”下查看桶的访问域名。

      图1 桶的访问域名

    2. 在微信小程序服务器域名配置中指定桶域名为合法域名。

  3. 计算POST上传签名。

    POST上传前需要根据上传时自定义使用的policy字段计算相关签名信息,签名计算规则请参见基于浏览器上传的表单中携带签名,计算签名相关源代码如下:

    对policy进行base64编码(GetPolicy.js):

    const Base64 = require('js-base64');
    
    function getPolicyEncode(policy) {
      // 传入表单上传的policy字段,对policy进行Base64编码
      const encodedPolicy = Base64.encode(JSON.stringify(policy));
      return encodedPolicy;
    }
    
    module.exports = getPolicyEncode;

    计算签名的源代码(GetSignature.js):

    const Crypto = require('crypto-js');
    const Base64 = require('js-base64');
    
    function getSignature(policyEncoded, SecretKey){
      // 利用SK对Base64编码后的policy结果进行HMAC-SHA1签名计算
      const bytes = Crypto.HmacSHA1(policyEncoded, SecretKey);
      // 对计算结果进行Base64编码,得到最终的签名信息
      const signature = Crypto.enc.Base64.stringify(bytes);
      return signature;
    }
    
    module.exports = getSignature;

  4. 使用小程序直传数据至对象存储桶中。

    基于3中得到的编码后的policy字段及signature字段,可以调用小程序中的上传接口,选择本地文件并上传。具体代码示例如下:

    配置AK、SK、访问域名等信息的配置文件(Configuration.js):

    • 使用永久访问秘钥(AK/SK)
      // 指定OBS服务相关信息:AK,SK,EndPoint
      var Configuration = {
        // 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全;本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量AccessKeyId和SecretKey。
        // 前端本身没有process对象,可以使用webpack类打包工具定义环境变量,就可以在代码中运行了。
        // 您可以登录访问管理控制台获取访问密钥AK/SK,获取方式请参见https://support.huaweicloud.com/intl/zh-cn/usermanual-ca/ca_01_0003.html
        AccessKeyId: process.env.AccessKeyID,
        SecretKey: process.env.SecretAccessKey,
        EndPoint: 'https://your-test-bucket.obs.myhuaweicloud.com',         //完整的桶访问域名
      };
      
      
      module.exports = Configuration;
    • 使用临时访问秘钥(AK/SK/securitytoken)
      获取临时AK/SK和securitytoken的方法,请参见获取临时AK/SK和securitytoken
      // 指定OBS服务相关信息:AK,SK,SecurityToken,EndPoint
      var Configuration = {
        // 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全;本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量AccessKeyId和SecretKey。
        // 前端本身没有process对象,可以使用webpack类打包工具定义环境变量,就可以在代码中运行了。
        // 您可以登录访问管理控制台获取访问密钥AK/SK,获取方式请参见https://support.huaweicloud.com/intl/zh-cn/usermanual-ca/ca_01_0003.html
        AccessKeyId: process.env.AccessKeyID,
        SecretKey: process.env.SecretAccessKey,
        SecurityToken: process.env.SecurityToken,        //securityToken
        EndPoint: 'https://your-test-bucket.obs.myhuaweicloud.com',         //完整的桶访问域名
      };
      
      
      module.exports = Configuration;

    配置文件中传入的EndPoint应该为完整的桶访问域名,例如:https://bucketName.obs.myhuaweicloud.com,其中bucketName即小程序上传的目标桶名。

    // 引入配置文件
    const config = require('./Configuration.js');
    // 引入policy编码计算方法
    const getPolicyEncode = require('./getPolicy.js');
    // 引入签名计算方法
    const getSignature = require('./GetSignature.js');
    
    const OBSupload = function (filePath){
      if(!filePath){
        wx.showToast({
          title: 'Invalid filePath',
          icon: 'Please re-select path',
        });
      }
      else{
        const fileName = 'testMiniprogram.jpg';   // 指定上传到OBS桶中的对象名    
    
        const OBSPolicy = {                   // 设定policy内容,policy规则定义可参考步骤3中的超链接签名计算规则文档
          "expiration": "2021-12-31T12:00:00.000Z",
          "conditions": [
            { "bucket": "your-test-bucket" },  // 桶名要和配置文件中endpoint中的桶名保持一致
            // { "x-obs-security-token": config.SecurityToken } // 如果是临时访问秘钥鉴权,必须设置该值
            { 'key': fileName }
          ]
        }
    
        const policyEncoded = getPolicyEncode(OBSPolicy);                    // 计算base64编码后的policy
        const signature = getSignature(policyEncoded, config.SecretKey);     // 计算signature
    
        wx.uploadFile({
          url: config.EndPoint,
          filePath: filePath,
          name: 'file',
          header: {
            'content-type': 'multipart/form-data; boundary=-9431149156168',
          },
          formData: {
            // 从配置文件中获取到的AK信息、计算得到的编码后policy及signature信息
            'AccessKeyID': config.AccessKeyId,
            'policy': policyEncoded,
            'signature': signature,
            'key': fileName,
            // "x-obs-security-token": config.SecurityToken, // 如果是临时访问秘钥鉴权,必须设置该值
          },
    
          success: function(res){
            console.log(res.statusCode);            //打印响应状态码
            if(res.statusCode=='204'){
              console.log('Uploaded successfully', res)
              wx.showToast({
                title: 'Uploaded successfully',
                icon: 'Success'
              });
            }
            else{
              console.log('Uploaded failed', res)
              wx.showToast({
                title: 'Uploaded failed',
                icon: 'Fail'
              });
            }
          },
          fail: function(e){
            console.log(e);
          }
        })
    
      }
    }
    
    module.exports = OBSupload;

相关操作

上传完成后,要获取对应对象的访问URL,请参见如何获取对象访问路径