新手问题 [已解决] Carrierwave 如何配置合理的上传文件名?+ 怎样在上传之前直接显示预览图片?

chairy11 · 2015年05月12日 · 最后由 pathbox 回复于 2016年04月08日 · 5126 次阅读

问题一:

看到李华顺的《Carrierwave 如何配置合理的上传文件名》是这么写的:

 def filename
  if original_filename
    # current_path 是 Carrierwave 上传过程临时创建的一个文件,有时间标记
    # 例如: /Users/jason/work/ruby-china/public/uploads/tmp/20131105-1057-46664-5614/_____2013-11-05___10.37.50.png
    @name ||= Digest::MD5.hexdigest(current_path)
    "#{@name}.#{file.extension}"
  end
end

但看日期是 2011 年写的,而且 ruby-china 好像同时使用了两种。

在《photo_uploader.rb》是:

def filename
    if super.present?
      # current_path 是 Carrierwave 上传过程临时创建的一个文件,有时间标记,所以它将是唯一的
      # 此方法只使用 Ruby China 这类图片上传的场景
      @name ||= Digest::MD5.hexdigest(current_path)
      "#{Time.now.year}/#{@name}.#{file.extension.downcase}"
    end
  end

在《avatar_uploader.rb》是:

def filename
    if super.present?
      "avatar/#{model.id}.jpg"
    end
  end

注,在《base_uploader.rb》里设置了前半部分

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

这两种使用场景怎么区分?怎么写比较好?

我以前是这样写的

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

没有直接设置 filename,结果是生成的文件名是uploads/project/avatar/1/1-__________________.jpg,好像所有中文都变成下划线,好怪……

大家都怎么弄的?

问题二:

我 logo 字段本身就在 something model 里,在新建 something 时就上传图片,那就统一在保存时保存数据,不用 ajax 上传(而在富文本编辑器 simditor 中我是用 ajax 上传的)。 我想在上传之前就显示预览图片,怎么弄? 正在研究这个帖子《Preview an image before it is uploaded》 目前我表单的结构是

 <%= f.input :logo, as: :file, label:"LOGO",
                hint: "支持JPG、GIF、PNG格式图片,不超过5M" %>
<%= image_tag(@something.logo_url, class:'img-responsive') if @something.logo %>
<%= f.hidden_field :logo_cache %>

额,忽然想到,看 simditor 的源码会不会就有这个功能的实现? 另外看到一个Jasny Bootstrap实现的上传按钮就很好,样式也不错,而且上传后有删除按钮,也有预览……咋抄下呢?

问题一解决方案:

# Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end


  # Override the filename of the uploaded files:
  # 参考李华顺的帖子(http://huacnlee.com/blog/carrierwave-upload-store-file-name-config/)
  def filename
    if original_filename
      @name ||= Digest::MD5.hexdigest(current_path)
      "#{@name}.#{file.extension}"
    end
  end

不过李华顺说我理解错了……额,在我想明白哪错了之前,先用这个方案吧……

问题二解决方案

抄的Jasny Bootstrap的 js 和 css 代码, fileinput.js_fileinput.scss

再改改样式,再配合 simple_form

<div>
          <span class="btn btn-default btn-file">
           <span class="fileinput-new">选择图片</span>
           <span class="fileinput-exists">更换图片</span>
            <%= f.input_field :logo, as: :file, label:false %>
          </span>
          <a href="#" class="close fileinput-exists" data-dismiss="fileinput" style="float: none">&times;</a>
          <p class="help-block">支持JPG、GIF、PNG格式图片,不超过5M</p>
 </div>
 <div class="fileinput-preview thumbnail" data-trigger="fileinput">
          <img class="img-responsive" src="/images/default-placeholder.jpg" alt="Default placeholder">
 </div>

其它遗留问题

李华顺说「uploads 这个应该是 Nginx 上面配置的」,我一直是自己在项目文件中设置 shared/public/uploaders 文件夹的,这个等有时间再深入研究下……

两个问题...好多问题吧!


Ruby China 的 Uploader 文件名格式有两种是有原因的!

  1. AvatarUploader - 用 model.id 作为文件名,这样用户的头像地址会一直固定不变,可以利于 Fragment Caching,同时还有个情况是 Ruby China 用的是 UpYun 存储头像文件,那边的 HTTP Expires 过期时间是 1 星期,在浏览器 HTTP 请求 ETag 检测的过程中能达到缓存更新的目的。
  2. PhotoUploader - 那样做是就是我博客里面说的目的,这里不再重复。

