前端從頭搭建我的博客

項目介紹

  幾乎每一個程序員都有一個博客夢。抱着學習先後端技術的心態花一個月左右的時間來完成這個我的項目。 這是一個使用vue2作前端框架,koa2作後端,mongodb數據庫,搭建的單頁面應用。網站的功能展現我本身的技術分享,和後臺編輯書寫博客,支持markdown語法。本次項目大量借鑑了BUPT-HJM大神的博客,從ui到代碼細節,借鑑了不少。真的很感謝開源的大神。代碼地址github我的網站地址javascript

項目搭建

  由於前端選型使用vue,想用vue-cli一路平推省心省力來着,可是爲了更加深刻學習webpack,決定手動從頭寫起。 關於webpack的實現細節我會單獨寫一篇博客,由於內容實在太多,在這裏只說明實現了哪些功能。css

開發環境

  如今前端開發仍是很看重開發體驗的,我以前在上上家公司前端開發時,開發環境一塌糊塗,覺得業內都是這樣(後來才知道是前端組長的不負責任)。上家公司的開發環境真的很舒服,不用過多的考慮兼容性,代碼風格強制統一,熱更新什麼的包羅萬象。 因此webpack構建的時候就加進去了熱更新,使用postcss超前使用css新特性和自動前綴。 須要特別說明的是webpack熱更新依賴的仍是node,咱們都知道webpack-dev-server這個插件是用了express框架做爲熱更新服務的,可是考慮到本次開發時的後臺也是node,只不過用的是koa2,那麼徹底能夠在開發過程當中只啓動一個node服務,來知足前端和後端的node需求。因此我沒有使用webpack-dev-server這個webpack插件,而是本身實現。具體代碼會在webpack那篇博客中細講。html

生產環境

  生產環境的構建中,也是很常規的一些實現。例如代碼壓縮,css加前綴,資源文件的路徑處理,圖片壓縮。開啓多線程的打包功能,這裏吐槽一句 剛開始打包時間是7秒左右,按照網上的打包優化建議一頓花裏胡哨的折騰,結果打包時間漲到9秒了。。。 使用了 webpack引覺得傲的很重要的功能,將vue文件拆分紅不一樣的Chunk,配合vue-router按需引入前端

// vue-router
 const HelloWorld = () => import('../components/HelloWorld.vue');
複製代碼

打包的時候把js拆分,按需引入,好處在於js不至於體積過大。須要說明一下這種語法是es7的新特性,配置babel的時候坑不少。vue

總結

  webpack更新換代太快了,不少時候沒有中文文檔,只能只知其一;不知其二的查github, 可是隨着webpack愈來愈成熟穩定,語法也固定下來,惟一須要咱們注意的地方是眼花繚亂的插件,根據本身的須要合理添加。java

