我的博客主頁搭建隨筆

常常在各類論壇、博客還有 github 上活躍的朋友不難發現,許多大牛都有本身的網站,也多以博客爲主。博主做爲一個立志前端的大白,難道不該該和大牛學習麼?

說幹就幹,前端部分和 web 開發博主作了不少學習和總結,很多也寫成了博客。對於後端,博主不敢說徹底沒有經驗,但接觸的也都比較簡單。因而乎,博主去年六月底開始看 Node 和 Express,利用空閒時間作了本身的博客,現階段還有許多不足,須要後續不斷改進。不過這不妨礙博主先總結一下本身的感覺。css

UI設計 和 架構設計

博主深入的感覺到本身並無什麼藝術細胞,只能去看各類設計稿刺激一下本身的神經,好比 Dribbble、water Chen UI、 千庫、 DotSpin、LogoSpire、Tencent ISUX 等等。其實不止這些,比較我看見的連接就會點進去,不知道點了多少,而後就這麼憋出了一份入門級都算不上的 UI 設計圖(不要笑,要指點)。html

這裏寫圖片描述

對,就是這麼簡單粗暴,連 ps 都沒用!前端

架構設計部分也沒有太深刻的知識,簡單的 MVC 結構,對於架構入門來講再適合不過了,即便如今的前端,有了 MV 結構的 React,MVVM 的 Angular 和 vue。架構以下圖,此次終於用到 ps,其實找個 word 就能夠畫的。vue

這裏寫圖片描述
(仍是這句話:不要笑,要指點)node

框架選型

因爲本身尚未看到Angular、vue、React這寫流行的框架,前端樣式的實現仍是選擇使用了 Bootstrap 和 Sass。而結構選擇了 jade 模板引擎。這裏會有如下幾個問題:webpack

爲何使用 Bootstrap ?git

這個不復雜,畢竟我但願實現兼容移動端和 PC 端的響應式頁面,因此我只能說,媒體查詢是利器,Bootstrap 是別無可選。github

爲何我使用 Sass ?web

寫習慣了 css 就不太習慣 Scss 的縮進,畢竟還有 less 和 Sass, 而選擇了 Sass 的緣由以下:shell

  1. Sass有一些成熟穩定的框架,如是Compass,Foundation,側面說明 Sass 比 Less 更好吧。
  2. 還有一個緣由是國外討論Sass的同行要多於Less,使用 Sass 的也更多一些。
  3. Sass有更清晰的語法,像一門編程語言同樣,不像 Less 的混合大法使用起來感受不舒服。

若是說 Sass 有麼很差,那就是它仍是創建在 ruby 上,有點不直接,比較麻煩。

爲什麼使用 jade ?

這個不須要太多理由,比 html 簡潔,還比 html 強大!比起其餘模板引擎,他又是專門爲 html 服務的。和 EJS 相比,jade 支持模板繼承,而 EJS 不支持。
其實我是我的開發,若是是團隊開發,我仍是以爲 EJS 會更好,多是博主適應了挖坑的寫法,用 html 會對先後端開發都方便許多。並且 jade 的縮進風格跨 IDE 會不方便。固然還有其餘的模板引擎,好比 swig,對博主來講徹底是不習慣 Django 的風格。

固然這裏還有個其它緣由:Express 默認 jade !!!

後端開發使用了 Nodejs + Express + MongoDB, 不得不說這個組合的資料真的不少,學習起來方便一些。這裏沒有爲何,就是想學 Node,而不是世界上最美的語言,畢竟有 js 基礎嘛。

爲什麼使用 MongoDB ?

說實在的,這是博主第一次接觸並使用非關係數據庫,忽然發現很喜歡它。以前使用的更多的是 MySQL。畢竟博客系統,存儲的數據是文章,因此關係數據庫定義數據類型實在是個坑,分配大了浪費,分配小了怎麼寫東西!固然,還有其餘的問題。因爲我發現許多文章都使用 MongoDB,其實也就沒有再猶豫。這裏想要說的仍是使用它的感覺,一句話歸納:JSON 的存儲方式真的是既直接,又爽快!

