如何從零構建個人部落格系統

Rina發表於2017-12-25

簡介

這篇文章主要分享部落格裡涉及的Ruby, Rails,前端CSS,JS,ubuntu系統命令等知識。如果有什麼不解的地方可以通過http://liuzhen.me頁面下方的二維碼掃描加我微信。

  • Ruby是一種純粹的物件導向程式語言。它由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)建立於1993年。

  • Ruby on Rails(官方簡稱為Rails,亦被簡稱為RoR),是一個使用Ruby語言寫的開源Web應用框架,它是嚴格按照MVC結構開發的。它努力使自身保持簡單,來使實際的應用開發時的程式碼更少,使用最少的配置。指南: https://ruby-china.github.io/rails-guides/

  • CSS 指層疊樣式表, 你在頁面看到的展示效果都是通過CSS做出來的,頁面的佈局,字型大小,顏色,邊框,選單等等. 詳情可以檢視: http://www.runoob.com/css/css-intro.html
  • JS 是屬於網路的指令碼語言, 能做的事太多了,像我部落格裡的相簿功能,時間線都是JS做出來的效果。

安裝Rails環境

你可以通過搜尋 Mac/windows/ubuntu install rails 來找到相關文件,這裡提供ubuntu 16.04版本的安裝文件: https://gorails.com/setup/ubuntu/16.04 , Mac的安裝文件: https://ruby-china.org/wiki/mac-nginx-passenger-rails

建立一個Rails專案

安裝好Rails環境之後,你可以建立一個Rails專案了,如果你從來沒用過Rails,可以先用15分鐘學習一下 Rails入門, 瞭解Rails MVC結構。

如果你對Rails有一定的瞭解,可以按照這個模版 https://github.com/80percent/rails-template 提供的操作步驟, 建立一個Rails專案,使用這個模版建立Rails專案的好處是,這個模版相當於一個全家桶,預先新增一個專案經常需要使用的Gem包,釋出需要的puma, mina, monit, nginx配置檔案,關於這幾個東西是什麼,有什麼用後面會講到。

啟動Rails

$ rails s

訪問 localhost:3000 就能看到 hello world 頁面了。

建立資料模型

我的部落格在設計之初只想要文章,相簿,簡歷這幾個功能,這三個功能比較相似,都有標題,內容和可有可無的描述。所以我就用了單表繼承,建了個base表。

$ rails g model Base title:string content:text subtitle:string type:string

type欄位就是用於單表繼承。

執行完這條命令之後,你會看到 db/migrate/xxxx_create_bases.rb 多了一個這樣的檔案,裡面的內容是:

class CreateBases < ActiveRecord::Migration[5.1]
  def change
    create_table :bases do |t|
      t.string :title
      t.string :subtitle
      t.text :content
      t.string :type
      t.timestamps
    end
  end
end

t.timestamps 是時間戳,系統會自動在這個表裡面加上 created_at, updated_at 兩個欄位。

新增完之後,需要把做一下資料遷移,我一開始學rails的時候對 資料遷移 這個詞很不理解。其實資料遷移的意思就是,我們現在通過命令建立了個資料表的檔案,但是這個檔案沒有被執行,不執行資料庫裡就還沒有這張表,只有在執行了 rails db:migrate 之後,rails才在資料庫裡把這張表給加上,這個操作就叫做 資料遷移。

建立完了Base表,現在就要建立文章表了 Article, 我們需要新增一個 app/models/article.rb 檔案,寫上:

class Article < Base
end

因為Article繼承了Base, 所以就擁有了Base的所有欄位了。

你可以通過 rails c 從控制檯輸入 Article.new 可以看到:

irb(main):006:0* Article.new
=> #<Article id: nil, title: nil, subtitle: nil, content: nil, type: "Article", created_at: nil, updated_at: nil>

type欄位自動就是Article, 這是Rails的一個特性,單表繼承。

輸入Article.all, 看到的sql語句實際是從bases裡查詢type為Article的所以記錄。

irb(main):007:0> Article.all
Article Load (127.0ms)  SELECT  "bases".* FROM "bases" WHERE "bases"."type" IN ('Article') LIMIT $1  [["LIMIT", 11]]

