Carrierwave 预先计算文件的 md5 校验和作为文件名

发布于 2024-12-11 09:12:53 字数 2898 浏览 0 评论 0 原文

使用载波上传器进行图像,尝试使用 md5 校验和作为文件名来提供上传图像的唯一性,

看起来我正在做一些错误的事情

模型定义如下:

class Image < ActiveRecord::Base
  attr_accessible :description, :img
  mount_uploader :img, ImageUploader

我的上传器代码如下:

class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file

def store_dir
  "images/#{filename[0,2]}"
end

def md5
  @md5 ||= ::Digest::MD5.file(current_path).hexdigest
end

def filename
  @name ||= "#{md5}#{::File.extname(current_path)}" if super
end

首先,我怀疑这种方法会造成校验和的计算每次查询图片条目进行

第二次显示,保存图片条目后,每隔一个 img.original_filename img.filename img.path img.current_path 似乎未定义,并出现以下错误:

You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
app/uploaders/image_uploader.rb:17:in `store_dir'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:43:in `store_path'
carrierwave (0.5.7) lib/carrierwave/storage/file.rb:41:in `retrieve!'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:95:in `block in retrieve_from_store!'
carrierwave (0.5.7) lib/carrierwave/uploader/callbacks.rb:17:in `with_callbacks'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:94:in `retrieve_from_store!'
carrierwave (0.5.7) lib/carrierwave/mount.rb:311:in `uploader'

感谢任何类型的帮助或提示

UPD:

以这种方式更改了上传者:

def store_dir
  "images/#{model.img_identifier[0,2]}"
end

def filename
  @name ||= "#{md5}#{::File.extname(current_path)}"
end

protected
def md5
  var = :"@#{mounted_as}_md5"
  model.instance_variable_get(var) or model.instance_variable_set(var, ::Digest::MD5.file(current_path).hexdigest)
end

current_path 似乎指的是表单提交的临时文件的完整路径,因此对于扩展名提取和摘要计算有效 img_identifier 代表持久化结果文件名,因此对于我们的 store_dir 的前缀提取有效 仍然不确定这种方法是否会引起任何警告,

也仍然不相信应该执行文件唯一性验证的方式

UPD:

我在我的模型中添加了这个before_validation回调类:

validates_uniqueness_of :checksum
before_validation :assign_checksum

def assign_checksum
  self.checksum = img.md5 if img.present? and img_changed?
end

其中 checksum 是我模型的数据库表中的一个单独的字符串字段
相当多余,因为它一般会重复img字段,但我仍然无法找出验证img本身唯一性的方法。

UPD:

通过这种方式摆脱了数据库冗余。在我的模型中:

validate :img_uniqueness

def img_uniqueness
  errors.add :img, "Image already exists in database" if Image.where(:img => self.img.filename).first
end

现在不需要 checksum 字段

Using carrierwave uploader for images, trying to provide uniqueness of uploaded images using md5 checksum as filename

looks like I'm doing something wrong

model is defined like:

class Image < ActiveRecord::Base
  attr_accessible :description, :img
  mount_uploader :img, ImageUploader

My uploader code is as following:

class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file

def store_dir
  "images/#{filename[0,2]}"
end

def md5
  @md5 ||= ::Digest::MD5.file(current_path).hexdigest
end

def filename
  @name ||= "#{md5}#{::File.extname(current_path)}" if super
end

first of all, I suspect this approach inflicts calculation of checksum each time image entry is queried to display

secondly, after image entry is saved, every other of img.original_filename img.filename img.path img.current_path seem to be undefined with following error:

You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
app/uploaders/image_uploader.rb:17:in `store_dir'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:43:in `store_path'
carrierwave (0.5.7) lib/carrierwave/storage/file.rb:41:in `retrieve!'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:95:in `block in retrieve_from_store!'
carrierwave (0.5.7) lib/carrierwave/uploader/callbacks.rb:17:in `with_callbacks'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:94:in `retrieve_from_store!'
carrierwave (0.5.7) lib/carrierwave/mount.rb:311:in `uploader'

any kind of help or tip is appreciated

UPD:

changed uploader this way:

def store_dir
  "images/#{model.img_identifier[0,2]}"
end

def filename
  @name ||= "#{md5}#{::File.extname(current_path)}"
end

protected
def md5
  var = :"@#{mounted_as}_md5"
  model.instance_variable_get(var) or model.instance_variable_set(var, ::Digest::MD5.file(current_path).hexdigest)
end

current_path seems to refer to full path of form-submitted tempfile, thus being valid for extension extraction and digest calculation
img_identifier stands for persisting resulting filename and thus goes valid for prefix extraction for our store_dir
still not sure if any caveat is induced with this approach

also still not convinced about the way file uniqueness validation should be performed

UPD:

I've added this before_validation callback in my model class:

validates_uniqueness_of :checksum
before_validation :assign_checksum

def assign_checksum
  self.checksum = img.md5 if img.present? and img_changed?
end

where checksum is a separate string field in my model's db table
it is quite redundant as it duplicates the img field in general, but I still can't figure out the way to validate uniqueness of img itself.

UPD:

Moved away from db redundancy this way. In my model:

validate :img_uniqueness

def img_uniqueness
  errors.add :img, "Image already exists in database" if Image.where(:img => self.img.filename).first
end

now there's no need in checksum field

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

但可醉心 2024-12-18 09:12:53

将 md5hash 字段添加到您的模型中,然后将以下代码添加到您的模型中:

  before_validation :compute_hash
  validates_uniqueness_of :md5hash, :on => :create

  def compute_hash
    self.md5hash = Digest::MD5.hexdigest(self.file.read)
  end

这应该可以解决问题。

Add a md5hash field to your model, then add the following code to your Model:

  before_validation :compute_hash
  validates_uniqueness_of :md5hash, :on => :create

  def compute_hash
    self.md5hash = Digest::MD5.hexdigest(self.file.read)
  end

That should do the trick.

爱情眠于流年 2024-12-18 09:12:53

1.当您定义 store_dir .. filename 似乎是 NIL!!

这似乎是立即错误 - 尝试使用 puts 语句打印出来文件名设置为什么..

如果文件名是 NIL,您将看到所看到的错误:

You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
app/uploaders/image_uploader.rb:17:in `store_dir'

