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

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

在小程序开发就是带着脚镣跳舞。 平时用习惯了 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 文件问题, 解决了这些, 就问题不大了。 希望对开发小程序的朋友有所帮助 。

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