V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
rina
V2EX  ›  分享创造

如何从零构建个人博客系统

  •  1
     
  •   rina · 2017-12-25 10:54:12 +08:00 · 3897 次点击
    这是一个创建于 2285 天前的主题,其中的信息可能已经有所发展或是发生改变。

    简介

    这篇文章主要分享博客里涉及的 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

    10 条回复    2017-12-27 17:38:36 +08:00
    superlead
        1
    superlead  
       2017-12-25 13:53:43 +08:00
    不错,很好,不过我是用 python 捣鼓的
    cominghome
        2
    cominghome  
       2017-12-25 17:36:11 +08:00
    @superlead 有地址吗?参观一下。我也想元旦自己撸一个,不太了解大概要弄成啥样子。想模仿很多建站的模版感觉不够好
    superlead
        3
    superlead  
       2017-12-25 18:02:12 +08:00
    zhengxiaowai
        4
    zhengxiaowai  
       2017-12-26 12:54:33 +08:00
    Hexo 值得拥有

    安利一下我写主题 : https://github.com/zhengxiaowai/hexo-theme-lessless
    demo 看 https://hexiangyu.me
    permaylau
        5
    permaylau  
       2017-12-26 12:56:31 +08:00
    很不错,不过我还是青睐 wordpress,因为他丰富的主题、插件、版本迭代和遍布全球开发者,形成了一个很好的生态系统。
    zhaohao
        6
    zhaohao  
       2017-12-26 13:38:35 +08:00
    发现主题好亲切的感觉,Jekyll 的 Clean Blog 主题。

    秀下自己一迷糊注册的 能看 不好用 的域名 https://zhào.com ,等价于 https://xn--zho-bla.com
    rina
        7
    rina  
    OP
       2017-12-26 18:45:03 +08:00
    @zhaohao 是的,我上一篇文章有写到使用的技术栈,有兴趣可以了解一下:)
    wsph123
        8
    wsph123  
       2017-12-26 23:28:55 +08:00
    6666 ROR 代码这么漂亮的么 OAQ
    rina
        9
    rina  
    OP
       2017-12-27 10:09:45 +08:00
    @wsph123 ROR 代码一直很优雅
    qfdk
        10
    qfdk  
       2017-12-27 17:38:36 +08:00 via iPhone
    不用自己不懂的语言乱搞 :)

    我也发一波博客 https://t.i2.tn
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1137 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 18:42 · PVG 02:42 · LAX 11:42 · JFK 14:42
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.