一個基於vue2、koa2和mongodb的部落格

小深刻的秋鼠發表於2019-03-03

部落格終於差不多寫完了,雖然還是可能有一堆bug, 不過我迫不及待要寫一篇博文來分享了= =

部落格前臺展示

一個基於vue2、koa2和mongodb的部落格
部落格前臺展示

部落格後臺展示

一個基於vue2、koa2和mongodb的部落格
部落格後臺展示

一個基於vue2、koa2和mongodb的部落格
部落格後臺編輯

專案地址

大家一起來star、fork呀,歡迎提出各種改進意見,我知道肯定還有一堆問題?
域名還沒有備案,後面就變成imhjm.com?,敬請期待

專案起源

其實我一直在使用hexo搭建的部落格,也一直用得還挺順手,但是它只是個靜態站點,當我需要一些定製化的需求,我沒辦法做更多的改變,並且搭在github page上有時不穩定,而且覺得搭建一個網站然後自己來做各種優化是一件cool的事情,可以嘗試各種新技術,可以接觸到各個層面上的優化,所以寫一個部落格的念頭就開始了。
其實vue-blog這個專案開了好久了,但是因為斷斷續續開發,過一段時間就覺得之前的程式碼寫得不好就開始重寫,而且也是邊寫來邊學習新技術,vue2全家桶,koa2, webpack2,這些都是之前我沒怎麼了解的技術,但是至少通過這個部落格專案,我對它們也有了新的認識
這裡也要感謝一下@Ma63d(@chuckliu),部落格搭建初期學習了很多他的kov-blog程式碼,學習到很多東西,也讓自己的後期實現得比較順利

專案技術細節

就是這個圖啦~

一個基於vue2、koa2和mongodb的部落格
部落格整體架構

但是當然還有更多細節

client端admin部分

這是部落格後臺,其實我的想法就是實現一個文字編輯器,有個左邊欄,然後右邊欄編輯,為了避免複雜,我只用了兩個頁,一個登入頁,然後一個就是帶有編輯器的列表頁

鑑權

統一使用axios來通訊,鑑權使用jwt,login頁中登入成功存入token,然後進入編輯頁,通過vue-router的beforeEach鉤子加入

Axios.defaults.headers.common['Authorization'] = 'Bearer ' + store.state.auth.token;複製程式碼

然後服務端接收時驗證token來決定是否鑑權成功,失敗時Axios統一攔截,刪除store裡存取的token, 通過vue-router再回到登入頁

狀態管理

列表和編輯器分成了兩個元件,用vuex統一管理狀態,通過action取/存然後mutaion修改狀態即可,但是這部分邏輯較多,具體實現就不展開描述了

// editor部分state
const state = {
  articleList: [],
  tagList: [],
  currentArticle: {
    id: -1,
    index: -1,
    content: '',
    title: '',
    tags: [],
    save: true,
    publish: false
  },
  allPage: 1,
  curPage: 1,
  selectTagArr: []
};複製程式碼
// auth部分state
const state = {
  token: sessionStorage.getItem('token')
};複製程式碼

編輯器/markdown

受@chuckliu安利同樣使用了simplemde-markdown-editor,然後解析和高亮部分使用了marked.jshighlight.js

client端front部分

event bus

因為考慮到前臺需要更快的載入速度,而且邏輯也比較簡單,就放棄使用vuex,採用vue event bus來實現非父子元件間的通訊

// 在main.js定義全域性event bus,不使用vuex管理
var EventBus = new Vue();
Object.defineProperties(Vue.prototype, {
  $eventBus: {
    get: function() {
      return EventBus;
    }
  }
});
// 然後在元件內可以這樣使用
this.$eventBus.$on('filterListByTag', this.filterListByTag);
this.$eventBus.$emit('filterListByTag', this.filterListByTag);
this.$eventBus.$off('filterListByTag', this.filterListByTag);複製程式碼

keep-alive

排除article元件,其他則保留元件狀態或避免重新渲染。

<transition name="fade" mode="out-in">
    <!-- keep-alive排除article -->
    <keep-alive exclude="article">
        <router-view>
         </router-view>
    </keep-alive>
</transition>複製程式碼

css小坑/小技巧

本來的transition是使用到transform的,因為移動端我的側邊欄是fixed,發現在切換的過程中側邊欄抖動,才發現是因為fixed是會跟隨transform的,可以具體參考CSS3 transform對普通元素的N多渲染影響,於是便刪去了切換時的移動,保留opacity

