UNIAPP封装小程序直传阿里云对象存储使用stsToken

2,505 阅读3分钟

前言

沉寂了好久的我,又回来了,这次是关于小程序直传阿里云的问题。首先介绍一下项目背景,这个项目需要用到图片上传,正好前段时间对接了浏览器上传阿里云oss,使用stsToken(临时授权)。所以趁机也把小程序的给整了吧,同时也包含图片上传前添加水印文字。

参考文档

支付宝小程序直传实践

微信小程序直传实践 在找了好多文章之后,我发现还是官方文档更靠谱,了解的更透彻。

关于数据和依赖

我公司的oss使用模式:后端创建RAM角色,前端通过接口去获取某个RAM角色的安全令牌(stsToken).这个token是可以过期的,当过期的时候再次去获取一个stsToken,避免了一次获取永久使用的问题。

npm install crypto-js
npm install js-base64
// 在处理policy和signature 需要使用到

原文

  • upload.js

      import crypto from 'crypto-js'
      import { Base64 } from 'js-base64'
      import { getOss } from '@/utils/utils.js'
      // 随机字符串
      function randomString(num) {
          const chars = ['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']
          let res = ''
          for (let i = 0; i < num; i++) {
              let id = Math.ceil(Math.random() * 35)
              res += chars[id]
          }
          return res
      }
      // 计算签名。
      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]
          ]
      }
      export function uploadFile({filePath, extensionName, fileType}) {
          return new Promise(async(resolve, reject) => {
              let oss = await getOss()
              const url = 'https://xxxxxx.xxxx.aliyuncs.com/'
              const policy = Base64.encode(JSON.stringify(policyText)) // policy必须为base64的string。
              const signature = computeSignature(oss.credentials.accessKeySecret, policy)
              const fileName = oss.path + randomString(6) + +new Date() + extensionName // 路径+随机字符串+当前时间戳+文件格式名
              uni.uploadFile({
                  'url': url,
                  // #ifdef MP_WEIXIN
                  'name': 'file',
                  // #endif
                  // #ifdef MP-ALIPAY
                  'fileType': fileType, // 文件类型支持图片、视频、音频( image / video / audio)
                  'fileName': 'file',
                  // #endif
                  'filePath': filePath, // 待上传的文件路径
                  'formData': {
                      'key': fileName, // 设置文件上传至OSS后的文件路径
                      'OSSAccessKeyId': oss.credentials.accessKeyId,
                      signature,
                      policy,
                      success_action_status: '200', // 默认上传成功状态码为204,此处被success_action_status设置为200。
                      'x-oss-security-token': oss.credentials.securityToken // stsToken
                  },
                  'success': (res) => {
                      console.log(res.statusCode)
                      // 默认上传成功状态码为204,此处被success_action_status设置为200。
                      if (res.statusCode == 200) {
                          console.log(res, '上传成功')
                          resolve({
                              url: url + fileName
                          })
                      } else {
                          console.log(res, '上传else')
                          reject(res)
                      }
                  },
                  'fail': err => {
                      console.log(err, '上传失败')
                      reject(err)
                  }
              })
          })
      }
    
  • 图片上传前添加水印

    const addWaterMark = (that, ctx, filePath, height, width) => {
      return new Promise((resolve, reject) => {
        const padding = 200
        const rotate = 35
        const getRadian = (degree: number) => degree * Math.PI / 180
        ctx.drawImage(filePath, 0, 0, width, height)
        ctx.translate(width / 2 * Math.tan(getRadian(rotate)), -height / 2 * Math.tan(getRadian(rotate)))
        ctx.rotate(rotate * Math.PI / 180)
        // 将图片放到cancas内,宽高为图片大小
        ctx.setFontSize(16)
        ctx.setTextAlign('center')
        ctx.setFillStyle('#797878')
        let x = 20
        let y = 20
        let addingX = true
        const fillText = '20210305'
        while (y < height) {
          ctx.fillText(fillText, x, y)
          if (x > width) {
            x = 20
            addingX = false
          }
          if (addingX) {
            x += padding
          } else {
            y += padding
            addingX = true
          }
        }
        ctx.draw(false, () => {
          uni.canvasToTempFilePath({
            canvasId: ctx.canvasId,
            destWidth: width,
            destHeight: height,
            success: (res) => {
              let path = res.tempFilePath
              resolve(path)
            },
            fail: (err) => {
              reject(err)
            }
          }, that)
        })
      })
    }
    
    // upload.js
    export async function uploadWaterMarkImage ({ that, filePath, extensionName, ctx, height, width}) {
        ...
        let waterMarkFilePath = await addWaterMark(that, ctx, filePath, height, width)
        ...
    }
    

总结

摸鱼了好久,终于回来了,会继续更文,把自己用的到的东西放到上面来,当做一个经验总结。下一篇关于根据此方法封装的upload组件。

UNIAPP 封装upload组件(添加文字水印)直传OSS