一直是想寫一個本身的博客,最近有點時間,花費了幾天就擼了一個,雛形已經有了,後續完善內容,優化功能,有不少地方還沒來的及作處理,後續繼續優化。本身能力有限,有些地方處理的很差,但願你們可以給予指正,之後本身的博客也會同步到這個網站上,下面進入正題。css
## 前端頁面的文檔前端
頁面的話,直接使用vue-cli生成項目,安裝相應依賴包,運行項目,該項目中安裝的依賴包包含以下:vue
// 克隆項目 git clone git@github.com:dragonnahs/new_websit_blog.git // 進入前端頁面目錄 cd baozi_blog // 安裝依賴 npm install // 運行項目 npm run dev // 打包項目 npm run build
這裏沒有一步步講實現,主要的地方大體說下,怎麼實現的,挺簡單的,能夠先clone下來運行一遍試下,依賴於後臺接口,因此儘可能吧後臺先跑起來。
下面主要記錄一些寫代碼過程當中一些技術點。node
須要用到的庫marked
和highlight.js
這兩個,在博客詳情頁面使用,mysql
// tempalte(v-highlight時後續加的自定義指令,下面會說) <div class="markdown" v-html="compiledMarkdown" v-highlight></div> // script // 引入和初始化 let marked = require('marked') let hljs = require('highlight.js') import 'highlight.js/styles/default.css' marked.setOptions({ renderer: new marked.Renderer(), gfm: true, tables: true, breaks: false, pedantic: false, sanitize: false, smartLists: true, smartypants: false, highlight: function (code, lang) { if (lang && hljs.getLanguage(lang)) { return hljs.highlight(lang, code, true).value; } else { return hljs.highlightAuto(code).value; } } }) export default{ data () { return { blogInfo: {} } }, computed: { // 解析代碼 compiledMarkdown(){ return marked(this.blogInfo.content || '', { sanitize: true }) } } } // 到這一步已經可以在頁面看到解析完成以後的內容了,可是沒有高亮樣式,緣由是vue-router在切換路由的時候會移除hight的事件,解決辦法是自定義一個指令從新給代碼加上對應的類名,實現代碼高亮,具體代碼以下 // main.js let hljs = require('highlight.js') // 自定義代碼高亮指令 Vue.directive('highlight',function (el) { let blocks = el.querySelectorAll('pre code'); blocks.forEach((block)=>{ hljs.highlightBlock(block) }) }) // 到此已經完美的解決了markdown的解析以及代碼的高亮問題。
既然涉及到博客管理後臺,就確定有登陸驗證的問題,使用的是token去驗證權限。
前端的路由守護,須要使用router.beforeEach
方法,對每個路由去進行判斷,驗證該路由是否須要登陸權限,若是須要登陸權限的話,就經過存儲在本地的token
去獲取用戶信息,獲得用戶信息,繼續當前的路由跳轉,不能的話就跳轉到登陸頁去登陸。這裏的token
是登陸的時候後臺給返回的,後臺設置過時時間,登陸後保存到本地session
中webpack
// router/index.js router.beforeEach((to, from, next) => { // 匹配路由,是否須要登陸驗證(在註冊路由的時候定義) if(to.matched.some(record => record.meta.requireAuthor)){ store.dispatch('getUser').then(data => { console.log(data) if(data && data.length > 0){ next() }else{ next('/login') } }).catch(err => { next('/login') }) } next() })
最開始用的是暢言的評論,後來改爲本身寫的評論了,主要緣由是使用暢言須要加載不少外部文件,能夠看下network裏面加載了不少東西,同時我對評論的功能沒有過高的要求,所以就本身隨便寫了個簡單的評論留言功能,可是仍是要記錄下如何實現暢言的引入。ios
這裏是作了個單獨的組件出來
<template> <div id="SOHUCS" sid="請將此處替換爲配置SourceID的語句"></div> </template> <script> export default { mounted () { window.changyan = undefined; window.cyan = undefined; this.loadScript('https://changyan.sohu.com/upload/changyan.js',()=>{ window.changyan.api.config({ appid: '###', // 此處換成你的暢言應用的appid, conf: '####', // 此處換成你暢言應用的conf。 }); }) }, methods: { loadScript(url, callback){ // 加載script let script = document.createElement('script'); if (script.readyState) { // IE瀏覽器 script.onreadystatechange = function () { if (script.readyState === 'loaded' || script.readyState === 'complete') { script.onreadystatechange = null; callback(); } } } else { // 其餘瀏覽器 script.onload = function () { callback(); } } script.src = url; document.getElementsByTagName('head')[0].appendChild(script); } } } </script>
而後在須要的地方引入組件就能夠了nginx
// build/webpack.prod.conf.js 修改這裏面的文件 new UglifyJsPlugin({ uglifyOptions: { compress: { warnings: false, drop_debugger: true, // 新增 drop_console: true, // 新增 pure_funcs: ['console.log'], // 新增 } }, sourceMap: config.build.productionSourceMap, parallel: true }),
默認狀況下,vue-router是用的hash的路由模式,在地址欄中總會有#號,這種狀況下微信的一些分享功能會把#號後面的去除掉,所以使用history模式更合適
// router/index.js const router = new Router({ mode: 'history', routes: [ { path: '/', component: resolve => require(['../views/out.vue'], resolve), } }) // 在初始化路由的時候,加上`mode: 'history'`這樣就好了, // 初步完成以後打包上線,上線以前要確保,config/index.js下的`assetsPublicPath`指定到根目錄下,否則刷新會報錯, build: { // Template for index.html index: path.resolve(__dirname, '../pcBlog/index.html'), // Paths assetsRoot: path.resolve(__dirname, '../pcBlog'), assetsSubDirectory: 'static', assetsPublicPath: '/', } // 完成以後,運行`npm run build`,把生成的包放到服務器上,這裏還不能算正式完成,須要修改nginx配置,才能正常使用history模式 // 找到服務器的`/etc/nginx/conf.d/blog.conf`文件,配置以下,必須指定到打包的目錄下才行, server { listen 80; server_name blog.baozinews.cn; # 指定到根目錄下 root /usr/share/nginx/new_websit_blog/baozi_blog/pcBlog; # 官方指定配置 location / { try_files $uri $uri/ /index.html; } # 接口代理 location /blog/v1/ { proxy_pass http://127.0.0.1:3006; } location ~* ^.+\.(css|js|ico|gif|jpg|jpeg|png)$ { log_not_found off; # 關閉日誌 access_log off; # 緩存時間7天 expires 7d; # 源服務器 #proxy_pass http://localhost:8888; # 指定上面設置的緩存區域 proxy_cache imgcache; # 緩存過時管理 proxy_cache_valid 200 302 1d; proxy_cache_valid 404 10m; proxy_cache_valid any 1h; proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; } }
到這裏vue-router的history模式配置已經完成,訪問刷新均可以正常使用.git
這裏記錄下上線的配置,前端後臺的都有,涉及到直接打包上線,node的部署,以及cdn的加速處理
1.首先是前端頁面
首先npm run build
生成打包後的文件,因爲修改了配置,我這裏生成的是pcBlog文件,將文件放到服務器上,配置對應的nginx
// /etc/nginx/conf.d/blog.conf server { listen 80; server_name blog.baozinews.cn; // 二級域名 root /usr/share/nginx/new_websit_blog/baozi_blog/pcBlog; // 目錄文件 // 這是vue-router官方指定配置,針對history模式的配置 location / { try_files $uri $uri/ /index.html; } location /blog/v1/ { proxy_pass http://127.0.0.1:3006; // 代理地址,後臺node的端口號 } }
由於採用的是history
模式,所以打包上線的時候須要將域名指定到項目的根目錄下,同事要加上上面的這段nginx配置,否則訪問二級頁面會報錯.
而後就是cdn加速,使用的是七牛的cdn加速,上傳圖片也是使用的七牛雲存儲,關鍵是免費。具體使用是先綁定cdn加速域名,我這裏使用的dragon.baozinews.cn
這個二級域名,(這裏的二級域名是www.dnspod.cn
上生成的),最終的加速訪問域名就是這個,須要配置好cname參數,配置很是簡單,根據提示或者網上搜索下就好了。配置完成以後就能訪問這個加速域名了,可是要在七牛雲上配置這個加速域名的回源域名,這裏指的就是你項目真正訪問的域名,就是在nginx配置的二級域名.到這裏就能直接訪問這個加速二級域名了,同時訪問的就是你配置好的nginx指向的目錄。
2.後端啓動
先把項目放到服務器,而後使用pm2 start src/app.js --name '啓動的項目名稱隨便起'
就能啓動對應的node項目,這是最簡單的啓動方式,pm2 save
保存啓動項目,pm2 ls
查看全部的啓動項目,pm2 restart id
重啓對應的項目,pm2 log id
查看對應項目log日誌
項目部署好後,完成了cdn加速,在代碼裏添加了新的功能,完成後部署到服務器,可是訪問加速域名仍是舊的頁面,這裏須要在七牛cdn加速功能處去處理,選中‘刷新預取’模塊,而後選中刷新目錄,在其中填寫http://dragon.baozinews.cn/
你的加速域名,而後提交,再刷新頁面,就是你剛提交的最新頁面了。
這裏後臺的存儲是用mongodb
主要緣由是這個稍微熟悉些,有熟悉mysql的也能夠直接使用mysql來存儲數據,mongodb不熟悉的同窗能夠查看下面的連接,稍微熟悉下api,參考連接
// jwt.js 建立和驗證token // 這裏是用的最簡單的一種生成方法,具體更嚴格的能夠去看npm上查看 const jwt = require('jsonwebtoken') let secret = 'jwttoken' class Jwt { constructor(data,token){ // data是前端傳過來的數據信息 this.data = data this.token = token } // 生成token createToken(){ let token = jwt.sign(this.data, secret, { expiresIn: '1h', // 有效時間 issuer: 'baozi' }) return token } // 驗證token verifyToken(){ try{ let result = jwt.verify(this.token, secret) return result }catch(err){ return null } } } module.exports = Jwt // user.js // 登陸接口的時候建立token let token = new jwtClass({username: req.username}).createToken() // 獲取用戶信息的時候驗證token let resultToken = new jwtClass('',token).verifyToken()
使用的是bcrypt對密碼進行加密和驗證,註冊用戶的時候對密碼進行加密,登陸的時候去驗證密碼是否正確。
// 註冊 static async registerUser(ctx){ let req = ctx.request.body let result = await UserModel.findOne({'username': req.username}) if(result){ ctx.body = new ErrorResModel('用戶名已存在') return } // 密碼加密 let password = req.password // hash是對密碼進行加密處理生成一個hash值,這裏的password是輸入的密碼,10是曼哈希輪數,支持異步處理,並把生成的hash值做爲密碼存到數據庫 let hashPassword = await bcrypt.hash(password,10) let newUser = new UserModel({ username: req.username, password: hashPassword, power: req.username == 'admin' ? 10 : 1, modifyAt: req.modifyAt || '', email: req.email || '', telphone: req.telphone || '', realName: req.realName || '' }) await newUser.save() let token = new jwtClass({username: req.username}).createToken() ctx.body = new SuccessResModel({ token: token, userinfo: {username: req.username} },'註冊成功') } // 登陸 static async loginUser(ctx){ let req = ctx.request.body let result = await UserModel.findOne({'username': req.username}) // 比較輸入的密碼和數據庫存儲的密碼是否一致,返回false不一致,返回true一致 let flag = await bcrypt.compare(req.password,result.password) if(!flag){ // 驗證不經過 ctx.body = new ErrorResModel('帳號密碼不正確') return } if(result){ let token = new jwtClass({username: req.username}).createToken() ctx.body = new SuccessResModel({ token: token, userinfo: result }, '登陸成功') return } ctx.body = new ErrorResModel('帳號密碼不正確') }
因爲blog表涉及到tag和user表的關聯,標籤這個是數組類型的,最開始的處理方式,在本地也獲取到了正確的列表,發佈到服務器以後卻出現了問題,先記錄下解決的方式,後續看下是否有更優化的處理方式.
// database/model/blog.js const blogSchema = new Schema({ title: { type: String, required: true }, createAt: { type: Date, default: Date.now() }, content: { type: String, required: true }, author: Schema.Types.ObjectId, tags: [{ type: Schema.Types.ObjectId, ref: 'Tags' }], // tags是數組格式的,關聯tags isShow: { type: Boolean, default: true }, modeifyAt: { type: Date, default: Date.now() }, clickNum: { type: Number, default: 0 }, imgUrl: String, power: { type: Number, default: 0 }, isTop: { type: Boolean, default: false } }) // controller/blog.js獲取blog列表 BlogModel.aggregate([ { // 單個關聯 $lookup: { from: 'users', localField: 'author', foreignField: '_id', as: 'userinfo' } }, { // 單個關聯 $lookup: { from: 'tags', localField: 'tags', foreignField: '_id', as: 'tagList' } }, { $match: { $or: [ {title: {$regex: searchKey,$options: '$i'}}, ] }, }, {$sort: {createAt: -1}}, {$skip: (pageNum-1)*pageSize}, {$limit: pageSize}, ])
最開是按照上面寫的來的,在本地運行的時候沒什麼問題,可以正常獲取到tagList
,而後部署到服務器上以後tagList顯示的一直是空,可是tag裏面已經存儲了tag的id,本覺得是mongodb版本過低的緣由,當時用的是3.2.0後來更新以後仍是不行,最終是經過另一種方式解決的,下面是解決方案,後續會找下優化的方式,是否是代碼寫的有問題,感受下面的處理代碼的方式不太好
// controller/blog.js let resultTemp = await BlogModel.aggregate([ { // 單個關聯 $lookup: { from: 'users', localField: 'author', foreignField: '_id', as: 'userinfo' } }, { $match: { $or: [ {title: {$regex: searchKey,$options: '$i'}}, ] }, }, {$sort: {createAt: -1}}, {$skip: (pageNum-1)*pageSize}, {$limit: pageSize}, ]) // 數組類型關聯 let result = await (function(){ return new Promise(resovlve => { // 在這裏在關聯下tags獲取tags列表,這樣處理在blogmodel中tags必須{type: mongoose.Schema.Types.ObjectId,ref:'Tags'} BlogModel.populate(resultTemp, 'tags', function(err,res){ resovlve(res) }) }) })()
目前已經能在服務器上正常運行,網上給出的方案就是第一種,可是不清楚爲何在服務上不能正常運行,後續會繼續查找方案,解決後會更新在文檔上.
// public/utils/time-out.js async function timeOut(ctx, next) { var tmr = null; const timeout = 5000;//設置超時時間 // 這裏是Promise.race數組中的promise對象誰先執行完就走誰 await Promise.race([ new Promise(function (resolve, reject) { tmr = setTimeout(function () { var e = new Error('Request timeout'); e.status = 408; reject(e); }, timeout); }), new Promise(function (resolve, reject) { //使用一個閉包來執行下面的中間件 (async function () { await next(); clearTimeout(tmr); resolve(); })(); }) ]) } module.exports = timeOut // app.js app.use(TimeOut)
先寫到這裏,功能還不是很完善,存在一些問題,但願你們給指正下,目前想來仍是先以完善功能爲主,後續會繼續優化代碼,功能上的話目前是規劃我的資料,評論列表管理,以及用戶列表管理,還有打點的圖形日誌,後端這邊的日誌沒有加上,目前主要是以pm2 log爲準,以後也會加上日誌,完善以後博客會更新到網站上,好了繼續擼代碼。