最近 NoSQL 很火嘛!咱也跟上时代的脚步,了解了一下。
之前的 Rails 项目都是 MySQL 数据库的,文件存储用 Linux 普通,上传图片用 Paperclip 这个插件,有两个项目的上传目录里面文件非常多,管理,同步很是成问题,而且之前定义的目录结构有性能隐患。这个文件存储一直是我的一块心病,项目的上传文件越来越多。
于是最近试着想办法把文件系统转移到 GridFS 里面存放,以来管理简单了,而来不用担心目录结构的问题,而且 Mongodb 的机制还可以应对未来的扩充。
程序上面转移过去倒是不难,难点在于以前的上传文件需要平滑转移到 GridFS 里面。
一下仅供参考,每个项目使用的组件和架构方式不同,不一定完全适合!
Homeland 的项目情况
程序方面改动
有些太写的我就没法一一讲了,
先是把 Paperclip 换成
Carrierwave (这里时因为截止到目前 Paperclip 还没有支持 GridFS)
我以一个 Model 为例子来介绍转换过程
1
2
3
4
5
6
7
8
9
10
11
12
| class Photo < ActiveRecord::Base
# 上传图片
has_attached_file :image,
:default_style => :normal,
:styles => {
:small => “100>”,
:normal => “680>”,
},
:url => “#{APP_CONFIG[‘upload_url’]}/:class/:attachment/:hashed_path/:id_:style.jpg”,
:path => “#{APP_CONFIG[‘upload_root’]}/:class/:attachment/:hashed_path/:id_:style.jpg”,
:default_url => “photo/:style.jpg”
end
|
具体步骤
- Gemfile 里面增加,Paperclip 插件依然保留:
1
2
3
4
| gem ‘carrierwave’
gem ‘mini_magick’
gem “mongoid”, “2.0.0.rc.7”
gem “bson_ext”, “~> 1.2.2”
|
- $ bundle install
- 生成 mongoid 的配置文件 $ rails g mongoid:config
- 新增 config/initializers/carrierwave.rb 文件,可以参考 carrierwave.rb
- 新增 app/uploaders/base_uploader.rb, 内容参考 base_uploader.rb
- 新增 app/uploaders/photo_uploader.rb, 并继承 BaseUploader, 内容参考 photo_uploader.rb
- 去掉 Photo model 里面的 has_attached_file 定义,改用 mount_uploader :image, PhotoUploader
- 生成个 migration,把数据库里面 image_file_name 字段重命名为 image
- Views 里面把所有以前 Paperclip 的图片调用方式改为 Carrierwave 的调用方式 (哎,为嘛不做成一样的呢)
- 上传图片流程不用修改
- routes
- 然后就可以测试上传了,如果没有出错,程序方面的改动就完成了。
文件转移
这里就比较麻烦了,我写了一个 Rake 脚本,里面有说明(根据自己的情况做一定的修改):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
| # coding: UTF-8
#
# 以下为 Rake 任务,功能是将普通文件系统里面的东西转移到 MongoDB GridFS 里面
# 此代码片段来自于 Homeland 项目:https://github.com/huacnlee/homeland/tree/mysql
# 场景:
# 老架构 Linux File Store, Paperclip, hash 目录:"https://github.com/huacnlee/homeland/blob/ca0bdd8ab26da7b780e2dae7eba12b79f41e6d65/config/initializers/paperclip_hashpath.rb"
# 新架构 Mongodb GridFS, Garrierwave, 继续沿用 Paperclip 目录兼容:https://github.com/huacnlee/homeland/tree/7100ce4c506cc2c4387f25e50c533e5bbcac6cc2/app/uploaders
# 整个过程不会修改任何原始数据库和上传文件
#
require 'mongo'
include Mongo
namespace :gridfs do
task :import => :environment do
# 这里将图片读取并转入 GridFS 的过程抽象为 GridFsImporter,见下面
@grid = GridFsImporter.new
# 处理 Photo 表的
puts "-"*120
puts "Import Photos"
# 定义原来的几种图片个规格,分别载入
photos_styles = %w(small normal original)
Photo.all(:conditions => 'image is not null').each do |photo|
photos_styles.each do |style|
@grid.put(photo, 'image', style)
end
end
# 处理 User 表
puts "-"*120
puts "Import Users"
avatars_styles = %w(small normal large original)
User.all(:conditions => 'avatar is not null').each do |user|
avatars_styles.each do |style|
@grid.put(user, 'avatar', style)
end
end
end
end
# 图片读取并转入 GridFS
class GridFsImporter
def initialize
@grid = Grid.new(Mongoid.database)
end
def put(model, attr_name, style)
print "#{model.class.to_s}: #{model.id} ..."
# Hash path, 我这个是在 BaseUploader 里面定义个一个按照之前 paperclip 那中目录生成方法写的
hashed_path = BaseUploader.new.hashed_path(model.id)
# file_name 的定义格式要按照原来 Paperclip 的标准定义,这样在网站正文中插入的图片就不会受到影响,图片地址通通不变
file_name = "#{model.class.to_s.tableize}/#{attr_name.tableize}/#{hashed_path}/#{model.id}_#{style}.jpg"
# 实际的文件地址,我在 APP_CONFIG['upload_root'] 里面配置了上传的根目录
real_file_name = File.join([APP_CONFIG['upload_root'],file_name])
begin
f = File.open(real_file_name)
rescue => e
puts "** [Error] open file error: #{e}"
end
# 检查有没有同样的,如果有,就不用插入了(一来可以防止重复图片,而来如果转移过程中断,之前加过的就不会再次加入)
if old = @grid.exist?({'filename' => file_name})
puts "-- skip, old #{old['filename']} existed."
else
begin
# 向 GridFS 提交文件
id = @grid.put(File.open(real_file_name), :filename => file_name)
puts "#{file_name} saved."
rescue => e
puts "** [GridFS] save file error: #{e}"
end
end
end
end
|
我说的不是很细,只是给出一个参考,可以多看一下 Homeland 项目里面的情况做对比,根据实际情况做修改。
祝你顺利转移!