爲什麼使用 Express ?

咱們都知道,Express 其實不能說是對 Node 的二次開發,而是一個擴展。博主開始學習前端,從基礎作起來是不會有錯的,若是你但願快速的開發,僅僅當它是個工具,那用 sail 會省事不少,再配合腳手架,事半功倍。固然 sail 除了官方文檔好像也沒啥別的能夠學習了,這個......
其實 sail 發展不起來也是有緣由的:

固然,若是有時間,博主倒還想學習一下,koa 和 hapi

最後一個選擇就是項目構建工具了,這裏能夠直接解釋我爲什麼使用了 Gulp

爲何選擇 Gulp ?

其實這裏可選的經常使用構建工具還有 webpack 和 grunt。gulp 和 webpack 其實各有優點,好比 gulp 任務定義和管理,Webpack 作不到,Webpack 基於模塊的依賴構建。gulp 也作很差。而 grunt 的確是比較笨重複雜,明顯能看出來如今用的已經比較少了。
其實 gulp 和 webpack 使用的感受也不同,gulp 是在寫一個個的任務,而 webpack 是在寫一個配置文件,注意我說的是感受。
從他們的核心功能來說,Gulp 定義任務和阻止,基於文件流的構建。而 webpack 按照模塊的依賴構建目標文件,具備支持不一樣模塊的 loader 體系。若是要用 Webpack,請確保項目模塊化,模塊之間充分依賴。除此以外的構建工做,都應該交給 gulp 繼續完成。博主目前的這個項目還不算很大,模塊依賴簡單,但指望完成諸如版本號替換,壓縮代碼,合併文件,發佈到服務器等和模塊化關係不大的工做,因此使用了 gulp。

博主使用 Gulp 也是本身的學習過程,正是由於 Gulp 和 Webpack 各有優缺點,下一步博主仍是計劃能用 Webpack 從新構建一下這個項目,親自感覺一下它們的魅力。

項目的總體流程

  1. 肯定功能,根據需求選擇合適的框架和工具
  2. 設計界面、架構、數據庫的模式
  3. 構建 express 的服務器框架和目錄結構
  4. 設計 gulp task 和 livereload
  5. 編寫頁面
  6. 測試功能模塊
  7. 作必要的性能優化

遇到的難點和坑

總結這個問題,博主真的感受好爲難。以博主的性格,作以前會以爲:「這個東西沒作過,好難,但查查資料今天上午必定能作出來」,作完了之後總以爲:「沒想到挺簡單的!!!」因此說呢,如今作完了,感受都不難了。

其實最難的應該屬於快速學習並使用這些工具,快速查找並解決問題。博主用了一個月的每一個晚上和週末學習了 Nodejs + Express + MongoDB + Gulp, 作了這個博客,能夠說大體理解他們的開發邏輯,結構,和基本使用方法。不少難以解決的問題,也無非是 google、知乎、github。

  • 登陸功能實現

登陸功能,利用了 passport 和 validator 組件實現,首先是驗證登陸,在 user model 中實現方法 verifyPassword

UserSchema.methods.verifyPassword = function(password){
  return password === this.password;
}

而對於須要驗證的頁面,在 user 的 controller 中實現了 中間件:

var needLogin = function(req, res, next){
  if(req.user){
    next();
  } else {
    req.flash('error', '請先登陸');
    res.redirect('/admin/users/login');
  }
}
module.exports.requireLogin = needLogin;

對於與 validator 在 express.js 中按要求添加 use 就能夠啦。

app.use(validator({
    errorFormatter: function(param, msg, value) {
        var namespace = param.split('.')
        , root    = namespace.shift()
        , formParam = root;

      while(namespace.length) {
        formParam += '[' + namespace.shift() + ']';
      }
      return {
        param : formParam,
        msg   : msg,
        value : value
      };
    }
  }));

