这篇文章简单谈谈在 ActiveStorage 的加持下 OSS 服务的直连上传模式,依旧是使用阿里云的 OSS 作为例子,想必其他服务商的 OSS 也是大同小异。原文链接: https://step-by-step.tech/posts/oss-upload-directly-on-rails-service
其实 Rails 的 ActiveStorage 再搭配各种第三方的插件,已经让 Rails 跟各大厂商的 OSS 服务能够无缝衔接了。正常来说只要简单改改配置,就能够让自家的服务切换到别家的 OSS 服务商,且不用改动任何的业务代码。
而且在业务逻辑里面如果需要做文件上传,那么可以很简单地编写出以下接口
# frozen_string_literal: true
module Api
module V1
class UtilsController < ApplicationController
def file_uploads
raise Exceptions::InvalidParams, '非法参数' unless params[:file].is_a?(ActionDispatch::Http::UploadedFile)
blob = ActiveStorage::Blob.create_after_upload!(io: params[:file], filename: params[:file].original_filename) # 统一接口
json_response({
signed_id: blob.signed_id,
url: blob.service_url
}, :created)
end
end
end
end
这种做法的好处在于编码简单。不用理会太多加密解密的事情,客户端直接通过file
参数传输需要上传的文件即可。然而,所有文件上传都经过 Rails 服务接口,会有以下问题
这种场景下,直连上传功能就有用武之地了,相当于用户直接往 OSS 服务上送文件。完美解决前面提到的两个问题。
PS:直连上传的前提是 OSS 服务处于私有读写/只读的状态下。直接把 OSS 服务弄成公共读写也能很简单地实现直连上传,然而安全性就有待考量了,这篇文章便失去了意义。
这里依旧使用阿里云的 OSS 服务来作为教材。既然要对私有化的 OSS 服务做直连上传,那么就必须要附带签名信息才行。否则的话上传会出现问题
可见,直接上传的话会受到权限的制约。要如何解决这个问题呢?ActiveStorage 做了个不错的解决方案,当然还是要依赖适配器。在 Rails 搭配 ActiveStorage 的场景下每个ActiveStorage::Blob
资源其实会对应 OSS 上的一个资源坑位,我们只要往这个坑位上上传文件即可。可以分几步走
占坑位其实很简单,创建一个 ActiveStorage::Blob 资源就好了,假设我们要上传一个 mp4 文件,大小
> ls -la example.png
-rw-r--r--@ 1 lan staff 131267 Jul 15 11:00 example.png
大概是 131267 个字节。对其 md5 并进行 base64 编码后可得到
> openssl dgst -md5 -binary ~/Desktop/example.png | base64
ZNRYpBB1Wz20ix5jwjFq3Q==
这两个都是占取坑位的重要参数,应该在进行直连上传之前通过客户端软件计算得出,并用以下的方式来占取坑位
blob = ActiveStorage::Blob.create(filename: 'example.png', byte_size: 131267, checksum: 'ZNRYpBB1Wz20ix5jwjFq3Q==', content_type: 'image/png')
接下来就可以利用ActiveStorage::Blob
的特性,获取到往该坑位推送资源的必要参数了
获取上传链接
> blob.service_url_for_direct_upload
=> "https://huiliu-staging.oss-cn-beijing.aliyuncs.com/resources/3bdec2dqnt5altpsjj4es5fxx9we"
获取上传所需要的头部信息
> blob.service_headers_for_direct_upload
=> {"Content-Type"=>"image/png", "Content-MD5"=>"ZNRYpBB1Wz20ix5jwjFq3Q==", "Authorization"=>"OSS LTAI4G6YWffqEULUZEjFVHMM:tPA0XdgC1xnJIJa0s4pvTNKZOzw=", "x-oss-date"=>"Fri, 22 Jul 2022 23:11:13 GMT"}
头部信息只在一定的时间内有效,因此可以在一定程度上保证上传的安全性。接着便可以利用上述信息往对应的坑位推送资源了,我且在 Postman 上完成这个过程
最后点击发送即返回 200 响应
我们可以通过service_url
来获取对应的访问链接,并进行访问
> blob.service_url
=> "https://huiliu-staging-resources.huiliu.net/resources/3bdec2dqnt5altpsjj4es5fxx9we"
把上面的概念应用到实际业务中,一般是要分几步走
Rails 服务用来占取坑位的接口大概长下面这个样子。
module Api
module V1
class UtilsController < ApplicationController
def upload_directly_meta_information
blob = ActiveStorage::Blob.create(filename: params[:filename].to_s, byte_size: params[:byte_size], checksum: params[:checksum].to_s, content_type: params[:content_type].to_s)
json_response({
url: blob.service_url_for_direct_upload,
headers: blob.service_headers_for_direct_upload,
signed_id: blob.signed_id
})
end
end
end
end
客户端只要用相应的元信息调用接口之后便能得到对应的上传连接url
,以及上传所需的头部信息headers
。这里还会返回signed_id
,主要是为了上传成功之后,客户端可以利用这个signed_id
把资源与某个数据表通过ActiveStorage::Attachment
进行关联。
这篇文章简单总结了在 Rails 搭配 ActiveStorage 使用的场景下要如何进行直连文件上传。一般要分几步走,这种做法虽然繁琐些但性能较好,且不会给自家服务带来太大的压力,针对大文件上传以及频次较高的文件上传等场景十分有效。