Rails图片上传与显示

由于rails封装了表单中的file类型,从而使得它的上传和下载变的很简单,只需要调用上传的file表单对象[Tempfile]的read方法就可以轻松的得到所上传文件的二进制流。

其实一般的上传文件有保存与服务器端有两种方式,一种是直接将所上传的文件内容以二进制流的形式保存在数据库中,当然了这就要求数据库支持二进制流大对象;而另外的一种方式是将上传文件保存在服务器的指定目录下,这样就涉及到了文件读和写的处理。

这二种方式的选择完全取决于你自已,这要根据你项目的实际情况来决定。

下面重点就第二种方式对rails图片上传与显示做一个简单的介绍(本文中以使用网页编辑器KindEditer3.5.5为背景来进行)

1.首先建立Image类的数据迁移来形成存方图片的表:

class CreateImages < ActiveRecord::Migration
  def self.up
    create_table :images do |t|
      t.string :filename, :default => "", :null => false
      t.string :disk_filename, :default => "", :null => false
      t.integer :filesize, :default => 0, :null => false
      t.string :content_type, :limit => 60, :default => ""
      t.string :description
      t.integer :author_id, :default => 0, :null => false
      t.timestamp :created_on
    end
  end

  def self.down
    drop_table :images
  end
end

其中的filename为上传时的文件名,而disk_filename为上传后的文件保存在服务器端时的文件名,它是以ASCII码加时间截形成的文件名,这样防止了重名的现象

2.编写Image类

class Image < ActiveRecord::Base

  #上传的文件在服务器端上的目录路径
  cattr_accessor :storage_path
  @@storage_path = "#{RAILS_ROOT}/files"

  #这里由页面上提交的一个file表单对象来得到文件其它属性
  def file=(file_field)
    unless file_field.nil?
      @temp_file = file_field
      if @temp_file.size > 0
        self.filename = sanitize_filename(@temp_file.original_filename)
        self.disk_filename = Image.disk_filename(filename)
        self.content_type = @temp_file.content_type.to_s.chomp
        if content_type.blank?
          self.content_type = content_type_by_filename(filename)
        end
        self.filesize = @temp_file.size
      end
    end
  end

  #为Image对象设置file属性
  def file
    nil
  end

  #在保存image对象之前将上传的文件得到并保存在服务器上的目录中
  def before_save
    if @temp_file && (@temp_file.size > 0)
      #应用缓冲的方法得到文件的内容并将其保存
      File.open(diskfile, "wb") do |f|
        buffer = ""
        while (buffer = @temp_file.read(8192))
          f.write(buffer)
        end
      end
    end
    # Don't save the content type if it's longer than the authorized length
    if self.content_type && self.content_type.length > 255
      self.content_type = nil
    end
  end

  # 在删除数据库文件信息时将本地的文件也连带删除
  def after_destroy
    File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
  end

  #返回文件在服务端的地址
  def diskfile
    "#{@@storage_path}/#{self.disk_filename}"
  end

  #保存由文件域上传提交的文件
  def self.attach_files(file_field, description)
     image=Image.new(:description => description.to_s.strip,
                     :author_id => 1) if file_field&&file_field.size > 0   #这里的作者用于测试
     image.file= file_field
     image.save
     image
  end

  #得到文件的后缀名
  def filename_suffix
    m= self.filename.to_s.match(/(^|\.)([^\.]+)$/)
    m[2].downcase
  end
  
  private
  #规范filename
  def sanitize_filename(value)
    # get only the filename, not the whole path
    #将文件名中的\和/符号去掉
    just_filename = value.gsub(/^.*(\\|\/)/, '')
    # NOTE: File.basename doesn't work right with Windows paths on Unix
    # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))

    # Finally, replace all non alphanumeric, hyphens or periods with underscore
    @filename = just_filename.gsub(/[^\w\.\-]/,'_')
  end

  # Returns an ASCII or hashed filename
  # 返回一个由时ascii码与时间截组成的文件名
  def self.disk_filename(filename)
    #得到当前的时间截
    timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
    ascii = ''
    if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
      ascii = filename
    else
      ascii = Digest::MD5.hexdigest(filename)
      # keep the extension if any
      ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
    end
    #判断当前目录中是否存在生成的文件
    while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}"))
      #如果生成的文件名与目录中的文件相冲突则调用succ方法来去重
      timestamp.succ!
    end
    "#{timestamp}_#{ascii}"
  end

  #由filename来得到ContentType
  def content_type_by_filename(filename)
    m = filename.to_s.match(/(^|\.)([^\.]+)$/)
    case m[2].downcase
    when 'gif' then  return'image/gif'
    when 'jpg' , 'jpeg' ,'jpe' then return 'image/jpeg'
    when 'png' then return 'image/png'
    when 'tiff, tif'then return 'image/tiff'
    when  "bmp" then return "image/x-ms-bmp"
    when 'xpm' then return "image/x-xpixmap"
    end
  end

end

上面的代码可以适当的删减来减少复杂性,而其实最为重在的核心代码为before_save方法,它实质的完成了将上传的文件内容保存到了服务器端的功能,这个方法在调用save或create方法之前自动执行,而上传的接口方法为self.attach_files(file_field, description)类方法,它接收一个页面上的标签对象(可由params[:XXX]来得到),和一个关与些图片的描述信息为参数,来实现上传的,同时在使用时保证在项目的根目录下有一个名为files的目录。

3.控制器

