Ruby 微信小程序上传使用 active_storage 的 direct upload

jicheng1014 · 2021年01月28日 · 750 次阅读

在小程序开发就是带着脚镣跳舞。平时用习惯了 activestorage, 今日想在小程序上上传照片也接入 active storage,发现还是有一些有意思。

之所以使用 direct upload 原因很简单:不花服务器带宽,增加体验。那么传统的 direct upload 需要几步呢?

active storage 的 direct upload 上传的步骤

  1. 拿到图片大小,type,checksum, type,filename
  2. 向服务器 post 请求 /rails/active_storage/direct_uploads 拿到 传输的 blob 信息
  3. 拿到 blob 的返回值里的 direct_upload 来传文件
  4. 将 blob 里的 signed_id 加入到 form 中

那么就在小程序中一一解决这些问题就好了

拿到图片大小,type,checksum

小程序无法操作 window 对象以及使用标准的 File 对象,所以,无法直接使用 activestorage npm 包,拿到图片的大小,type,filename 咱瞎编一个,都好办,难点在于 checksum.

checksum 本来是在file_checksum.js中得出的,原理大概是 用 spark-md5 读出文件的 md5, 再 base64 下 MD5,得到最终的 checksum。

小程序中有一个 wx.getFileInfo, 可以拿到 文件 的 md5 值,这样就不用使用标准 File 读文件算 md5,但是因为小程序没有 window.btoa 方法,那么实现这个方法即可,后来发现 npm 包 crypto-js 即可实现

默认情况下,小程序没打开 npm 构建,需要操作一下

在小程序根目录执行 npm init, 之后再在微信开发程序面板右侧点击详情,在本地设置里打开“使用 npm 模块”,之后执行 yarn add cryto-js,最后在微信开发程序的菜单栏里选择 工具,构建 npm,这样就可以 import 了

const fileInfo = await wx.getFileInfo({
  filePath: file.path,
})
const checksum = crypto.enc.Base64.stringify(crypto.enc.Hex.parse(fileInfo.digest));


至于 content type,则在 wx.getImageInfo 返回值里的 type 体现,比如 jpg 比如 png,需要自行拼接一下

const info = await wx.getImageInfo({
  src: file.path
})

至此,最麻烦的步骤一 checked

向服务器 post 请求 /rails/active_storage/direct_uploads 拿到 传输的 blob 信息

这里比较简单,主要需要解决的是 csrf 的问题,我是为了自己以后扩展方便,重新继承了之前的 direct_upload controller, 顺便跳过了 csrf

class Api::V1::DirectUploadsController < ActiveStorage::DirectUploadsController
  # Should only allow null_session in API context, so request is JSON format
  protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }

  # Also, since authenticity verification by cookie is disabled, you should implement you own logic :
  before_action :verify_user

  private

  def verify_user
      .....授权
  end
end

步骤 2 checked

拿到 blob 的返回值里的 direct_upload 来传文件

这里有个小程序的坑,小程序官方提供的 uploadFile 是确定了 method 必须为 post 和 data 格式,由于我的项目使用了 阿里云的 oss 存储,是用的 put 方法,所以没办法直接用 uploadFile,而是要靠 wx.request 来解决问题了

其实也不难,从 blob 返回拿好数据后,将 file 打到 body 中即可。那么如何将 file 打到 body 呢?需要使用 wx.getFileSystemManager().readFile

他的结果里包含了 file 的内容,具体代码为

wx.getFileSystemManager().readFile({
      filePath: file.path,
      success: (e) => {
        wx.request({
          url: resp.data.direct_upload.url,
          method: 'put',
          header: resp.data.direct_upload.headers,
          data: e.data,
          success: (xxx) => {console.log(xxx)}
        });
      }
})

至此,上传文件 ok

将 blob 里的 signed_id 加入到 form 中

这个没啥 正常 request 处理即可

至此,整个流程都可以跑通了。

总结

难点在于 计算 checksum 和 解决 put 文件问题,解决了这些,就问题不大了。希望对开发小程序的朋友有所帮助。

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请 注册新账号