加密使用了 md5, 這個很少說了。還有一點就是登陸狀態驗證:在服務器端,登陸狀態被存儲在數據庫的 sessions 集合中,經過 passport 和 express-session 實現:

app.use(session({
    secret: 'nodeblog',
    resave: false,
    saveUnitialized: true,
    cookie: {secure: false},
    store: new MongoStore({ mongooseConnection: connection })
  }));
  app.use(passport.initialize());
  app.use(passport.session());
  app.use(function(req, res, next){
    req.user = null;
    if(req.session.passport && req.session.passport.user){
      User.findById(req.session.passport.user, function(err, user){
        if(err) return next(err);
        user.password = null;
        req.user = user;
        next();
      })
    } else {
      next();
    }
  })
  • markdown 和內容驗證

這個功能以前去找了 github 中 'markdown' 排第一的 node 插件 marked 插件實現。關於插件這裏很少說了。這裏重點說一下的內容驗證,由於內容驗證是安全性保障的重要一個環節。這裏即要保障惡意腳步不會被提交,或運行,並且還要保障 markdown 能夠很好的轉換數據。對於輸入的字符串處理,博主本身實現了幾個工具函數:

var clearUtil = function(app){};

// 清除 XML 標籤
clearUtil.clearXMLTags = function(str, deeply){
  var deeply = deeply || false;
  var temp = str.replace(/<[^>]+>/g, '');
  if(deeply){
    temp = temp.replace(/[\r\n][\r\n]+/g, '');
  }
  return temp;
};

// 清除 script 標籤及內容
clearUtil.clearScripts = function(str){
  return str.replace(/<script>.*?<\/script>/ig,'');
};

// 清除換號符 CR/LF
clearUtil.clearReturns = function(str){
  return str.replace(/[\r\n]/g, '');
};

// 轉義 xml 尖括號
clearUtil.TransferTags = function(str){
  var temp = str.concat();
  temp = temp.replace(/</g, '&lt;');
  temp = temp.replace(/>/g, '&gt;');
  return temp;
};

module.exports = clearUtil;
  • 性能優化

這個部分,主要工做是代碼壓縮,和緩存利用。
壓縮方法都是十分主流的方法,利用
imagemin, clean-css, uglify, 壓縮了圖片,css 和 js, 同時網頁以 gzip 格式傳輸,減少體積。
同時,對 css 和 js 附加緩存,配合 E-Tag 和 版本號實現服務器更新,這一部分其實 Express 已經幫咱們實現好了。因此這個部分,主要十幾個 gulp 配置

gulp.task('sass', function () {
  gulp.src('./source/css/**/*.scss')
    .pipe(autoprefix())
    .pipe(sass())
    .pipe(clearCss())
    .pipe(gulp.dest('./dist/css'))
    .pipe(browserSync.reload({stream:true}));
});
gulp.task('imagemin', function(){
  gulp.src('./source/img/*.{png,jpg,gif,ico}')
    .pipe(imagemin())
    .pipe(gulp.dest('./dist/img'));
});
gulp.task('minifyjs', function() {
  return gulp.src('./source/js/**/*.js')
    .pipe(rename({suffix: '.min'}))
    .pipe(uglify())
    .pipe(gulp.dest('./dist/js'));
});
  • 偷懶配置

博主用了一個 CentOS 的服務器,其實經過 scp 和 ssh 就能夠實現上傳和配置了,但依據「進步源於懶惰」的碼農提升準則,博主仍是作了一個本身上傳部署的功能:

/* part of gulpfile.js */
var shell = require('gulp-shell');
var ssh = require('gulp-ssh');
var deployConfig = require("./deploy-config");
var gulpSequence = require('gulp-sequence');
var zip = require('gulp-zip');
var through = require('through2');
var async = require('async');
var scpClient = require('scp2');
var gulpUtil = require('gulp-util');
var deploySSH = require('./deploy-ssh');

