22

在我详细介绍之前,我要直截了当:有没有人想出一种方法让 Carrierwave 将文件保存为时间戳或每个文件唯一的任意字符串?

默认情况下,Carrierwave 将每个文件及其替代版本保存在其自己的目录中(以型号 ID 号命名)。我不喜欢这个,因为不是一个有 1,000 个的目录,而是为了使用大整数,文件(在我的例子中是图片)在其中我们得到一个有 1,000 个子目录的目录,每个子目录有一个或两个文件。呸。

现在,当您将 Uploader 的store_dir方法覆盖为如下所示时:

def store_dir
  "uploads/#{model.class.to_s.underscore}/#{mounted_as}"
end

你最终得到了我想要的确切行为。所有文件(图片)都进入一个快乐的大文件夹。当对象被删除时,没有更多的子文件夹存在。

只有一个问题。文件冲突。如果你上传了两次delicious_cake.jpg,第二个将覆盖第一个,即使它们是两张不同的美味蛋糕图片!这就是为什么该store_dir方法在/#{model.id}它返回的值的末尾加上了额外的原因。

那么该怎么办?在阅读了一下之后,我发现在生成的上传文件中有一个明显的解决方案被注释掉了。

# Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
# def filename
#   "something.jpg" if original_filename
# end

经过一番搜索,我发现有人做了以下事情

def filename
  @name ||= "#{secure_token}.#{file.extension}" if original_filename
end

这让我想,为什么不这样做

def filename
  @name ||= "#{(Time.now.to_i.to_s + Time.now.usec.to_s).ljust(16, '0')}#{File.extname(original_filename)}"
end

就在那时,事情变得非常糟糕。这样做的问题是,filename显然每个版本的文件都会被调用,所以我们最终会得到像 1312335603175322.jpg 和 thumb_1312335603195323.jpg 这样的文件名。注意到细微的差别了吗?每个文件名都基于filename该特定版本的调用时间。那根本行不通。

我接下来厌倦了使用model.created_at时间戳的基础。只有一个问题,第一个版本返回 nil,因为它还没有被放入数据库。

经过进一步思考,我决定在我的图片控制器中尝试以下操作。

def create
  if params[:picture] and params[:picture][:image]
    params[:picture][:image].original_filename = "#{(Time.now.to_i.to_s + Time.now.usec.to_s).ljust(16, '0')}#{File.extname(params[:picture][:image].original_filename)}"
  end
  ...

这会在 Carrierwave 到达之前覆盖 original_filename 属性,使其成为时间戳。它正是我想要的。文件的原始版本以 1312332906940106.jpg 之类的名称结束,而缩略图版本(或任何其他版本)以 thumb_1312332906940106.jpg 之类的名称结束。

但是,这似乎是一个可怕的黑客攻击。这应该是模型的一部分,或者更好的是安装在模型上的上传器的一部分。

所以,我的问题是,有没有更好的方法来实现这一目标?我是否错过了一些重要的 Carrierwave 使这变得容易?有没有一种不那么明显但更清洁的方式来解决这个问题?工作代码很好,但不难闻的工作代码更好。

4

3 回答 3

21

您可以在uploader文件中执行类似的操作,它也适用于版本化文件(即,如果您有一个图像,然后创建同一文件的 3 个其他缩略图版本,它们都将具有相同的名称,只是附加了大小信息名字上):

  # Set the filename for versioned files
  def filename
    random_token = Digest::SHA2.hexdigest("#{Time.now.utc}--#{model.id.to_s}").first(20)
    ivar = "@#{mounted_as}_secure_token"    
    token = model.instance_variable_get(ivar)
    token ||= model.instance_variable_set(ivar, random_token)
    "#{model.id}_#{token}.jpg" if original_filename
  end

这将创建一个像这样的文件名,例如:76_a9snx8b81js8kx81kx92.jpg其中 76 是模型的 id,另一位是随机 SHA 十六进制。

于 2011-08-03T06:05:04.427 回答
3

还检查现在可用的carrierwave wiki的解决方案https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Use-a-timestamp-in-file-names

您可以在覆盖文件名的文件名中包含时间戳,您可以在 Carrierwave 文档中阅读:

 class PhotoUploader < CarrierWave::Uploader::Base
     def filename
       @name ||= "#{timestamp}-#{super}" if original_filename.present? and 
       super.present?
    end

   def timestamp
     var = :"@#{mounted_as}_timestamp"
     model.instance_variable_get(var) or model.instance_variable_set(var, Time.now.to_i)
   end
 end

不要忘记将结果存储在实例变量中,否则您可能会将不同的时间戳写入数据库和文件存储。

于 2017-07-25T07:13:17.597 回答
0

解决方法和官方文档中描述的一样

但它总是返回original_filenamenil. 所以只需将其更改为实例变量@original_filename.present?

于 2020-02-18T09:16:15.847 回答