class ImageController < ApplicationController
  before_filter :find_image_by_param, :only=>[:show]

  #处理文件的上传
  def upload
    @image=Image.attach_files(params[:imgFile], params[:imgTitle])
    if @image
      render :text => {"error" => 0, "url" => "#{url_for :controller=>"image", :action=>"show", :id=>@image}" }.to_json
    else
      render  :text => {"error" => 1}
    end
  end

  #显示图片
  def show
    data=File.new(@image.diskfile, "rb").read
    send_data(data, :filename=>@image.filename,
      :type=>@image.content_type, :disposition => "inline")
  end

  #显示所有上传的图片
  def show_image_list
    @images=Image.find(:all)
    @json=[]
    @images.each do |image|
      temp= %Q/{
            "filesize" : "#{image.filesize}",
            "filename" : "#{image.filename}",
            "dir_path" : "#{url_for :controller=>"image", :action=>"show", :id=>image}.#{image.filename_suffix}",
            "datetime" : "#{image.created_on.strftime("%Y-%m-%d %H:%M")}"
      }/
      @json << temp
    end
    render :text => ("{\"file_list\":[" << @json.join(", ") << "]}")
  end

  private
  def find_image_by_param
    @image=Image.find(params[:id]) if  params[:id]
  end
end

当使用rails进行下载时可用send_file方法来返回,由于这里只是显示图片故用send_data方法返回流:

send_file @image.diskfile, :filename => @image.filename,
                                    :type => @image.content_type,
                                    :disposition => ('attachment')

在设置dir_path时加上后缀是为了通过页面上KindEditor对于文件名的合法性检查

由于KindEditor采用的是JSON来做为返回的结果的,所以返回的结果为text,并且由于KindEditor的file_manager组件生成的图片地址为result.current_url + data.filename,这与rails中的show/ID的形式不符所以也要对其的javascript文件做一定的更改(XXXX\kindeditor\plugins\file_manager\file_manager.js):

更改var createView = function(responseText)方法

//var fileUrl = result.current_url + data.filename;
//这里直接更改为dir_path
var fileUrl=data.dir_path
//var iconUrl = data.is_dir ? './images/folder-64.gif' : (data.is_photo ? fileUrl : './images/file-64.gif');
//这里直接改为fileURL
var iconUrl=fileUrl
同时更改var bindEvent = function (el, result, data, createFunc)方法,此方法用于单击图片时在“远程图片”选项卡的图片地址显不路径

//var fileUrl = result.current_url + data.filename;
var fileUrl=data.dir_path;
同时在初始化KindEditor时要指定它的上传文件服务器端程序路径和浏览服器器端路径:

imageUploadJson
指定上传图片的服务器端程序。
数据类型:String
默认值:../../php/upload_json.php

fileManagerJson
指定浏览远程图片的服务器端程序。
数据类型:String
默认值:../../php/file_manager_json.php

在这里本人将其封装成了一个方法,在helpers类文件中定义如下方法:

#产生一个文本编辑器
  def edit_textarea_for(id, option= {} )
    default_opts={:skinType=>"'default'", :resizeMode=> 0, :minWidth=>200, :minHeight=> 100, :width=>200, :height=> 100}
    default_opts.merge!(option)
    items = option[:items] || "[ 'source', '|', 'fullscreen', 'undo', 'redo', 'print', 'cut', 'copy', 'paste',
		                             'plainpaste', 'wordpaste', '|', 'justifyleft', 'justifycenter', 'justifyright',
                                 'justifyfull', 'insertorderedlist', 'insertunorderedlist', 'indent', 'outdent', 'subscript',
                                 'superscript', '|', 'selectall', '-',
                                 'title', 'fontname', 'fontsize', '|', 'textcolor', 'bgcolor', 'bold',
                                 'italic', 'underline', 'strikethrough', 'removeformat', '|', 'image',
                                 'flash', 'media', 'advtable', 'hr', 'emoticons', 'link', 'unlink', '|', 'about']"
    setting_params="skinType : #{default_opts[:skinType]}, resizeMode : #{default_opts[:resizeMode]}, minWidth : #{default_opts[:minWidth]},
                    minHeight : #{default_opts[:minHeight]}, width : #{default_opts[:width]}, height: #{default_opts[:height]}, items : #{items}"
    #设置图片上传的路径
    if default_opts[:imageUploadJson]
       setting_params << ", imageUploadJson : '#{default_opts[:imageUploadJson]}'"
    end
    #设置远程图片管理的路径
    if default_opts[:fileManagerJson]
      setting_params << ", fileManagerJson : '#{default_opts[:fileManagerJson]}', allowFileManager : true"
    end
    content_tag(:textarea, "#{default_opts[:values]}", {:id=>"#{id}", :name=>"#{id}",:style=>"width:#{option[:width]}px;height:#{option[:height]}px;"}) +
    javascript_include_tag("kindeditor/kindeditor.js") +
    javascript_tag("KE.show({id : '#{id}', #{setting_params} });");
  end
在使用时注意引入kindeditor.js文件

使用如下:

<%=  edit_textarea_for "article[content]", :height=> 450, :width=>700, 
          :imageUploadJson=>url_for(:controller=>"image", :action=>"upload"),
          :fileManagerJson=>url_for(:controller=>"image", :action=>"show_image_list")%>

关于KindEditor的更多使用参见它的官网:http://www.kindsoft.net/doc.php

扩展:在以二进制流保存文件与数据库时用Tempfile对象(即页面上的file输入框的上传对象)的read来直接得到二进制流,然后直接赋值给Image的Data类型字段,在定认data类型字段时用

t.binary :data
来定义数据迁移。

总结:其时Rails为我们封装了file输入框的上传对象,只需要一个read方法就能得到上传流,然后根据自己的需要来处理,从而使的上传与下载操作变的很"傻瓜"。在使用上传时不要忘了设置表单的encType属性为multipart/form-data,并且method为post

参考:飞翔的山猪的rails中实现kindeditor中的图片上传

你可能感兴趣的:(Ruby,on,rails开发)