你文件名的问题,就是 filename 这个需要覆盖,不然你说我没事写那个函数干嘛?


store_dir 你那么写其实也对的,Ruby China 这么做还是有原因的,因为 UpYun 当个文件夹的文件数量他们是没有限制的,那边不是一个文件夹,而是一个服务(不会有性能问题),但会到传统的磁盘就不能像 Ruby China 这么做了,普通磁盘目录一个文件夹里面的文件超过一定的数量会有性能问题。

另外,你不应该在前面加 uploads 这个应该是 Nginx 上面配置的,写在哪儿 uploads 会存到数据库。

#1 楼 @huacnlee

再问问哦:

  1. 那按你这么说,我是没用第三方存储的,目前就在一台服务器上,那我的实现方法是在【base_uploader.rb】中写
# Override the directory where uploaded files will be stored.
 # This is a sensible default for uploaders that are meant to be mounted:
 def store_dir
   "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
 end


 # Override the filename of the uploaded files:
 # 参考李华顺的帖子(http://huacnlee.com/blog/carrierwave-upload-store-file-name-config/)
 def filename
   if original_filename
     @name ||= Digest::MD5.hexdigest(current_path)
     "#{@name}.#{file.extension}"
   end
 end

可以吗?然后因为都用同一个方法,在子 uploader 上就不再写什么了……

  1. 那个头像,你写死了扩展名是jpg(#{model.id}.jpg),如果我上传的头像图片是 png 怎么办?

  2. 「uploads 这个应该是 Nginx 上面配置的」?不懂耶,因为我平时是用 capinstrano3 部署,然后我在项目目录里把 uploads 文件夹放在shared/public/uploads上面,以便多版本共享, 在「deploy.rb」文件中这么设置:

    set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system public/uploads}
    

    如果是我本机开发环境,就会在 public/uploads 文件夹下…… 不是这样做的吗?

  1. 不对,理解反了,认真看我的描述,以及想想为什么
  2. 扩展名,强制改了 .jpg 的
  3. Nginx 不懂,看 Nginx 文档,或者网上搜索,到处都有,比如:http://stackoverflow.com/questions/18915679/carrierwave-nginx-serving-images-from-public-directory
  1. 文件名中文变下划线的问题,这个是 CarrierWave 的默认行为,参考 https://github.com/carrierwaveuploader/carrierwave#filenames-and-unicode-chars
  2. 图片上传前预览只能通过 JavaScript 的 FileReader 实现,如果用了 Simple Form,可以用 simple_form_fancy_uploads 这个 gem 实现你问题二中代码的功能。然后再用引入 simple_form_fancier_uploads 这个 gem 实现上传前预览图片。 https://github.com/teeceepee/simple_form_fancier_uploads

#3 楼 @huacnlee

555,理解不了…… 我现在的写法,比如我在 something 的 id 为 1 的 object 里存 logo 字段图片,得到的会是 5 个文件 uploads/something/logo/1/4ae9b71e903519e73662775229466559.png uploads/something/logo/1/big_bf3d504f375eda565f56a109e2d818a3.png uploads/something/logo/1/large_29da5e316959516fcd8dc7d612d299cf.png uploads/something/logo/1/normal_50686705ef4f18084c68c906a8baddff.png uploads/something/logo/1/small_99867caa881638bb224b7f21872ce9d6.png

注:写法是【base_uploader.rb】

# Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end


  # Override the filename of the uploaded files:
  # 参考李华顺的帖子(http://huacnlee.com/blog/carrierwave-upload-store-file-name-config/)
  def filename
    if original_filename
      @name ||= Digest::MD5.hexdigest(current_path)
      "#{@name}.#{file.extension}"
    end
  end

话说,我以前不知道 nginx 要对 uploader 设置的,这个我得再花时间好好研究一下……

#4 楼 @yuhaidonghd 请问simple_form_fancier_uploadssimple_form_fancy_uploads有什么区别?实现的是同一个功能吧? 看到第一个是 9 天前才建的……我应该用第二个吧?

#4 楼 @yuhaidonghd

555,我装了simple_form_fancy_uploads,然后写了下面代码

<%= f.input :logo, as: :image_preview, use_default_url: true %>

上传完图片没啥变化啊…… 这里有个Jasny Bootstrap倒是实现得很漂亮,可是我又不能用两套 bootstrap,要不我去抄下它的相关 js 代码……

#5 楼 @chairy11 后面那些带前缀的是 rmagick 或者 minimagick 处理的 补充一下,ruby-china 的 baseuploder 是抽出来的,给其他继承用,外面用的是它的子类。。。见 AvatarUploader

