常常在各類論壇、博客還有 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
- Sass有一些成熟穩定的框架,如是Compass,Foundation,側面說明 Sass 比 Less 更好吧。
- 還有一個緣由是國外討論Sass的同行要多於Less,使用 Sass 的也更多一些。
- 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 從新構建一下這個項目,親自感覺一下它們的魅力。
項目的總體流程
- 肯定功能,根據需求選擇合適的框架和工具
- 設計界面、架構、數據庫的模式
- 構建 express 的服務器框架和目錄結構
- 設計 gulp task 和 livereload
- 編寫頁面
- 測試功能模塊
- 作必要的性能優化
遇到的難點和坑
總結這個問題,博主真的感受好爲難。以博主的性格,作以前會以爲:「這個東西沒作過,好難,但查查資料今天上午必定能作出來」,作完了之後總以爲:「沒想到挺簡單的!!!」因此說呢,如今作完了,感受都不難了。
其實最難的應該屬於快速學習並使用這些工具,快速查找並解決問題。博主用了一個月的每一個晚上和週末學習了 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, '<'); temp = temp.replace(/>/g, '>'); 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個基本部分,前臺是給用戶看的,花的時間比較多,還有就是安全,這個不能大意。
- 前臺效果,這個你們都能看到,不說了:
- 固然,前臺還有移動端:
- 後臺效果,東西很少,界面很簡潔,不少東西還須要逐步添加: