一直是想寫一個本身的博客,最近有點時間,花費了幾天就擼了一個,雛形已經有了,後續完善內容,優化功能,有不少地方還沒來的及作處理,後續繼續優化。本身能力有限,有些地方處理的很差,但願你們可以給予指正,之後本身的博客也會同步到這個網站上,下面進入正題。css
頁面的話,直接使用vue-cli生成項目,安裝相應依賴包,運行項目,該項目中安裝的依賴包包含以下:前端
// 克隆項目
git clone git@github.com:dragonnahs/new_websit_blog.git
// 進入前端頁面目錄
cd baozi_blog
// 安裝依賴
npm install
// 運行項目
npm run dev
// 打包項目
npm run build
複製代碼
這裏沒有一步步講實現,主要的地方大體說下,怎麼實現的,挺簡單的,能夠先clone下來運行一遍試下,依賴於後臺接口,因此儘可能吧後臺先跑起來。 下面主要記錄一些寫代碼過程當中一些技術點。vue
須要用到的庫marked
和highlight.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
是登陸的時候後臺給返回的,後臺設置過時時間,登陸後保存到本地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
這裏是作了個單獨的組件出來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
// 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模式配置已經完成,訪問刷新均可以正常使用.
這裏記錄下上線的配置,前端後臺的都有,涉及到直接打包上線,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爲準,以後也會加上日誌,完善以後博客會更新到網站上,好了繼續擼代碼。