# encoding: utf-8

class ImageUploader < CarrierWave::Uploader::Base

  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this uploader:
  storage :file

  # 存放目录,打印出来看看嘛
  def store_dir
    p model.class.to_s.underscore
    p mounted_as
    p model.id
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # 这里的就是minimagick处理的,文件名会保存为thumb_#{MD5}.extention(png/jpg),也就是上面多出的四个带前缀的文件,big_xxx.png。相当于resize为200*200,下同
  version :thumb do
    process :resize_to_fit => [200, 200]
  end

  version :small do
    process :resize_to_fit => [32, 32]
  end

  version :mid do
    process :resize_to_fit => [48, 48]
  end

  version :large do
    process :resize_to_fit => [72, 72]
  end

  # 格式白名单
  def extension_white_list
    %w(jpg jpeg gif png)
  end

  # 文件名没必要纠结吧,有中文就写成这个
  def filename
    if original_filename
      @name ||= Digest::MD5.hexdigest(File.dirname(current_path))
      "#{@name}.#{file.extension}"
    end
  end
end

#8 楼 @flowerwrong 额,没明白你想表达什么了…… 我也是啊,先有抽像母类,再来子类。下面是我现在的写法 《base_uploader.rb》

# encoding: utf-8
class BaseUploader < CarrierWave::Uploader::Base

  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this uploader:
  storage :file
  # storage :fog

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end


  # Override the filename of the uploaded files:
  # 参考李华顺的帖子(http://huacnlee.com/blog/carrierwave-upload-store-file-name-config/)
  def filename
    if original_filename
      @name ||= Digest::MD5.hexdigest(current_path)
      "#{@name}.#{file.extension}"
    end
  end


  # 调整临时文件的存放路径,默认是再 public 下面
  def cache_dir
    "#{Rails.root}/tmp/uploads"
  end

  # Provide a default URL as a default if there hasn't been a file uploaded:
  def default_url
    # ====== 待修改 ======
    # 改为对应不同大小版本的默认图片
    # "images/#{version_name}.jpg"
    "default-placeholder.jpg"
  end

  # Add a white list of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  def extension_white_list
    %w(jpg jpeg gif png)
  end


end

《logo_uploader.rb》

# encoding: utf-8

class LogoUploader < BaseUploader

  # 设置大小
  version :normal do
    process resize_to_fill: [48, 48]
  end

  version :small do
    process resize_to_fill: [16, 16]
  end

  version :large do
    process resize_to_fill: [96, 96]
  end

  version :big do
    process resize_to_fill: [120, 120]
  end



  def extension_white_list
    %w(jpg jpeg png)
  end



end

正好搭车问个问题,上传的图片保持在多少,能到高清的标准?1280?2048?

#7 楼 @chairy11 simple_form_fancy_uploads 提供的功能是如果对应的属性是 CarrierWave 上传的图片那么显示出来。simple_form_fancier_uploads 要与 simple_form_fancy_uploads 一起用,提供上传前预览的功能。好吧,那个其实是我前几天写的,如果你不想多引入一个 gem 那就把这段代码复制吧。

$(document).on('ready page:load', function() {
  $('input[type=file]').change(function() {
    previewImage(this);
  });

  var previewImage = function(input) {
    var files = input.files;
    if (files && files[0]) {
      var image = files[0];

      var fileReader = new FileReader();
      fileReader.onload = function(event) {
        var dataURL = event.target.result;
        var imageTag = $(input).parent().find('img');

        if (imageTag.length == 0) {
          imageTag = $('<img>');
          imageTag.insertBefore($(input));
        }

        if (!imageTag.attr('width')) {
          imageTag.attr('width', '100%');
        }
        imageTag.attr('src', dataURL);
      };

      fileReader.readAsDataURL(image);
    }
  };
});

#11 楼 @yuhaidonghd 哦,谢谢。

主要感觉这两个 gem 文档写得不是很好,所以我一卡住就放弃了。 你写的比较简洁,但我之前已经用其它方法解决这问题了,懒得再换方案了。 见原帖问题二解决方案:抄的 Jasny Bootstrap 的 js 和 css 代码, fileinput.js 和_fileinput.scss

#12 楼 @chairy11 simple_form_fancy_uploads 的文档还可以吧,不过我当时也认为它能实现上传前预览。至于我自己写的那个,跟没有文档没有啥区别,不过以后会更新的,目标是取代 simple_form_fancy_uploads 。你抄的那个和我的原理是一样的,都是用 FileReader 实现,你可以看一下的。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号