在佈局上學習了下vue官網的兩欄,兩欄超出部分都可以滑動,並且不互相影響,這是怎麼實現的?其實還是挺簡單的,不過用到一個小技巧,設定

// 這樣可以實現一個元素100%width以及100%height
div {
 position: absolute;
 top: 0;
 right: 0;
 bottom: 0;
 left: 0;
}複製程式碼

其他就是經典兩欄佈局和加個overflow-y:auto了

還有一個經典的佈局問題,就是foot部分如何實現在頁面沒什麼東西的,固定在頁面底部,頁面出現滾動條,然後foot部分跟隨在頁面主體後面

server端

server端直接上koa2了,支援async/await, 非同步部分用起來真的很舒服,現在node7+也支援了,所以小夥伴們趕緊用吧~

Json Web Token

再講講jwt鑑權吧
其實並不複雜
在驗證登入時,後端取出資料庫已有的密碼驗證,成功則用配置好的secret生成一個signed jwt,通俗點說你將它加密了再傳給客戶端

const token = jwt.sign({
        uid: user._id,
        name: user.name,
        exp: Math.floor(Date.now() / 1000) + 24 * 60 * 60 //1 hours
      }, config.jwt.secret);複製程式碼

http無狀態,所以客戶端需要存下這個token, 然後之後請求時帶上這個token服務端驗證通過後返回資源即可

try {
    tokenContent = await jwt.verify(token, config.jwt.secret);
  } catch (err) {
    if ('TokenExpiredError' === err.name) {
      ctx.throw(401, 'token expired');
    }
    ctx.throw(401, 'invalid token');
  }複製程式碼

如果想更深層次地瞭解jwt是什麼,可以自行去搜尋網上的文章

RESTful API

Representational State Transfer簡單來理解就是每個URL都是資源,通過不同的http method去操作這些資源就行,設計上就可以這樣,然後統一加上prefix:'/api'

router.get('/articles', verify, $.getAllArticles) //獲取所有文章
    .post('/articles', verify, $.createArticle) //建立文章
    .patch('/articles/:id', verify, $.modifyArticle) //修改特定文章
    .get('/articles/:id', $.getArticle)  //獲取特定文章
    .delete('/articles/:id', verify, $.deleteArticle) //刪除特定文章複製程式碼

webpack相關

本來打算直接使用vue-cli,這部分就非常省心了,vue-cli這個腳手架做得確實很友好,我覺得vue比較好上手開發的一部分原因也是因為它吧,不過因為這個部落格專案就是學習的過程,不想這麼輕易地逃過這部分的學習?,直接上webpack2,然後參考vue-cli和webpack官網來寫,覺得也學習到很多東西,並且因為front和admin是分開的,所以也實現了多頁配置

開發環境

hot-reload是必備的

// ...
entry: {
    'modules/admin': [
      'webpack-hot-middleware/client?reload=true',
      CLIENT_FOLDER + '/src/modules/admin/main'
    ],
    'modules/front': [
      'webpack-hot-middleware/client?reload=true',
      CLIENT_FOLDER + '/src/modules/front/main'
    ]
  },
// ...
plugins: [
    new webpack.HotModuleReplacementPlugin()
// ..複製程式碼

用CleanWebpackPlugin清空下目錄,HtmlWebpackPlugin自動生成html,然後該寫的loader

生產環境

生產環境下改動就大了,先得刪去hot-reload和devtool部分,然後提取css

//...
styl: ExtractTextPlugin.extract({
      use: [{
        loader: 'css-loader',
        options: {
          minimize: true,
          sourceMap: true
        }
      }, {
        loader: 'stylus-loader',
        options: {
          sourceMap: true
        }
      }],
      fallback: 'vue-style-loader'
    }),
//...複製程式碼

UglifyJsPlugin壓縮程式碼,然後提取公有程式碼vendor、manifest,manifest用來防止vendor的hash在vendor部分沒有變化時不被修改

// ...
// 分別提取vendor、manifest
    new webpack.optimize.CommonsChunkPlugin({
      name: 'modules/vendor_admin',
      chunks: ['modules/admin'],
      minChunks: function(module, count) {
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            join(__dirname, './node_modules')
          ) === 0
        )
      }
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'modules/manifest_admin',
      chunks: ['modules/vendor_admin']
    }),
//...複製程式碼

然後用CopyWebpackPlugin將static拷進dist就好了

與koa配合