//上傳功能實現主任務
gulp.task('up', function (){
  shell.task(['rm -rf publish']);
  gulpSequence('copyFile', 'zipFile', 'deploy',  function() {
    gulpUtil.log(PLUGIN_NAME, "***** Deploy Finished!!!!");
    process.exit(0);
  });
});

gulp.task('copyFile', function() {
  return gulp.src([
    'node_modules/**',
    '*.json',
    '*.js',
    'app/**',
    'config/**',
    'dist/**',
    '!config.js'
  ], { base: './'})
  .pipe(gulp.dest('./publish'));
});

gulp.task('zipFile', function() {
  return gulp.src(['publish/**'], { base: './' })
    .pipe(zip('publish.zip'))
    .pipe(gulp.dest('./publish'));
});

gulp.task('deploy', function() {
  var config = deployConfig.production;
  config.deployPath = '/home/faremax/website/publish/';
  return gulp.src("publish/publish.zip", { base: './' })
    .pipe(deploySSH({
    servers: config.servers,
    dest: config.deployPath + 'publish.zip',
    logPath: 'deploy',
    shell:[
      'cd ' + config.deployPath,
      'shopt -s extglob',
      'rm -rf !(logs|node_modules|config.js|publish.zip)',
      'unzip -o publish.zip',
      'cp -rf publish/** .',
      'rm -rf publish',
      "rm publish.zip",
      'npm install --production',
      'pm2 startOrRestart pm2-start.json'
    ],
  }));
});
/* deploy-ssh.js */
var gulp = require('gulp');
var gutil = require('gulp-util');
var through = require('through2');
var ScpClient = require('scp2').Client;
var ssh = require('gulp-ssh');
var async = require('async');
var ProgressBar = require('progress');

const PLUGIN_NAME = 'deploy-ssh'

module.exports = function (options) {
  var servers = options.servers;
  var dest = options.dest;
  var shell = options.shell;
  var logPath = options.logPath;

  return through.obj(function (file, enc, callback) {
    if (file.isNull()) {
      callback(null, file);
      return;
    }

    if (file.isStream()) {
      return callback(new gutil.PluginError(PLUGIN_NAME, 'No stream support'));
    }

    var i = 0;
    async.eachSeries(servers, function(server, done) {
      var hostName = server.sshConfig.host;
      gutil.log(PLUGIN_NAME, "start deploy:" + hostName)
      var client = new ScpClient(server.sshConfig);

      var bar = null;
      client.on("transfer",  function(buffer, uploaded, total){
        if(bar == null){
          bar = new ProgressBar(hostName + ' uploading [:bar] :percent :elapsed s', {
            complete: '=',
            incomplete: ' ',
            width: 50,
            total: total
          });
        }
        bar.tick(1);
      });

      client.write({
        destination: dest,
        content: file.contents
      }, function () {
        ssh(server).shell(shell, {filePath: logPath + "-" + hostName + ".log", autoExit: true}).on('error', function (err) {
          done(err);

          gutil.PluginError(PLUGIN_NAME,  err)
        }).on('finish', function () {
          gutil.log(PLUGIN_NAME, "finish deploy:" + hostName);

          done();

          if (++i === servers.length) {
            callback(null, file);
          }
        }).pipe(gulp.dest('logs'));
      });
    });

  });

};
/* deploy-config.js */
var config = {
  production:{
    servers:[
    {
      sshConfig: {
        host: '114,214,132,211',
        port: 80,
        username: '******',
        password: '******',
        readyTimeout: 600000
      }
    }]
  }
};

module.exports = config;

網站實現效果

從功能上來看,其實僅僅實現了博客展現、博客的管理和分類管理3個基本部分,前臺是給用戶看的,花的時間比較多,還有就是安全,這個不能大意。

  • 前臺效果,這個你們都能看到,不說了:

這裏寫圖片描述

  • 固然,前臺還有移動端:

這裏寫圖片描述

  • 後臺效果,東西很少,界面很簡潔,不少東西還須要逐步添加:

這裏寫圖片描述

相關文章
相關標籤/搜索