用vue-node-mongodb寫了一個我的博客

擼了一個本身的我的博客

一直是想寫一個本身的博客,最近有點時間,花費了幾天就擼了一個,雛形已經有了,後續完善內容,優化功能,有不少地方還沒來的及作處理,後續繼續優化。本身能力有限,有些地方處理的很差,但願你們可以給予指正,之後本身的博客也會同步到這個網站上,下面進入正題。css

github地址 網站地址html

前端頁面的文檔

頁面的話,直接使用vue-cli生成項目,安裝相應依賴包,運行項目,該項目中安裝的依賴包包含以下:前端

  • vuex(數據管理)
  • element-ui(ui庫)
  • axios(接口請求)
  • sass(css預處理器)

使用步驟

// 克隆項目
git clone git@github.com:dragonnahs/new_websit_blog.git

// 進入前端頁面目錄
cd baozi_blog

// 安裝依賴
npm install 

// 運行項目
npm run dev

// 打包項目
npm run build

複製代碼

這裏沒有一步步講實現,主要的地方大體說下,怎麼實現的,挺簡單的,能夠先clone下來運行一遍試下,依賴於後臺接口,因此儘可能吧後臺先跑起來。 下面主要記錄一些寫代碼過程當中一些技術點。vue

markdown格式解析和高亮

須要用到的庫markedhighlight.js這兩個,在博客詳情頁面使用,node

// 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去驗證權限。mysql

  • 前端頁面

前端的路由守護,須要使用router.beforeEach方法,對每個路由去進行判斷,驗證該路由是否須要登陸權限,若是須要登陸權限的話,就經過存儲在本地的token去獲取用戶信息,獲得用戶信息,繼續當前的路由跳轉,不能的話就跳轉到登陸頁去登陸。這裏的token是登陸的時候後臺給返回的,後臺設置過時時間,登陸後保存到本地sessionwebpack

// 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

這裏是作了個單獨的組件出來nginx

<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>


複製代碼

而後在須要的地方引入組件就能夠了git

打包上線時去除log日誌

// 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
}),

複製代碼

使用history模式

默認狀況下,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模式配置已經完成,訪問刷新均可以正常使用.

上線的過程

這裏記錄下上線的配置,前端後臺的都有,涉及到直接打包上線,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日誌

  1. 配置好cdn加速後如何更新代碼

項目部署好後,完成了cdn加速,在代碼裏添加了新的功能,完成後部署到服務器,可是訪問加速域名仍是舊的頁面,這裏須要在七牛cdn加速功能處去處理,選中‘刷新預取’模塊,而後選中刷新目錄,在其中填寫http://dragon.baozinews.cn/你的加速域名,而後提交,再刷新頁面,就是你剛提交的最新頁面了。

後端代碼

主要技術棧

  • node
  • vue
  • mongoose

這裏後臺的存儲是用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('帳號密碼不正確')
    
}

複製代碼

mongoose關聯數據查詢的問題

因爲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爲準,以後也會加上日誌,完善以後博客會更新到網站上,好了繼續擼代碼。

相關文章
相關標籤/搜索