今日在 收看 driftingruby.com Purge Orphaned Active Storage Records | Drifting Ruby 的时候,觉得这个思路有点意思,故总结了下大概的思路
(drifting ruby 是一个不错的 ruby on rails 的视频教程网站,每月订阅费为 15usd,各位老哥可以每个月花一顿请客吃饭的钱学习下 ruby 的一些知识。 这是我的订阅推广链接 https://www.driftingruby.com/subscription/new?referral=4ce28682160093d18c0480e9571ef504 ,有兴趣的朋友可以订阅)
在 active storage 的上传流程
active storage 的 direct upload 上传的步骤
/rails/active_storage/direct_uploads
拿到 传输的 blob 信息当我们默认使用 direct_upload: true 的方式启用 直传的时候,rails 会拦截 默认的 form 提交,改为:完成 blob 创建,文件上传,再将 blob id 插入到 form 中,再次提交,服务器会再新建一个 attachment 用来标记存储上传完毕的文件,以及存储 attachment 与 model 的对应关系。这些动作是一气呵成的,在外界看来,就像是自己提交了一个 form,没什么 ui 级别的区别。
但有两种情况比较尴尬,文件会上传成功,但是因为种种原因,没有生成 attachment 记录:
当这种情况发生时,我们就会有一个我们永远不会调用的文件存储了,如果是类似使用了 OSS 啥的文件存储服务,就会产生 没有价值的 oss 消耗。
那么如何删除这些内容呢?
有两种思路,任意其中一种即可:
这个很简单,只需要定时查询出没有关联的 blob,删除即可。 注意,这里有个时间的筛选,是因为有时候存在这样的情况:用户已经上传完毕文件,但是正在编辑 form 的途中的(特别是我说的方式 2,使用 js DirectUpload),所以要避免删除特别新的 blob
比如每天 凌晨
ActiveStorage::Blob.where('id not in (select blob_id from active_storage_attachments)')
.where('created_at < ?', 10.minutes.ago)
.map(&:purge_later)
总体思路是 扩展一下 blob, 当 blob 建立后,加入一个 background job,用来检测在一段时间后是否有对应的 attachment,若是没有,则自行删除
那么 如何改造 blob 让其有这种能力呢?
第一种,猴子补丁,直接真刀真枪的复写 ActiveStorage::Blob 文件即可
第二种,利用 Rails.configuration.to_prepare
在 initializer 中,委婉的打个补丁
第一种简单暴力,第二种相对优雅
第二种方式的核心代码是
# config/initializers/blob_include.rb
Rails.configuration.to_prepare do
ActiveStorage::Blob.include ActiveStorage::BlobExtension
end
之后 利用 ActiveSupport::Concern
的 included 加入一个 after_create 到 blob 对象中。
核心思路已经说清楚了,再具体的代码就不赘述了,有兴趣的同学可以订阅 driftingruby.com, 15usd 买不了吃亏买不了上当。
Rails.configuration.to_prepare
方法