這部分就可以看vue-cli是怎麼實現的了,vue-cli使用的是express通過webpack-dev-middleware和webpack-hot-middleware實現開發模式下熱過載,當然koa也可以

  const koaWebpack = require('koa-webpack');
  const webpack = require('webpack');
  const webpackConfig = require('../webpack.config');
  let compiler = webpack(webpackConfig);
  app.use(koaWebpack({
    compiler: compiler,
    dev: {
      //noInfo: true,
      stats: {
        colors: true,
        chunks: false
      },
      publicPath: webpackConfig.output.publicPath,
    }
  }));複製程式碼

順便講到專案中用的是history模式,這裡也需要後端配合

app.use(convert(historyApiFallback({
  verbose: process.env.NODE_ENV == 'production' ? false : true,
  index: '/front.html',
  rewrites: [
    { from: /^\/admin$/, to: '/admin.html' },
    { from: /^\/admin\/login/, to: '/admin.html' },
    { from: /^\/$/, to: '/front.html' },
    { from: /^\/article/, to: '/front.html' }
  ]
})))複製程式碼

但是這裡有個坑是要注意書寫use的順序,放在router api後面就行,這裡需要理解下koa的洋蔥結構

一個基於vue2、koa2和mongodb的部落格
koa

webpack優化

我覺得這部分除了區分開發環境和生產環境以外,還要注意到對webpack打包的過程和模組的分析,不要吝嗇webpack打包的輸出

// 顯示顏色,耗時長的都有顏色區分 --colors
// 可以看到每一步的耗時 --profile
// 顯示模組 --display-modules
// 並且按size大小排序  --sort-modules-by size
webpack.config.js --colors --profile --display-modules --sort-modules-by size複製程式碼

這樣就能看出打包什麼耗去你大量的時間,佔據了大量空間,還有是否是重複打包

還有一個直觀而又酷炫的方式github.com/alexkuz/web…

一個基於vue2、koa2和mongodb的部落格

直接看佔比就能看出哪部分需要你去優化了

我通過alias和external優化了下,效果還是挺明顯的,最後本地生產環境打包大概14-20s,也還可以接受,但是估計還有優化的空間,其實我也試過happypack和並行的uglify,不過發現沒什麼效果= =

//...
resolve: {
    extensions: ['.js', '.vue', '.json'],
    modules: [join(__dirname, './node_modules')],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      'vuex$': 'vuex/dist/vuex.esm.js',
      'vue-router$': 'vue-router/dist/vue-router.esm.js',
      'simplemde$': 'simplemde/dist/simplemde.min.js',
      'highlight.js$': 'highlight.js/lib/highlight.js',
      'fastclick': 'fastclick/lib/fastclick.js',
      'lib': resolve(__dirname, './client/src/lib'),
      'api': resolve(__dirname, './client/src/api'),
      'publicComponents': resolve(__dirname, './client/src/components'),
    }
  },
//...
// html template引入simplemde cdn
externals: {
    'simplemde': 'SimpleMDE'
 },
//...複製程式碼

還有一些快取優化的問題,就是js/css的hash問題,js chunkhash然後提取css使用contenthash,為了避免改動vendor的hash,commonChunk額外提取出manifest(extract the webpack bootstrap logic into a separate file),這樣當vendor沒有被修改的時候,重新執行webpack不會再生產新的hash,變動的只有manifest,具體可以看webpack.js.org/plugins/com…

關於多頁配置

其實只需要根據模組劃分然後多寫幾行配置就行,要做類似腳手架的話可以使用glob這些工具
具體看程式碼吧,會生成以下目錄

dist
 |---css
     |---modules
             |---admin.xxx.css
             |---front.xxx.css
 |---fonts
 |---modules
     |---admin.xxx.min.js
     |---front.xxx.min.js
     |---vendor_admin.xxx.min.js
     |---vendor_front.xxx.min.js
     |---manifest_admin.xxx.min.js
     |---manifest_front.xxx.min.js
 |---static
 |---admin.html
 |---front.html複製程式碼

線上部署及優化

pm2

表示之前我在玩耍node服務的時候都是使用screen命令的,pm2確實很贊,監控/日誌管理這些都很完善,日誌方面我還使用了pm2-logrotate

pm2 install pm2-logrotate
pm2 set pm2-logrotate:retain 100 //控制日誌數量
pm2 set pm2-logrotate:size 1M  //控制日誌切割大小複製程式碼

nginx

nginx基本是上線必備,應該也不用多說了,開啟gzip效果確實很顯著,本來前臺的vendor_front從227k直接被壓縮到84k,簡直cool!?

最後

謝謝閱讀~
歡迎follow我哈哈github.com/BUPT-HJM
看到這裡,不star不行了?
github.com/BUPT-HJM/vu…
歡迎繼續觀光我的部落格~

歡迎關注

相關文章