使用CarrierWave实现RESTful文件上传

6
我正在尝试构建一个文件上传的API后端。我希望能够通过POST请求上传包含Base64编码的文件字符串的文件。服务器应该解码该字符串,并使用CarrierWave保存文件。以下是我的进展情况:

photo.rb:

class Photo
  include Mongoid::Document
  include Mongoid::Timestamps
  mount_uploader :image_file, ImageUploader
end

image_uploader.rb:

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

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

Rails控制台:(摘要)
ruby-1.8.7-p334 :001 > img = File.open("../image.png") {|i| i.read}
 => "\377���JFIF\000\001\002\001\000H\000H\000\000\377�Photoshop 3.0\0008BIM\003...
ruby-1.8.7-p334 :003 >   encoded_img = Base64.encode64 img
=> 3af8A\nmLpplt5U8q+a7G2...
ruby-1.8.7-p334 :005 >   p = Photo.new
 => #<Photo _id: 4e21b9a31d41c817b9000001, created_at: nil, updated_at: nil, _type: nil, user_id: nil, image_file_filename: nil> 
ruby-1.8.7-p334 :006 > p.user_id = 1
 => 1 
ruby-1.8.7-p334 :007 > p.image_file = Base64.decode64 encoded_img
\255��=\254\200�7u\226���\230�-zh�wT\253%����\036ʉs\232Is�M\215��˿6\247\256\177...
ruby-1.8.7-p334 :008 > p.save
 => true 
ruby-1.8.7-p334 :009 > p.image_file.url
 => nil 

full

问题似乎与将Base64解码字符串转换为文件的过程有关。CarrierWave似乎期望一个File对象,但我却提供了一个String。那么如何将该String转换为File对象呢?我希望这种转换不会保存任何东西到文件系统中,只是创建对象并让CarrierWave完成剩下的工作。


1
我知道这与您所要求的不同,但我放弃了Paperclip和CarrierWave,转而选择了功能非常强大的DragonFly。请查看http://markevans.github.com/dragonfly/file.Models.html下的“使用访问器”。 - kain
@kain 我现在正在使用carrierwave.. 很想听听你使用dragonfly的经验。 - noli
出于某种原因,每次我使用Paperclip和CarrierWave时,我最终都会得到自定义补丁,尤其是Paperclip。DragonFly允许定制而无需打补丁,而且它的工作非常稳定。非平凡的配置很难设置,但一旦你掌握了要领,你就可以做任何事情。例如?我需要我的应用程序具有强大真实的MIME类型,因为它们也写在远程存储头文件中。请查看我的票证:http://bit.ly/pSv6EE - kain
谢谢,是的...我也不得不为一些烦人的事情修补carrierwave。 - noli
3个回答

24

CarrierWave也接受StringIO,但它需要一个original_filename方法,因为它需要用它来确定文件名并执行扩展名检查。在Rails 2和3之间,做法有所不同,以下是两种方法:

Rails 2

io = StringIO.new(Base64.decode64(encoded_img))
io.original_filename = "foobar.png"

p.image_file = io
p.save

在 Rails 3 中,您需要创建一个新类,然后手动添加 original_filename 回来。

class FilelessIO < StringIO
    attr_accessor :original_filename
end

io = FilelessIO.new(Base64.decode64(encoded_img))
io.original_filename = "foobar.png"

p.image_file = io
p.save

1
我有点失望,因为我必须要进行猴子补丁来修改StringIO,但它确实有效! - user94154
如果只是单次使用,您可以编写: io = StringIO.new(Base64.decode64(encoded_img)); def io.original_filename; "foobar.png"; end - Adam Klein
4
从技术上讲,你并没有对StringIO进行猴子补丁,而是创建了一个新类,该类继承自StringIO并添加了一个额外的属性original_filename。完全不是猴子补丁。 - foomip
哈哈,回头看这条评论,你完全是对的。惊讶于有人花了这么长时间才发现。看起来已经修复了。 - Zachary Anker
文档中指定了另一种方法,可以在不打补丁StringIO的情况下完成:def s.original_filename; "foobar.png"; end - fsinisi90

1
你不需要对StringIO进行任何修改或将其放入模型中。你可以在上传器定义中覆盖cache!()方法。或者,你可以进一步创建一个自己的模块进行包含。我的文件是来自JSON文档的序列化字符串。传入的对象看起来像这样{ :filename => 'something.jpg', :filedata => base64字符串 }。
下面是我的模块:
module CarrierWave
  module Uploader
    module BackboneLink  
      def cache!(new_file=sanitized_file)
        #if new_file isn't what we expect just jump to super
        if new_file.kind_of? Hash and new_file.has_key? :filedata
          #this is from a browser, so it has all that 'data:..' junk to cut off.
          content_type, encoding, string = new_file[:filedata].split(/[:;,]/)[1..3]
          sanitized = CarrierWave::SanitizedFile.new( 
            :tempfile => StringIO.new(Base64.decode64(string)), 
            :filename => new_file[:filename], 
            :content_type => content_type 
          )
          super sanitized
        else
          super
        end
      end
    end
  end
end

然后我可以将其包含在上传器中。 uploaders/some_uploader.rb:

class SomeUploader < CarrierWave::Uploader::Base

  include CarrierWave::Uploader::BackboneLink

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接