前端細節

  項目開發的時候前端是最熟悉的一部分,vue做爲前端框架,對於這種中小型項目來講很合適。寫博客網站也是爲了學習,乾脆用了vue全家桶。 vue-router,vuex,都在用。 由於個人審美和ui設計實在有限,大量借鑑(抄襲BUPT-HJMnode

首頁和博客詳情頁

  首頁包括一個header組件,一個信息展現組件,博客列表頁,和分頁組件。稍微有點複雜的組件只有信息展現組件和分頁組件。 信息展現組件用到了vue的插槽,slot ,默認是自我介紹。關於插槽囉嗦幾句:不少初學vue的同窗對於插槽的使用和理解很費解,對於vue中的props都會用,做爲父組件給組件傳值用,而插槽也是如此,只不過props傳入的是data數據,而插槽傳入的是html 。   分頁組件實際上是和vuex結合一塊兒寫的。vuex裏面的state記錄分頁的當前頁數,點擊改變頁數觸發vuex裏面的mutations去加載分頁數據。   博客詳情頁數據的加載來源也是和vuex結合,從state裏面獲取文章id,去get請求博客細節。博客詳情頁有兩個問題, 1.從後端取出來的數據是markdown原始數據,須要解析。 從網上找了一個很好用的解析插件marked,引入,稍微封裝一下就能夠用了,配置了一下代碼塊的樣式。若是有興趣能夠去看看代碼。 2.利用vue的 this.$nextTick 等dom元素渲染完成後,抽離博客中h1到h6的標籤。來組合成菜單欄。react

Array.from(this.$refs.post.querySelectorAll("h1,h2,h3,h4,h5,h6")).forEach(
       (item, index) => {
         item.id = item.localName + "-" + index
         this.category.push({
           tagName: item.localName,
           text: item.innerText,
           href: "#" + item.localName + "-" + index
         })
       }
     );

複製代碼

以上是核心代碼,也是參考了大神的源碼。從新組合成一個菜單欄,這時候就用到了以前信息展現組件的插槽功能。   css直接用了postcss,postcss好處在於它的語法和scss相似,並且豐富的插件可使得使用不少超前的css語法,雖然我用的不多。關於postcss的插件和webpack配置,也不復雜,很常規的一些。細節見代碼。原本不打算對移動端博客做兼容的,考慮到你們廣泛仍是手機瀏覽博客頻率更高。因此對移動端作下兼容處理,並且postcss對於媒體查詢的語法很友好webpack

@custom-media --small-viewport (max-width: 850px);

@media (--small-viewport) {
 .ArticlePage .articleDate{
   margin-left: 0;
   width: 100%;
 }
 .ArticlePage .articleDate .time{
   margin-left: 0;
 }
 .abstract {
   width: 100%;
 }
}
複製代碼

以上是對移動端作的媒體查詢 ios

以上是移動端和pc端不一樣的ui展現,也是大量借鑑了別人的博客

登錄頁面和博客編輯頁面

登錄鑑權

  由於這個博客只是單純本身用,因此很簡單的寫了一個登錄頁面,用的是elementUI, 這裏涉及到一個前端鑑權的功能,登錄成功時,會獲取到一個token值有效期24小時。前端拿到token後,寫入請求頭中,後端某些(例如進去admin頁面)請求須要驗證token。

// 來自 vuex/mutations.js 做用是將獲取到的token存入storage
   	 [types.CREATE_TOKEN]: (state, res) => {
   			state.token = res.token
   			state.userInfo = res.name
   			sessionStorage.setItem('vue-blog-token', res.token)
   			sessionStorage.setItem('vue-blog-userName', res.name)
   		},

   		// 來自 封裝的 js/http.js 做用是將token寫入headers頭部
   	const createToken = ()=>{
   		if(store.state.token){
   		// 全局設定header的token驗證,注意Bearer後有個空格
   			axios.defaults.headers.common['Authorization'] = 'Bearer ' + store.state.token
   		}
   	}
   	// axios攔截返回,攔截token過時
   	axios.interceptors.response.use(function (response) {
   		return response
   	}, function (error) {
   		if (error.response.data.error.indexOf('token') !== -1) {
   				store.commit('DELETE_TOKEN')
   		}
   		return Promise.reject(error)
   	});
複製代碼

以上是前端處理鑑權的核心代碼。

博客編輯功能

博客後臺是兩部分構成1. 博客列表頁,包括公開和沒有公開的博客 ,2 markdown 編輯器。

  這裏尋找markdown編輯器仍是費了一番周折,開始使用的是 vue-simplemde 開發環境下沒有問題,可是打包生成之後報錯webpack的babel-loader編譯通不過,折騰很久我把這個npm包中涉及到編輯器的vue文件抽出來,vue-simp.vue文件直接引用,反而能夠用了,真的是玄學。第二個難點是博客編輯功能的開發,涉及到新建,更新,刪除,是否發佈,也是bug最多的地方。 博客後臺截圖

前端總結

  vue和vuex用的更加熟練了,webpack不敢說熟練,可是起碼各類資源文件的處理和配置比之前駕輕就熟。多少也鍛鍊了本身前端封裝組件的能力。咱們遇到的問題,別人也會遇到。多用谷歌和github總能解決的。   這裏簡單聊一下我對前端組件化的一些感想,我總共接觸寫過三種前端框架,vue,react,小程序。逐漸接受了一種組件化就是容器組件和展現組件。容器組件是指參與項目邏輯和響應用戶指令的組件。例如分頁組件,內部處理複雜的分頁邏輯。展現組件是指只負責渲染的組件,例如博客列表組件,只接受數據,而且渲染。接受到用戶指令後,例如父子組件傳值或者vuex,redux等狀態管理工具來傳遞給容器組件處理。每每展現組件能夠被屢次使用,抽取爲公共組件。可是根據項目的不一樣,組件抽取的顆粒程度不一樣,組件的做用也不一樣。

後端細節

  這是我第一次完整的使用koa2,剛開始難免有些無從下手,我仍是相信程序員的學習和進步都是從學習大神代碼開始的,參考大神代碼,將服務端代碼分爲如下部分:

入口文件

  由於在koa中使用es6和部分es7新特性,因此在入口文件中加入babel編譯

// 來自server/start.js
require('babel-register')({
  presets: [ 'env' ]
})

// Import the rest of our application.
module.exports = require('./index.js')

複製代碼

就是很粗淺的配置了一下,因此真正的入口文件是 index.js,    在webpack那講過開發的時候,先後端使用同一個node服務。判斷是否開發環境,而後直接執行函數,將koa做爲參數傳入,去啓動webpack的服務,具體會在關於webpack博客中細講。

const app = new koa()
const isDeve = process.env.NODE_ENV === 'development'
if (isDeve) {
  require('../build/server')(app)
}
複製代碼

require引入的文件是webpack配置文件,這也是webpack和koa結合最核心的代碼。至此入口文件中都是很常規的app.use()和鏈接mongoodb了。   有一個被我忽視的地方就是node怎麼指向打包完成後的主頁,開發模式中,目標文件夾中是看不到編譯後的文件的,實時編譯後的文件都保存到了內存中,個人前端代碼被webpack 會使用inline mode(內聯模式)。這種模式在 bundle 中注入客戶端。而在生產環境中須要指明主頁。

import serve  from 'koa-static'
const home  = serve(path.join(__dirname+"../../dist/"))
app.use(home)
複製代碼

dist文件夾就是打包生成的前端代碼,ko2會默認指定index.html爲主頁。

路由

   用koa寫接口koa-router官方推薦的,使用也簡單,這裏也不過多說明,koa-router和controllers結合一塊兒使用,

import * as $ from '../../controllers/articles_controller'
   import verify from '../../middleware/verify'
   export default async(router) => {
   	router.get('/getArticles',$.getAllPublishArticles)
   	router.post('/saveArticle',verify,$.saveArticle)
   	router.get('/articleDetails',$.articleDetails)
   	router.post('/changeArticle',verify,$.changeArticle)
   	router.post('/deletaArticle',verify,$.deletaArticle)
   }
複製代碼

簡單說明一下以上代碼,實際的業務代碼所有放在comtrollers下,路由只負責調取不一樣的comtroller,verify是鑑權,經過koa中間件,來實現接口是否現須要鑑權才能調用。這樣就可使得業務邏輯和路由的剝離,相比我以前寫在一塊兒要好的多。

controllers和middleware

  後端代碼最核心的部分就是controller,包括網站數據的存儲,修改,刪除,管理員登錄。雖然是最核心的代碼,卻也是最簡單。就是增刪改查。代碼就不放了,感興趣的大佬能夠去github上自行查看github

  關於鑑權的功能,是放在中間件來實現的,同時在中間件還使用了koa-bodyparser,做用獲取post提交數據。對於koa來說這個中間件是使用率最高的了。

import jwt from 'jsonwebtoken'
import config from '../serverConfig/index'
export default async(ctx, next) => {
    // console.log(ctx.get('Authorization'))
    const authorization = ctx.get('Authorization')
    if (authorization === '') {
        ctx.throw(401, 'no token detected in http header \'Authorization\'')
    }
    const token = authorization.split(' ')[1]
    let tokenContent
    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')
    }
    console.log('鑑權成功')
    await next()
};

