在上传到 Cloudinary 后对图像进行 post 处理时,有没有办法解决 ActiveStorage 完整性错误?

Is there a way to solve an ActiveStorage integrity error when doing post processing on an image after upload to Cloudinary?

我目前正在使用 Rails (6.0.3.2) 开发 Rails 应用程序。我正在使用 ActiveStorage 和 Cloudinary gem 将照片上传到 Cloudinary。

正常上传照片一切正常。但是,当我在 Cloudinary 上设置规则以在上传时调整照片大小时,我的日志最终充满了以下错误。具体来说,照片被调整大小,然后保存到云存储,替换原来的,而不是简单地创建一个变体。我相信这就是问题所在。

2020-10-09T04:47:28.917859+00:00 app[web.1]: I, [2020-10-09T04:47:28.917748 #9]  INFO -- : 
[ActiveJob] Enqueued ActiveStorage::AnalyzeJob (Job ID: 09e91b28-ee03-48e8-b04f-7120a06910b8)
 to Async(active_storage_analysis) at 2020-10-09 04:47:46 UTC with arguments: #
<GlobalID:0x000055a832b56848 @uri=#<URI::GID gid://trace-taiwan/ActiveStorage::Blob/268>>

2020-10-09T04:47:28.918128+00:00 app[web.1]: I, [2020-10-09T04:47:28.918053 #9]  INFO -- : 
Retrying ActiveStorage::AnalyzeJob in 18 seconds, due to a ActiveStorage::IntegrityError.

这会在 18 秒后重复,然后是 80 秒,然后是 256 秒,依此类推。除了这个错误之外,实际上一切正常。这些照片在开发和生产过程中都通过应用程序上传、调整大小和显示,但我不希望这个过程 运行 在每次上传时都像这样不断失败。同样,如果我不调整照片大小,这就会消失,但是考虑到我将允许用户上传的照片数量和我有限的存储空间,这对我来说不是一个好选择。

有什么方法可以让 ActiveStorage 知道照片已经并且应该已经调整大小,或者有什么方法可以立即或根本不进行此检查运行?

因此,在广泛搜索之后,用于创建完整性检查校验和的部分信息是图像的 'byte-size'。

校验和首先在将图像传递给 Cloudinary 之前创建,然后在成功保存之后创建。由于在 Cloudinary 上对传入图像进行服务端图像处理会在保存它们之前调整它们的大小,因此确实没有办法做到这一点。

作为解决方法,我创建了一个后台作业,我可以在创建 post 后调用它。

require 'tmpdir'
require 'fileutils'
require 'open-uri'
class ResizeImagesJob < ApplicationJob
  queue_as :default

  def perform(post)
    post.images.each do |image|
      blob = image.blob
      blob.open do |temp_file|
        path = temp_file.path
        pipeline = ImageProcessing::MiniMagick.source(path)
        .resize_to_limit(1200, 1200)
        .call(destination: path)
        new_data = File.binread(path)
        post.send(:images).attach io: StringIO.new(new_data), filename: blob.filename.to_s, 
                           content_type: 'image'
      end
      image.purge_later
    end
  end
end

在保存带有图像的 post 后,我这样调用作业

ResizeImagesJob.set(wait: 2.seconds).perform_later(@post)

它所做的是像往常一样将照片上传到 Cloudinary,因此用户不会注意到额外的等待,就像我自己调整它们的大小一样。在我更改照片之前等待 2 秒以允许进行完整性检查。然后 re-downloads 来自 Cloudinary 的照片,re-sizes 它们,将新调整大小的照片附加到 post,并删除旧照片,全部在后台。

显然,这并不理想,但它确实有效。非常高兴听到任何更好的解决方案。

我在 before_save 钩子中使用以下代码

class Thing < ApplicationRecord
  has_many_attached :pictures

  before_save :resize_pictures

  private

  # Note: AbcSize: 21.47
  def resize_pictures
    # Loop through attached files
    pictures.each_index do |index|
      # Ignore previously saved attachments
      next if pictures[index].persisted?

      # The real uploaded file (in my case, a Rack::Test::UploadedFile)
      # This is an undocumented Rails API, so it may change or break in future updates
      file = attachment_changes['pictures'].attachables[index]

      # Resize and override original file
      processed = ImageProcessing::Vips.source(file.path)
                                       .resize_to_limit!(1024, 768)
      FileUtils.mv processed, file.path

      # Update the ActiveStorage::Attachment's checksum and other data.
      # Check ActiveStorage::Blob#unfurl
      pictures[index].unfurl processed
    end
  end
end

它有效但对我来说感觉有点老套,因为使用了未记录的 Rails 方法,并且假设 attachment_changes['pictures'].attachables 顺序与 pictures 完全相同。