相簿表Photo,簡歷表ResumeArticle的建立方式相同.

表建立好了,我們就可以建立Controller了,Controller需要區分前端和後端,前端就是提供給使用者查詢的,後臺是提供自己新增,更新,刪除操作的。另外後臺因為是管理的地方所以不能讓所有人都訪問,所以需要設定成通過使用者名稱和密碼登入。這樣別人就無法訪問你的後臺。

後端設計

為了與前臺有所區分,所以需要加一下名稱空間: 這裡設定成 admin. 先在 config/routes.rb 裡新增路由,

Rails.application.routes.draw do
  namespace :admin do
    root 'dashboard#index', as: 'root'
    resources :articles
    resources :photos
    resource :resume, only: [:edit, :update]
  end
end

root 'dashboard#index', as: 'root' 設定後臺的root路由。使用 rails routes 命令可以檢視具體的路由資訊。

控制器

controllers 目錄下新增admin目錄,這個目錄下用於存放所有的後臺檔案,後臺新增一個 app/controllers/admin/base_controller.rb 檔案,繼承了 ApplicationController, 這麼做是為了admin下的所有controller繼承 Admin::BaseController 後,使用者訪問後端連結就會先校驗當前使用者是否登入,如果沒有登入就跳轉到登入頁面。 程式碼如下:

class Admin::BaseController < ApplicationController
  layout 'admin'
  before_action :authenticate_user
  def authenticate_user
    unless session[:login]
      redirect_to new_session_path
    end
  end
end

因為article, photo, resume這幾個功能比較相似,所以我只講一下article控制器:app/controllers/admin/articles_controller.rb

程式碼:

class Admin::ArticlesController < Admin::BaseController
  def index
    @articles = Article.all.order(created_at: 'DESC').page(params[:page])
  end
  def new
    @article = Article.new
  end
  def create
    @article = Article.new(article_params)
    if @article.save
      redirect_to admin_articles_path
    else
      render 'new'
    end
  end
  def edit
    @article = Article.find(params[:id])
  end
  def update
    @article = Article.find(params[:id])
    if @article.update(article_params)
      flash[:notice] = '更新成功'
      redirect_to admin_articles_path
    else
      render 'edit'
    end
  end
  def destroy
    @article = Article.find(params[:id])
    if @article.destroy
      flash[:notice] = '刪除成功'
    else
      flash[:notice] = "刪除失敗, 原因: #{@article.errors.messages.to_s}"
    end
  end
  private
  def article_params
    params.require(:article).permit(:title, :subtitle, :content)
  end
end

控制檯裡面很簡單,就是增,刪,改,查。需要注意的就是redirect_to, render的區別,什麼時候要用render, 什麼時候用redirect_to.

render 是指直接薰染某個頁面.

redirect_to 是指告訴瀏覽器,讓瀏覽器再重新傳送一個指定路由的請求操作。

如:create action 裡寫到如果儲存成功就 redirect_to admin_articles_path, 如果失敗就 render 'new'.

  1. 假如儲存成功,就會告訴瀏覽器, 讓瀏覽器再向伺服器傳送一個admin/articles路由請求,然後進入index action裡,查詢所有Action記錄,再薰染index.html頁面,返回給瀏覽器。

  2. 假如儲存失敗,就用使用者填寫的@article資訊薰染new.html頁面,並用flash裡的資訊,告訴使用者提交失敗的原因,如果失敗後用 redirect_to new_admin_articles_path,也能跳轉到new頁面,但是使用者提交的資訊就沒有了。

view

.row
  .offset-md-2.col-md-8
    = simple_form_for [:admin, @article] do |f|
      = f.error_notification
      = f.input :title
      = f.input :subtitle
      = f.text_area :content, id: 'editor_content', class: 'simditor', autofocus: true
      = f.submit '提交', class: 'btn btn-primary'
      = link_to '取消', admin_articles_path