複製代碼

以上代碼是寫在中間件的鑑權代碼,用的是jwt,使用也很簡單,功能就是檢查header是否攜帶token,和token是否過時。關於koa中間件的高階使用和源碼之類的請原諒個人弱雞,之後會慢慢補上來的。

mongodb

  單獨講一下mongodb吧,關於服務器部署mongodb的文章一抓一大把這裏也再也不說明,代碼中mongodb的操做使用的也是常規的mongoose,我做爲非科班出身的前端程序員,數據庫設計實在是短板,只能參考別人的設計。本網站的數據庫設計分兩個collection,一個是博客的collection,另外一個是用戶的collection。

  在業務邏輯代碼中,須要頻繁的查詢修改。好在mongoose的語法也足夠簡單。

export async function articleDetails(ctx) {
  const articleID = ctx.query.articleID
  const articleDetail = await Article.findOne({
    _id: articleID
  }).catch(err => {
    ctx.throw(500, ERRMESG)
  });
  ctx.body = {
    success: true,
    articleDetail,
  };
}
複製代碼

上面代碼中用到的findOne是mongoose查詢語句的一種,參數是查詢條件。其餘的查詢語句遇到了去官網查一下就行。

後端總結

   對於koa的學習算是入門了,可是遠遠沒有達到一個合格的標準,不是裝逼,而是寫的越多,發現本身越弱。中間件只會簡單使用,koa對比express的優勢在哪也說不出。要學的東西還不少。