注意:

您正在覆盖两者文件名和 store_dir ...并在其中使用文件名store_dir 的定义...
这里可能存在“先有鸡还是先有蛋”的问题。更好地检查

store_dir 应该只是一个目录,例如 /somewhere/on/your/disk/images

filename 应该只是一个不带路径的文件名,例如 24371592d9ea16625854ed68ac4b5846 ,或者24371592d9ea16625854ed68ac4b5846.jpg

例如检查这两个在 store.rb 的代码中如何使用(在下面的末尾)

问题:

使用filename[0,2] - 您正在使用带有 MD5-sum 2 字母前缀的目录来存储图像?

2.旁注:什么是 current_path? 似乎用词不当。应该是路径+文件名,而不仅仅是路径

3。检查 store.rb 中的第二个代码片段(如下)

似乎您将 store_dir 设置为相对目录 - 这是非常脆弱且容易出错的..将其设置为绝对路径(以“/”开头)

4可能是一个更好的主意。尝试将 filename 和 store_dir 设置为常量以进行调试

就像健全性检查一样,当您执行此操作时它是否有效:

def store_dir
   '/tmp/'      # from the source code, it looks like this needs to start with '/' !!
end

def filename
   'my_uploaded_file'
end

在将 filename 设置为 MD5 sum 之前应该有效。


来自源代码: (0.5.7)

lib/rierwave/storage/file.rb

lib/rierwave/uploader/store.rb

您可以覆盖 CarrierWave::Uploader::Store#filename 以指向您选择的文件名(请参阅源代码):

在 CarrierWave::Uploader::Base 中覆盖它应该可以工作,因为 'Store' 包含在 'Base' 中;这会覆盖默认文件名。

Fromstore.rb

  # Override this in your Uploader to change the filename.                                                                                                                                                                                                                   
  #                                                                                                                                                                                                                                                                          
  # Be careful using record ids as filenames. If the filename is stored in the database                                                                                                                                                                                      
  # the record id will be nil when the filename is set. Don't use record ids unless you                                                                                                                                                                                      
  # understand this limitation.                                                                                                                                                                                                                                              
  #                                                                                                                                                                                                                                                                          
  # Do not use the version_name in the filename, as it will prevent versions from being                                                                                                                                                                                      
  # loaded correctly.                                                                                                                                                                                                                                                        
  #                                                                                                                                                                                                                                                                          
  # === Returns                                                                                                                                                                                                                                                              
  #                                                                                                                                                                                                                                                                          
  # [String] a filename                                                                                                                                                                                                                                                      
  #                                                                                                                                                                                                                                                                          
  def filename
    @filename
  end

您还可以检查缓存的文件名,计算 MD5 和(或更好的 SHA1 和),然后使用结果来命名文件。


