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

简介

这篇文章主要分享博客里涉及的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,简历表Resume和Article的创建方式相同.

表创建好了,我们就可以创建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'.

假如保存成功,就会告诉浏览器, 让浏览器再向服务器发送一个admin/articles路由请求,然后进入index action里,查询所有Action记录,再熏染index.html页面,返回给浏览器。

假如保存失败,就用用户填写的@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调试步骤:

右击选择'检查',就能打开控制台,在控制台处,通过点击(2) 处的图标,可以选择页面上任意节点,选择后(3)处会显示这个节点所对应的CSS样式。同样在style处可以通过添加和注释css来对页面样式进行调试。

样式这里涉及的东西太多,我不一一讲解,只讲一些我认为值得讲一讲的知识点。如果想学习更多的css样式知识,可以在文章开头处提供的文档查看学习。

文章的展示对字体,间距,背景,颜色等等都要求很高,如果设计的不好,文章看久了就容易累,而且容易给别人一种不想去看的感觉。如果间距很窄,一大段落全是文字,就给人一种很不舒服的感觉。如果你对这些信息了解不多,不知道把这些值设置成多少比较好,也不要担心,找一个你觉得文字展示效果看起来很舒服的网站,打开他的控制台,看一下这个网站上这些信息设置的值是多少,跟着一样设置就行了。具体的细节可以再另做调整。

博客的页面底部用的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.rb,deploy.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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容

  • Awesome Ruby Toolbox Awesome A collection of awesome Ruby...
    debbbbie阅读 2,805评论 0 3
  • "15分钟搭建一个博客系统",是Ruby On Rails非常经典Demo练习。rails的创始人DHH当年用一个...
    vito1994阅读 2,698评论 3 19
  • 很怀疑,是不是黑马河的日出花光了我去茶卡盐湖的好运气。(据说好多游客在黑马河逗留数日,都未能见一次日出)从黑马河出...
    史文泓阅读 342评论 5 2
  • 十堰是我的家乡,是一座美丽的城市。他仙山秀水,景色宜人,因为汽车而闻名中外,所以又叫汽车城。 武当山在十堰...
    浅浅的童话阅读 301评论 0 0
  • 感情能值几个钱?这一问题,我把它抛给了无所不能的百度来解决,输完这七个字,答案一涌而现。最醒目的答案,我通常会看页...
    日槿阅读 1,227评论 0 1