javascript:
  new Simditor({
    textarea: $('#editor_content'),
    toolbar: ['title', 'bold', 'italic', 'underline', 'strikethrough', 'fontScale', 'color', '|', 'ol', 'ul', '|', 'blockquote', 'code', 'table', 'link', 'image', 'hr', 'indent', 'outdent', 'alignment']
  });

這個有點需要講的是編輯器使用了simditor外掛,具體要加哪些資訊可以看一下這個文件: http://simditor.tower.im/, 但是要支援上傳圖片功能需要在admin下新增一條路由: post '/upload', to: 'photos#upload', 在photos controller裡新增一個upload action,把上傳的圖片儲存到資料庫。

前端設計

前端的controller繼承ApplicationController,前端的因為只設計到查詢,所以新增路由的時候加上only, 如: resources :articles, only: [:index, :show], 就只新增兩條路由,如果不加only預設會建立 7 條路由。

前端功能主要就涉及到css.

css除錯步驟:

  1. 右擊選擇'檢查',就能開啟控制檯,在控制檯處,通過點選(2) 處的圖示,可以選擇頁面上任意節點,選擇後(3)處會顯示這個節點所對應的CSS樣式。同樣在style處可以通過新增和註釋css來對頁面樣式進行除錯。

樣式這裡涉及的東西太多,我不一一講解,只講一些我認為值得講一講的知識點。如果想學習更多的css樣式知識,可以在文章開頭處提供的文件檢視學習。

  1. 文章的展示對字型,間距,背景,顏色等等都要求很高,如果設計的不好,文章看久了就容易累,而且容易給別人一種不想去看的感覺。如果間距很窄,一大段落全是文字,就給人一種很不舒服的感覺。如果你對這些資訊瞭解不多,不知道把這些值設定成多少比較好,也不要擔心,找一個你覺得文字展示效果看起來很舒服的網站,開啟他的控制檯,看一下這個網站上這些資訊設定的值是多少,跟著一樣設定就行了。具體的細節可以再另做調整。

  2. 部落格的頁面底部用的fa字型,在gemfile裡新增 font-awesome-sass 後,就能展示出這些字型圖示。但是目前的字型中沒有支付寶的字型圖示,你先不要看程式碼,想一想,如果是你,你要怎麼實現一個跟fa字型相同效果的圖示,這個圖示帶有hover效果,當滑鼠放上去的時候背景變成了藍色。

我的實現方法:

一開始我想的是用一個黑白圖片代替,弄完之後我發現hover效果無法實現。於是我就用一個背景透明只有一個支字的圖片代替,設定border-radius,background-color和字型達成一致效果,當滑鼠放上去的時候就改變background-color: #0085A1;

程式碼:

footer .fa-alipay {
    border-radius: 50%;
    margin-bottom: 4px;
    background-color: #222529;
    width: 41px;
}
footer .fa-alipay:hover {
    background-color: #0085A1;
}

除錯頁面上關於hover,visited, focus, active效果,可以像圖片中勾選來檢視相應的樣式效果。

時間線

時間線是用的一個js庫,https://github.com/RyanFitzgerald/vertical-timeline, 具體可以檢視文件。值得說一下的是,一開始看到這個時間線的效果是在一個網站看到的。然後我通過頁面控制檯,看到裡面class名稱命名很規範,所以感覺是個js庫,直接在google搜尋 cd-timeline-block 第一個結果就是這個庫的資訊。除了這種方式,還可以通過控制檯的Sources檢視assets檔案資訊,一般都是經常壓縮的,但是有些外部庫是有註釋的,會寫上這個是來自哪個庫之類的資訊。不過最簡單快速的辦法還是用google搜尋來的快一點。如果你搜的class名字沒有找到相應的資訊,可以換個class名字試試。

另外一個要說的是,這個JS庫裡的一個js檔案main.js, 與turbolink一起載入,沒生效,載入的時候就沒被執行,然後我就把它用$(document).on 'turbolinks:load', 載入就好了。

相簿