来自store.rb

  # Calculates the path where the file should be stored. If +for_file+ is given, it will be                                                                                                                                                                                 
  # used as the filename, otherwise +CarrierWave::Uploader#filename+ is assumed.                                                                                                                                                                                            
  #                                                                                                                                                                                                                                                                         
  # === Parameters                                                                                                                                                                                                                                                          
  #                                                                                                                                                                                                                                                                         
  # [for_file (String)] name of the file <optional>                                                                                                                                                                                                                         
  #                                                                                                                                                                                                                                                                         
  # === Returns                                                                                                                                                                                                                                                             
  #                                                                                                                                                                                                                                                                         
  # [String] the store path                                                                                                                                                                                                                                                 
  #                                                                                                                                                                                                                                                                         
  def store_path(for_file=filename)   # DEFAULT argument is filename - you don't need to override both
    File.join([store_dir, full_filename(for_file)].compact)
  end

文件相等:

files_equal?( filename1, filename2 )

      return true if File.size(filename1) == File.size(filename2)

      # return MD5(filename1) == MD5(filename2)

      # as we assume that the filename == MD5 + some suffix :

      return File.basename(filename1) == File.basename(filename2)   # if names == MD5 are the same

end

1. When you define store_dir .. filename seems to be NIL!!

That seems to be the immediate error -- try a puts statement to print out what filename is set to..

If the filename is NIL, you'll see the error you're seeing:

You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
app/uploaders/image_uploader.rb:17:in `store_dir'

NOTE:

you're overriding both filename and store_dir ... and using filename inside the definition of store_dir...
There could be a "chicken and the egg" type of problem here.. better check on that

store_dir should just be a directory, e.g. /somewhere/on/your/disk/images

filename should just be a filename without path, e.g. 24371592d9ea16625854ed68ac4b5846 , or 24371592d9ea16625854ed68ac4b5846.jpg

e.g. check how those two are used in the code in store.rb (at end below)

Question:

using filename[0,2] - you're using directories with 2-letter prefix of the MD5-sum to store the images?

2. Side-note: What is current_path?? Seems a misnomer. Should be a path+filename, not just a path

3. Check the second code snippet (below) from store.rb

Seems like you set store_dir to a relative directory -- that's very fragile and error prone.. it might be a better idea to set it to an absolute path (starting with '/')

4. Try setting filename and store_dir to constants for debugging

Just as a sanity check, does it work when you do this:

def store_dir
   '/tmp/'      # from the source code, it looks like this needs to start with '/' !!
end

def filename
   'my_uploaded_file'
end

that should work before setting filename to MD5 sum..


From the source code: (0.5.7)

lib/carrierwave/storage/file.rb

lib/carrierwave/uploader/store.rb

You can override CarrierWave::Uploader::Store#filename to point to a filename of your choice (see source code):

Overriding it in CarrierWave::Uploader::Base should work, as 'Store' is included in 'Base' ; this overrides the default filename.

Fromstore.rb

  # Override this in your Uploader to change the filename.                                                                                                                                                                                                                   
  #                                                                                                                                                                                                                                                                          
  # Be careful using record ids as filenames. If the filename is stored in the database                                                                                                                                                                                      
  # the record id will be nil when the filename is set. Don't use record ids unless you                                                                                                                                                                                      
  # understand this limitation.                                                                                                                                                                                                                                              
  #                                                                                                                                                                                                                                                                          
  # Do not use the version_name in the filename, as it will prevent versions from being                                                                                                                                                                                      
  # loaded correctly.                                                                                                                                                                                                                                                        
  #                                                                                                                                                                                                                                                                          
  # === Returns                                                                                                                                                                                                                                                              
  #                                                                                                                                                                                                                                                                          
  # [String] a filename                                                                                                                                                                                                                                                      
  #                                                                                                                                                                                                                                                                          
  def filename
    @filename
  end

You could also check for the cached filename, compute the MD5-sum (or better SHA1 sum) on it, and then use the result to name the file.


Fromstore.rb

  # Calculates the path where the file should be stored. If +for_file+ is given, it will be                                                                                                                                                                                 
  # used as the filename, otherwise +CarrierWave::Uploader#filename+ is assumed.                                                                                                                                                                                            
  #                                                                                                                                                                                                                                                                         
  # === Parameters                                                                                                                                                                                                                                                          
  #                                                                                                                                                                                                                                                                         
  # [for_file (String)] name of the file <optional>                                                                                                                                                                                                                         
  #                                                                                                                                                                                                                                                                         
  # === Returns                                                                                                                                                                                                                                                             
  #                                                                                                                                                                                                                                                                         
  # [String] the store path                                                                                                                                                                                                                                                 
  #                                                                                                                                                                                                                                                                         
  def store_path(for_file=filename)   # DEFAULT argument is filename - you don't need to override both
    File.join([store_dir, full_filename(for_file)].compact)
  end

File Equality:

files_equal?( filename1, filename2 )

      return true if File.size(filename1) == File.size(filename2)

      # return MD5(filename1) == MD5(filename2)

      # as we assume that the filename == MD5 + some suffix :

      return File.basename(filename1) == File.basename(filename2)   # if names == MD5 are the same

end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文