十年前,Web 應用程式架構 Rails 創始人 David Heinemeier Hansson 曾錄製視頻,向我們示範如何使用 Ruby on Rails 在 15 分鐘內創作一個 blog 引擎。這個視頻通過 Rails 優秀的 MVC 、習慣優於配置(Convention over Configuration)等設計,以及強大的代碼產生、scaffold 等功能,成功展示了 Ruby on Rails 編寫 Web 應用程式核心功能的高效簡潔。Ruby on Rails 這門技術也在 Web 2.0 時代大放異彩,成為了 Web 應用程式開發最佳的技術方案選擇之一。
經過十年的發展,軟體行業早已邁入雲端運算時代。為了應對大規模的訪問量,同時控制研發和運營成本,作為雲端運算基石的雲端儲存,已經成為了 Web 開發必不可少的基礎設施。今天,就讓我們一起來看看如何使用 Rails 和七牛雲端儲存,在 15 分鐘內打造一個圖片分享社交應用原型。
七牛雲端儲存 是一個公用雲端服務,提供海量Object Storage Service功能,以及雲端檔案處理和分發服務。開始之前,我們需要建立一個七牛雲端儲存 試用帳戶,並且瞭解一些基礎知識:
七牛雲端儲存是一個 Key-Value 形式的Object Storage Service系統,一個 key 對應一個資源(檔案)。
資源必須儲存在某個空間(Bucket)中,不可單獨存在。一個帳戶可以建立多個空間。
建立基本 Rails 項目
你應該可以使用 Ruby 1.9 以上,Rails 3.0 以上的任意版本,本例中我們使用的是 Ruby 2.2.3 和 Rails 4.2.5。
安裝好 Ruby 和 Rails 之後,使用 rails 命令建立應用程式 konata 的目錄結構和基本檔案:
複製代碼 代碼如下:
rails new konata
稍候這條命令執行完成,我們立即得到了一個可以啟動並執行空白 Rails 應用程式,執行以下命令,並在瀏覽器中訪問 http://localhost:3000 查看運行效果:
複製代碼 代碼如下:
cd konata
rails server
使用 Rails Scaffold 實現 CRUD
我們將使用 Rails 的 scaffold 功能,產生用於處理圖片發表的 model、controller、view,以及 database migration 等原始碼檔案。
複製代碼 代碼如下:
rails generate scaffold post title filename qiniu_hash
rake db:migrate
訪問 http://localhost:3000/posts 可以看到,我們已經獲得了 post 的完整 CRUD 功能,只是暫時還不能上傳圖片。
使用七牛 API 實現圖片上傳
修改 Gemfile,在其中加入對七牛 Ruby SDK 的引用:
複製代碼 代碼如下:
gem 'qiniu'
執行以下命令安裝七牛 Ruby SDK。這裡原本應該執行 bundle 進行安裝,但由於七牛 Ruby SDK 依賴的 mime-types 版本設定比較保守,需要使用 bundle update 命令降級 mime-types,解決依賴衝突。
複製代碼 代碼如下:
bundle update mime-types
編輯 config/secrets.yml,在其中加入七牛雲端儲存帳戶的密鑰:
複製代碼 代碼如下:
development:
secret_key_base: <YOUR_SECRET_KEY_BASE>
qiniu_access_key: <YOUR_QINIU_ACCESS_KEY>
qiniu_secret_key: <YOUR_QINIU_SECRET_KEY>
建立 config/initializers/qiniu.rb,使用剛才加入的密鑰與七牛雲端儲存體服務器建立串連。內容如下:
require 'qiniu'Qiniu.establish_connection!( access_key: Rails.application.secrets.qiniu_access_key, secret_key: Rails.application.secrets.qiniu_secret_key)
注意:
AccessKey 和 SecretKey 必須絕對保密,不可出現在使用者可以查看的 Web 前端原始碼裡,或是編譯進用戶端二進位代碼中。
七牛 API 提供了多種上傳方式 以滿足不同的業務情境需求。這裡我們選擇使用最有代表性,也最簡單的 HTML 表單上傳+HTTP 303 重新導向返回的方式實現用戶端檔案直接上傳七牛雲端儲存。這種方法的好處是用戶端檔案無需通過商務服務器(app server)中轉,既可以利用七牛強大的 CDN 最佳化上傳速度及提高可靠性,也可以節省商務服務器頻寬。
編輯 app/views/posts/_form.html.erb,根據七牛雲端儲存 SDK 構造上傳表單。注意其中的上傳憑證欄位,我們將在 PostsController 裡建立它:
<%= form_tag 'http://upload.qiniu.com', multipart: true do %> <%= hidden_field_tag :token, @qiniu_upload_token %> <div class="field"> <%= label_tag :title %><br> <%= text_field_tag 'x:title' %> </div> <div class="field"> <%= label_tag :image %><br> <%= file_field_tag :file %> </div> <div class="actions"> <%= submit_tag 'Create' %> </div><% end %>
編輯 app/controllers/posts_controller.rb,添加代碼產生上傳憑證,以及根據七牛雲端儲存自訂響應內容建立 post 執行個體:
def new @qiniu_upload_token = generate_qiniu_upload_token @post = Post.new end def create upload_ret = JSON.parse(Base64.urlsafe_decode64(params[:upload_ret])) @post = Post.new( title: upload_ret['title'], filename: upload_ret['fname'], qiniu_hash: upload_ret['hash'] ) # ... end private def generate_qiniu_upload_token put_policy = Qiniu::Auth::PutPolicy.new('konata') put_policy.return_body = { fname: '$(fname)', hash: '$(etag)', title: '$(x:title)' }.to_json put_policy.return_url = create_posts_url Qiniu::Auth.generate_uptoken(put_policy) end
編輯 config/routes.rb,將 create action 定義為使用 get 方法亦可訪問:
resources :posts do collection do get 'create', as: :create end end
重新啟動 rails server,訪問 http://localhost:3000/posts/new,現在我們已經可以在發表新 post 的時候上傳圖片至七牛雲端儲存。
提示:
檔案將會上傳到名為 konata 的公開空間(bucket)。
我們沒有在代碼中指定 key,七牛雲端儲存預設會使用根據檔案內容計算的 hash (etag) 值做為 key。這種做法可以非常簡單地避免內容相同的檔案儲存體多份浪費空間。
由於上傳表單將會直接提交到七牛雲端儲存體服務器,我們的應用程式後端無法獲得 title 等業務對象欄位,我們使用七牛雲端儲存 API 的 自訂變數 和 自訂響應內容 功能,通過七牛雲端儲存上傳 API 中轉獲得這些欄位。
展示使用者上傳的圖片
修改 app/helpers/application_helper.rb,添加 qiniu_image_url 方便產生圖片 URL。為了保持簡單我們直接寫入程式碼了空間的網域名稱:
def qiniu_image_url(post, format = :raw) url = "http://7xokus.com2.z0.glb.qiniucdn.com/#{post.qiniu_hash}" case format when :square url << '?imageView2/1/w/300/h/300/q/90' when :preview url << '?imageView2/2/w/1000/h/1000/q/90' when :raw url << "?attname=#{post.filename}" else url end end
修改 app/views/posts/index.html.erb 和 app/views/posts/show.html.erb,調用剛才建立的 URL helper 展示圖片:
<tr> <td><%= post.title %></td> <td><%= link_to image_tag(qiniu_image_url(post, :square), size: '300'), post %></td> <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr>
<p> <%= link_to image_tag(qiniu_image_url(@post, :preview)), qiniu_image_url(@post), class: 'image' %></p><%= link_to 'Back', posts_path %>
修改 config/routes.rb,將網站根目錄設定為 posts 列表頁面:
root 'posts#index'
訪問 http://localhost:3000,現在我們可以看到剛才上傳的圖片。
提示:
每個空間(bucket)內的資源都可以通過該空間的預設或自訂網域名,加上檔案 key 構造的 HTTP URL 進行訪問。
可以在 URL 後增加特定查詢參數,調用七牛雲端儲存強大的 資料處理(Fop) API,即時產生自訂格式的縮圖。
可以通過查詢參數 attname 指定 URL 下載時使用的檔案名稱。
簡單的 UI 美化
修改 app/views/posts/index.html.erb,將原有的 table 布局改為 flexbox 布局:
<div class="posts"> <% @posts.each do |post| %> <p> <%= link_to image_tag(qiniu_image_url(post, :square), size: '300'), post, class: 'image' %> <br> <%= post.title %> <br> <%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %> </p> <% end %></div>
修改 app/assets/stylesheets/application.css,加入對應的 CSS:
body { padding: 20px;}a.image:hover { background-color: transparent;}div.posts { display: flex; flex-flow: row wrap; justify-content: space-between;}
訪問 http://localhost:3000,圖片列表看起來像一個正常的相簿了。
添加使用者賬戶功能
我們的應用程式還有兩個明顯的問題:沒有記錄圖片是由誰分享的;任何人都可以刪除 post。這對於一個社交應用來說顯然是不能接受的問題。這對於一個社交應用來說顯然是不可接受的,所以接下來我們將使用 Rails 社區流行的 Devise 組件直接獲得使用者註冊、登入、驗證子系統,實現圖片發表者資訊記錄等功能。
修改 Gemfile,加入對 Devise 的依賴:
複製代碼 代碼如下:
gem 'devise'
執行以下命令安裝 Devise,產生 User model,以及我們所需的 data migration:
複製代碼 代碼如下:
bundle
rails generate devise:install
rails generate devise user
rails generate migration add_author_to_posts creator:belongs_to
rake db:migrate
修改 app/controllers/posts_controller.rb,對查看以外的操作要求登入,並在發表 post 時記錄發表者身份:
before_action :authenticate_user!, except: [:index, :show] def create # ... @post = Post.new( title: upload_ret['title'], filename: upload_ret['fname'], qiniu_hash: upload_ret['hash'], creator: current_user ) # ... end
修改 app/models/post.rb,添加與 User model 的從屬關聯:
複製代碼 代碼如下:
belongs_to :creator, class_name: 'User'
修改 app/views/layouts/application.html.erb,添加登入狀態資訊和登出連結:
<header> <% if user_signed_in? %> <p> Hello <%= current_user.email %> <%= link_to 'Logout', destroy_user_session_path, method: :delete %> </p> <% end %> </header>
修改 app/views/posts/index.html.erb,限制僅 post 發表者可以刪除該 post:
<% if user_signed_in? and post.creator == current_user %> <br> <%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %> <% end %>
重啟 rails server 後,訪問 http://localhost:3000,現在使用者需要註冊登入才能發表圖片了。
實現點贊功能
最後,做為一個社交應用,沒有點贊功能怎麼能讓點贊狂魔們滿足呢?!我們將添加一個 like scaffold 用來處理點贊和儲存點贊資訊。
執行以下命令產生 scaffold,Like model 將做為一個 join model 同時從屬於 Post 和 User:
複製代碼 代碼如下:
rails generate scaffold like post:belongs_to user:belongs_to
rake db:migrate
修改 app/models/post.rb,建立 Post 與 Like model 的“擁有/嵌套”關係:
複製代碼 代碼如下:
has_many :likes
修改 app/models/like.rb,限制一個點贊狂魔對一個 post 只能點一次贊:
複製代碼 代碼如下:
validates_uniqueness_of :user, scope: :post
修改 config/routes.rb,將 likes 設定為 posts 的嵌套資源:
複製代碼 代碼如下:
resources :posts do
# ...
resources :likes
end
修改 app/views/posts/index.html.erb,添加點贊資訊顯示以及 AJAX 點贊連結:
<%= post.title %> <br> <%= content_tag(:span, "#{post.likes.count} likes", id: "post_#{post.id}_likes") %> <% if user_signed_in? %> <br> <%= link_to 'Like', post_likes_path(post), method: :post, remote: true %> <% if post.creator == current_user %> <%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %> <% end %> <% end %>
修改 app/controllers/likes_controller.rb,真正將 likes 資源實現為 posts 的嵌套資源,並在點贊時記錄點贊狂魔的身份:
before_action :set_post def create @like = @post.likes.new(user: current_user) respond_to do |format| if @like.save format.js { } else format.js { } end end end private def set_post @post = Post.find(params[:post_id]) end
建立 app/views/likes/create.js.erb,使用 Server-generated JavaScript 更新點贊資訊:
複製代碼 代碼如下:
$("#<%= "post_#{@post.id}_likes" %>").text("<%= "#{@post.likes.count} likes" %>")
重啟 rails server 後,訪問 http://localhost:3000,讓我們愉快地點贊吧~
小結
雖然這隻是一個簡單的原型,但是因為使用了七牛雲端儲存作為圖片儲存後端,我們的社交應用產品在一開始的原型階段就擁有了能為大規模使用者提供高速、可靠服務的潛能。
簡單回顧一下我們剛才學習到的內容吧。使用 Rails 的代碼產生功能, 基於 CRUD 結構的 scaffold 實現業務對象維護,以及業務操作非常高效;使用七牛雲端儲存則可以輕鬆處理業務系統中的檔案儲存體,獲得圖片視頻等多媒體檔案的雲端處理能力,減少,甚至避免了部分研發和營運工作。希望這個視頻可以協助大家認識這兩個優秀的開發工具,直觀感受到它們的高效強大和簡單易用。
參考資料
Ruby on Rails Guides
七牛開發人員中心
範例程式碼
https://github.com/rainux/konata-sample
比起直接試用範例程式碼,你應該按照教程自己編寫這些代碼,親自編寫代碼有助於加深理解和記憶。