部落格裡我最喜歡的就是這個相簿功能了,當初也是看了這個翻書的效果,我才有重寫部落格的衝動。看到這個js庫是在github Trending上, 這上面會推薦github上比較火的專案。這個庫的地址: http://www.turnjs.com, 這裡面提供了幾個demo. 這個turnjs用的 yepnope 載入js,這麼載入是因為,有些內容需要在其他檔案載入之後去執行。但是有個問題是在生產環境這些js檔案都是被轉譯了的。所以直接在yepnope裡面寫上檔名,在生產環境上就會找不到對應的檔案。對於這個我沒有想到特別好的處理辦法,就用

$('head').append('<%= javascript_include_tag 'turn.min.js' %>')

來載入檔案,然後再執行yepnope({complete: loadApp})。如果你有更好的辦法可以交流一下。

其他知識點

  • 效果支援手機端頁面需要加上: meta width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no

  • 前端和後端的JS, CSS儘量分開,這樣載入速度會快一些

  • 使用的外部庫最好重新命名成可讀名稱, 不然時間長了你就不知道這個庫是幹什麼的了. 比如我用了 timeline 的JS庫, 裡面有個main.js的檔案, 我就把它重新命名為 timeline-main.js, 這樣不需要再加註釋來說明這個檔案是做什麼的了.

釋出

釋出需要在伺服器上安裝好rails後,配置nginx.conf,一般放在/etc/nginx/conf.d/xxx.conf檔案.

puma.rbdeploy.rb 配置檔案是mina部署的相關配置資訊,具體操作了什麼可以通過bundle exec mina deploy -v來檢視:

$ bundle exec mina deploy -v
-----> Creating a temporary build path
-----> Server: liuzhen.me
-----> Path: /home/ruby/RBlog
       $ echo "-----> Branch: master"
-----> Branch: master
-----> Using RVM environment "2.3.1"
-----> Quiet sidekiq (stop accepting new work)
-----> Fetching new git commits
       $ (cd "/home/ruby/RBlog/scm" && git fetch "https://github.com/liuzhenangel/RBlog.git" "master:master" --force)
-----> Using git branch 'master'
       $ git clone "/home/ruby/RBlog/scm" . --recursive --branch "master"
       Cloning into '.'...
       done.
-----> Using this git commit
       $ git rev-parse HEAD > .mina_git_revision
       $ git --no-pager log --format="%aN (%h):%n> %s" -n 1
       liuzhenangel (eb06b54):
       > update timeline
       $ rm -rf .git
-----> Symlinking shared paths
-----> Installing gem dependencies using Bundler
       $ bundle install --without development test --path "vendor/bundle" --deployment
-----> DB migrations unchanged; skipping DB migration
-----> Skipping asset precompilation
-----> Cleaning up old releases (keeping 5)
-----> Deploy finished
-----> Building
-----> Moving build to /home/ruby/RBlog/releases/41
-----> Build finished
-----> Launching
-----> Updating the /home/ruby/RBlog/current symlink
-----> Restart Puma -- hard...
-----> Stopping Puma...
-----> Starting Puma...

從這些日誌資訊可以看出, 首先會根據你配置的伺服器的域名和使用者名稱ssh到伺服器上,載入ruby環境,從github拉取最新程式碼,安裝gem包,如果css, js, 圖片有更新就重新編譯壓縮js, css, 圖片, 執行 db:migrate 資料遷移, 首次釋出還會執行rails db:create來建立資料庫。然後重啟puma.

為什麼要nginx,puma

nginx相當於一個代理,當你在瀏覽器輸入:http://liuzhen.me 的時候,先通過DNS找到這個域名對應的IP,然後通過路由到達IP所在的伺服器上,伺服器發現使用者請求的是 liuzhen.me,然後nginx就根據配置檔案裡配置的 liuzhen.me 找到對應的專案,這個Rails專案是由puma啟動的,然後nginx就把這個事交給puma, puma收到後就根據路由去到對應的controller,然後返回對應的頁面給到瀏覽器。

monit 是什麼

沒有monit也能釋出成功,但是有時候你的puma可能異常關掉了,如果有monit的話,就能時刻監聽這個程式是不是啟動狀態,一旦關閉了,就重新啟動。

部落格地址: http://liuzhen.me

部落格程式碼: https://github.com/liuzhenangel/RBlog

原文來源: http://liuzhen.me/articles/16

相關文章