4

我在 Rails 4 项目中使用carrierwave,文件存储用于开发和测试,雾存储(用于存储在Amazon S3 上)用于生产。

我想用这样的路径保存我的文件:

/model_class_name/part_of_hash/another_part_of_hash/hash-model_id.file_extension

(示例:/images/12/34/1234567-89.png其中 1234567 是文件内容的 SHA1 哈希值,89 是数据库中关联图像模型的 id)。

到目前为止,我尝试的是:

class MyUploader < CarrierWave::Uploader::Base

  def store_dir
    "#{model.class.name.underscore}/#{sha1_for(file)[0..1]}/#{sha1_for(file)[2..3]}"
  end

  def filename
    "#{sha1_for(file)}-#{model.id}.#{file.extension}" if original_file
  end

  private

    def sha1_for file
      Digest::SHA1.hexdigest file.read
    end

end

这不起作用,因为:

  • model.idfilename调用时不可用
  • filestore_dir调用时并不总是可用

所以,来回答我的问题

  • 是否可以在其中使用模型 ID/属性filename这个链接说不应该这样做;有没有办法解决它?
  • 是否可以在其中使用文件内容/属性store_dir?我没有找到这方面的文档,但到目前为止我的经验说“不”(见上文)。
  • 您将如何实现文件/目录命名以尽可能接近我在开头概述的内容?
4

3 回答 3

5
  • 在创建时在文件名中包含 id 可能是不可能的,因为文件名存储在数据库中,但 id 尚不可用。一个(诚然相当极端的)解决方法是在创建时使用临时值,然后after_commit on: :create移动文件并更改数据库中的名称。可以用 优化它after_create,但我会留给你。(是carrierwave实际上传文件的地方。)

  • 直接在 中包含文件属性store_dir是不可能的,因为store_dir用于计算url-<code>url 需要知道 sha1,这需要访问文件,这需要知道 url 等。解决方法很明显:在模型的数据库记录中缓存您感兴趣的属性(在本例中为 sha1),并在store_dir.

  • id-in-filename 方法的更简单的变体是使用一些其他值,例如 uuid,并将该值存储在数据库中。这里有一些说明。

于 2013-08-27T14:17:43.860 回答
2

Taavo 的回答严格回答了我的问题。但我想快速详细说明我实施的最终解决方案,因为它也可能对其他人有所帮助......

我放弃了在文件名中使用模型 id 并用随机字符串替换它的想法(文件名中模型 id 的整个想法是确保与不同模型关联的 2 个相同文件最终具有不同的文件名; 并且一些随机字符也确保了这一点)。

所以我最终得到了像filehash-randomstring.extension.

由于carrierwave将文件名保存在模型中,我意识到我已经在模型中拥有了可用的文件哈希(以文件名的第一部分的形式)。所以我只是用它store_dir来生成表单中的路径model_class_name/file_hash_part/another_file_hash_part

我的最终实现如下所示:

class MyUploader < Carrierwave::Uploader::Base

  def store_dir

    # file name saved on the model. It is in the form:
    # filehash-randomstring.extension, see below...
    filename = model.send(:"#{mounted_as}_identifier")

    "#{model.class.name.underscore}/#{filename[0..1]}/#{filename[3..4]}"
  end

  def filename
    if original_filename

      existing = model.send(:"#{mounted_as}_identifier")

      # reuse the existing file name from the model if present.
      # otherwise, generate a new one (and cache it in an instance variable)
      @generated_filename ||= if existing.present?
        existing
      else
        "#{sha1_for file}-#{SecureRandom.hex(4)}.#{file.extension}"
      end

    end
  end

  private

    def sha1_for file
      Digest::SHA1.hexdigest file.read
    end

end
于 2013-08-28T12:04:20.057 回答
2

我最近遇到了同样的问题model.id,在创建记录时将文件名存储在数据库中时还不可用uploader。我找到了这个解决方法。我不确定它是否尊重 RESTful 原则,我愿意接受建议。

我修改了控制器,以便在创建图像后update_attributes立即执行 an ,以便将包含现有model.id值的文件名保存在数据库中。

  def create
    @uploader = Uploader.new(uploader_params)
    if @uploader.save
      if @uploader.update_attributes(uploader_params)
          render json: @uploader, status: :created 
      end
    else
      render json: @uploader.errors, status: :unprocessable_entity
    end
  end
于 2015-07-03T15:12:29.307 回答