由于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
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
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中的图片上传