上線過程

  網站的服務器是從阿里雲購買,域名sxywzg是我和女友姓名的縮寫,(喂狗糧支線任務達成)。我購買服務器日期是十月末,買了沒幾天阿里雲推送告訴我服務器雙十一打折。。。在服務器上安裝node,數據庫,配置亂七八的東西不細說了。網上也有不少教程,重點說下nginx配置。

upstream vue{
  server 127.0.0.1:3000;
}

server {
  listen  80;
  server_name www.sxywzg.cn;
  location / {
    proxy_set_header X-real-ip $remote_addr;
    proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-Nginx-Proxy true;
    proxy_pass http://vue;
    proxy_redirect off;
  }
}
複製代碼

網站端口都是80,將端口3000轉發到80上,server_name就是網站域名。就這麼簡單的配置我折騰很久,仍是在同事的幫助下整出來。nginx的安裝也很簡單,自行搜索教程。

pm2

  pm2我以前只是簡單的讓node服務在服務器跑起來的工具而已,其實遠不止那麼簡單。個人代碼是放在github上面的,每次更新代碼都會提交到上面,pm2能夠幫助我從git倉庫拉取代碼,而且重啓服務。

{
  "apps": [
    {
      "name": "vue-blog",
      "script": "server/start.js",
      "error_file": "server/logs/app-err.log",
      "out_file": "server/logs/app-out.log",
      "env_dev": {
        "NODE_ENV": "development"
      },
      "env_production": {
        "NODE_ENV": "production"
      }
    }
  ],
  "deploy": {
    "production": {
		// 我服務器用戶名
      "user": "zhigang",
			///服務器地址
      "host": ["47.95.***.***"],
      "port": "22",
			//代碼分支
      "ref": "origin/master",
			//代碼倉庫
      "repo": "git@github.com:463755120/vue-blog.git",
			//服務器存放代碼地址
      "path": "/home/zhigang/vue-blog",
      "ssh_options": "StrictHostKeyChecking=no",
			// 每次執行的命令
      "post-deploy":"cnpm i && npm run build && pm2 start pm2.json --env production",
      "env": {
        "NODE_ENV": "production"
      }
    }
  }
}
複製代碼

以上是pm2.json配置,做用是一鍵上線代碼,不用咱們手動把代碼在服務器中拉下來,而後跑命令。pm2幫你作這些累活。剛開始用這個功能,驚爲天人,還有這麼善解人意的工具,可是不知道是個人緣由仍是pm2的缺陷,項目第一次上線時pm2執行cnpm i下載的包不完整,我只能在服務器裏手動 下載,之後就好使了。因此我須要更新網站時,把代碼提交到git倉庫,執行一下 npm run pm2 省時省力。

寫在最後

  寫博客網站的緣由有兩個,第一個是爲了檢驗本身能不能寫一個完整的而且能上線的項目,第二個是爲之後方便本身寫博客。若是不總結本身的技術,用不了多久就會忘得差很少。上家公司的前端組長給我說忘了你是前端開發,你只是解決問題的程序員。我深覺得然,公司能夠把咱們劃分爲前端開發,後端開發。可是我以爲從內心須要知道程序員是解決問題的人,不能由於是前端開發就只侷限於本身的一畝三分地。不要知足在別人配置好的環境中開發。瞭解一些項目架構和上線流程,服務器配置不是壞事。2018年立刻過去,今年也承受了我這個年紀不應承受的裁人之苦。明年還要學基本的算法,設計模式,typescript和react。之後的博客也會不按期的增長個人學習筆記。但願大佬們多提意見,聯繫個人方式很簡單,首頁有個人知乎帳號,發私信便可。

相關文章
相關標籤/搜索