Image upload in production

前面使用的圖片上傳程序在 development 環境中用起來不錯,但圖片都存在本地端系統中(如 app/uploaders/picture_uploader.rbstorage :file 這行所示),在 production 環境中這麼做顯然不太好。

所以我們要使用雲端儲存服務來儲存圖片,和 APP 所在的檔案系統分開。

在 Heroku 中儲存的檔案都是暫時性的,重新部署後會把之前上傳的檔案刪除

我們要使用 fog gem 來設定 APP,在 production 環境中使用雲端儲存:

app/uploaders/picture_uploader.rb

class PictureUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  process resize_to_limit: [400, 400]

  if Rails.env.production?
    storage :fog
  else
    storage :file
  end

  # 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

  # Add a white list of extensions which are allowed to be uploaded.
  def extension_white_list
    %w(jpg jpeg gif png)
  end
end

上面使用了 production? 布林值方法根據所在的環境選擇儲存方式:

if Rails.env.production?
  storage :fog
else
  storage :file
end

雲端儲存服務有很多,我們要使用其中一個最受歡迎且支援度比較好的:Amazon.com 的 Simple Storage Service (S3)

S3 是付費服務,不過測試這個應用,每月的費用不會超過一美分

基本步驟如下:

  1. 註冊一個 Amazon Web Services 帳號
  2. 透過 AWS Identity and Access Management (IAM) 建立一個使用者,記下 access key 和 secret key
  3. 使用 AWS Console 建立一個 S3 bucket(名稱自訂),然後賦予上一步建立的使用者讀寫權限

關於這些步驟的詳細說明,可以參考 S3 documentation,如果有需要的話就直接 Google。

實作時,把第 3 步驟的意思弄混了,以為要在 bucket 增加 permissions 建立使用者權限,但其實不用,因為建立 IAM 的同時就會設定權限了,詳情請看 Creating Your First IAM User and Administrators Group 的設定

建立並設定好 S3 帳號後,就要建立並設定 CarrierWave 文件:

config/initializers/carrier_wave.rb

if Rails.env.production?
  CarrierWave.configure do |config|
    config.fog_credentials = {
      # Configuration for Amazon S3
      :provider              => 'AWS',
      :aws_access_key_id     => ENV['S3_ACCESS_KEY'],
      :aws_secret_access_key => ENV['S3_SECRET_KEY'],
      :region                => ENV['S3_REGION']
    }
    config.fog_directory     =  ENV['S3_BUCKET']
  end
end

注意,如果做了這些設定連不上 S3,可能是區域位置的問題。有些使用者要在 fog 中增加 :region => ENV[’S3_REGION’],然後在 terminal 執行 heroku config:set S3_REGION=<bucket region> 指令,其中 是你所在的區域,例如 eu-central-1。如果想查詢你的區域,請參考 Amazon 文件 Regions and Endpoints

自己多加了一個 :region 設定

和 production 環境中 email 的設定一樣,上述的設定也使用 Heroku 的 ENV 變數,沒有直接在程式碼裡寫入敏感訊息。在 Section 10.3 中,這些變數是由 SendGrid add-on 自動定義,但在這個例子中,我們要自己定義,方法是使用 heroku config:set 指令:

$ heroku config:set S3_ACCESS_KEY=<access key>
$ heroku config:set S3_SECRET_KEY=<secret key>
$ heroku config:set S3_REGION=<region>
$ heroku config:set S3_BUCKET=<bucket name>

注意,S3_ACCESS_KEY=<access key> 之間不能有空格!

設定完之後,我們就可以準備提交和部署。建議更新 .gitignore,忽略圖片上傳的資料夾:

.gitignore

# See https://help.github.com/articles/ignoring-files for more about ignoring
# files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
#   git config --global core.excludesfile '~/.gitignore_global'

# Ignore bundler config.
/.bundle

# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal

# Ignore all logfiles and tempfiles.
/log/*.log
/tmp

# Ignore Spring files.
/spring/*.pid

# Ignore uploaded test images.
/public/uploads

然後提交、合併:

$ bundle exec rake test
$ git add -A
$ git commit -m "Add user microposts"
$ git checkout master
$ git merge user-microposts
$ git push

接著部署、重設資料庫、重新把 seed data 載入資料庫:

$ git push heroku
$ heroku pg:reset DATABASE
$ heroku run rake db:migrate
$ heroku run rake db:seed

因為 Heroku 已經安裝 ImageMagick,所以在 production 環境中調整圖片尺寸和上傳圖片的功能都能正常使用: