vue+node+mongodb 搭建一个完整博客
Vue + Node + Mongodb 開發一個完整博客流程
前言
前段時間剛把自己的個人網站寫完, 于是這段時間因為事情不是太多,便整理了一下,寫了個簡易版的博客系統
服務端用的是 koa2框架 進行開發
技術棧
Vue + vuex + element-ui + webpack + nodeJs + koa2 + mongodb
目錄結構講解
- build - webpack的配置文件
- code - 放置代碼文件
- config - 項目參數配置的文件
- logs - 日志打印文件
- node_modules - 項目依賴模塊
- public - 項目靜態文件的入口 例如: public下的 demo.html文件, 可通過 localhost:3000/demo.html 訪問
- static - 靜態資源文件
- .babelrc - babel編譯
- postcss.config.js - css后處理器配置
build 文件講解
- build.js - 執行webpack編譯任務, 還有打包動畫 等等
- get-less-variables.js - 解析less文件, 賦值less全局變量
- style-loader.js - 樣式loader配置
- vue-config.js - vue配置
- webpack.base.conf.js - webpack 基本通用配置
- webpack.dev.conf.js - webpack 開發環境配置
- webpack.prod.conf.js - webpack 生產環境配置
code 文件
1.admin - 后臺管理界面源碼
2.client - web端界面源碼
跟后臺管理界面的結構基本一樣3.server - 服務端源碼
config - 項目參數配置的文件
logs - 日志文件
public - 項目靜態文件的入口
static - 靜態資源文件
.babelrc - babel編譯
postcss.config.js - css后處理器配置
后臺管理
開發中用的一些依賴模塊
- vue/vue-router/vuex - Vue全家桶
- axios - 一個現在主流并且很好用的請求庫 支持Promise
- qs - 用于解決axios POST請求參數的問題
- element-ui - 餓了么出品的vue2.0 pc UI框架
- babel-polyfill - 用于實現瀏覽器不支持原生功能的代碼
- highlight.js / marked- 兩者搭配實現Markdown的常用語法
- js-md5 - 用于登陸時加密
- nprogress - 頂部加載條
components
這個文件夾一般放入常用的組件, 比如 Loading組件等等
views
所有模塊頁面
store
vuex用來統一管理公用屬性, 和統一管理接口
1. 登陸
登陸是采用 jsonwebtoken方案 來實現整個流程的
- jwt.sign(payload, secretOrPrivateKey, [options, callback]) 生成TOKEN
- jwt.verify(token,secretOrPublicKey,[options,callback]) 驗證TOKEN
- 獲取用戶的賬號密碼
-
通過 jwt.sign 方法來生成token
//server端 import jwt from 'jsonwebtoken'let data = { //用戶信息username,roles,... }let payload = { // 可以把常用信息存進去id: data.userId, //用戶IDusername: data.username, // 用戶名roles: data.roles // 用戶權限 }, secret = 'admin_token'// 通過調用 sign 方法, 把 **用戶信息**、**密鑰** 生成token,并設置過期時間 let token = jwt.sign(payload, secret, {expiresIn: '24h'})// 存入cookie發送給前臺 ctx.cookies.set('Token-Auth', token, {httpOnly: false }) - 每次請求數據的時候通過 jwt.verify 檢測token的合法性 jwt.verify(token, secret)
2. 權限
通過不同的權限來動態修改路由表
-
通過 vue的 鉤子函數 beforeEach 來控制并展示哪些路由, 以及判斷是否需要登陸
import store from '../store' import { getToken } from 'src/utils/auth' import { router } from './index' import NProgress from 'nprogress' // Progress 進度條 import 'nprogress/nprogress.css' // Progress 進度條樣式const whiteList = ['/login']; router.beforeEach((to, from, next) => {NProgress.start()if (getToken()) { //存在tokenif (to.path === '/login') { //當前頁是登錄直接跳過進入主頁next('/')}else{if (!store.state.user.roles) { //拉取用戶信息store.dispatch('getUserInfo').then( res => {let roles = res.data.rolesstore.dispatch('setRoutes', {roles}).then( () => { //根據權限動態添加路由router.addRoutes(store.state.permission.addRouters)next({ ...to }) //hash模式 確保路由加載完成})})}else{next()}}}else{if (whiteList.indexOf(to.path) >= 0) { //是否在白名單內,不在的話直接跳轉登錄頁next()}else{next('/login')}} }) router.afterEach((to, from) => {document.title = to.nameNProgress.done() })export default router-
通過調用 getUserInfo方法傳入 token 獲取用戶信息, 后臺直接解析 token 獲取里面的 信息 返回給前臺
getUserInfo ({state, commit}) {return new Promise( (resolve, reject) => {axios.get('user/info',{token: state.token}).then( res => {commit('SET_USERINFO', res.data)resolve(res)}).catch( err => {reject(err)})}) }
-
-
通過調用 setRoutes方法 動態生成路由
import { constantRouterMap, asyncRouterMap } from 'src/router'const hasPermission = (roles, route) => {if (route.meta && route.meta.role) {return roles.some(role => route.meta.role.indexOf(role) >= 0)} else {return true} }const filterAsyncRouter = (asyncRouterMap, roles) => {const accessedRouters = asyncRouterMap.filter(route => {if (hasPermission(roles, route)) {if (route.children && route.children.length) {route.children = filterAsyncRouter(route.children, roles)}return true}return false})return accessedRouters }const permission = {state: {routes: constantRouterMap.concat(asyncRouterMap),addRouters: []},mutations: {SETROUTES(state, routers) {state.addRouters = routers;state.routes = constantRouterMap.concat(routers);}},actions: {setRoutes({ commit }, info) {return new Promise( (resolve, reject) => {let {roles} = info;let accessedRouters = [];if (roles.indexOf('admin') >= 0) {accessedRouters = asyncRouterMap;}else{accessedRouters = filterAsyncRouter(asyncRouterMap, roles)}commit('SETROUTES', accessedRouters)resolve()})}} } export default permission
axios 請求封裝, 統一對請求進行管理
import axios from 'axios' import qs from 'qs' import { Message } from 'element-ui'axios.defaults.withCredentials = true // 發送時 axios.interceptors.request.use(config => {// 開始(LLoading動畫..)return config }, err => {return Promise.reject(err) })// 響應時 axios.interceptors.response.use(response => response, err => Promise.resolve(err.response))// 檢查狀態碼 function checkStatus(res) { // 結束(結束動畫..)if (res.status === 200 || res.status === 304) {return res.data}return {code: 0,msg: res.data.msg || res.statusText,data: res.statusText}return res }// 檢查CODE值 function checkCode(res) {if (res.code === 0) {Message({message: res.msg,type: 'error',duration: 2 * 1000})throw new Error(res.msg)}return res }const prefix = '/admin_demo_api/' export default {get(url, params) {if (!url) returnreturn axios({method: 'get',url: prefix + url,params,timeout: 30000}).then(checkStatus).then(checkCode)},post(url, data) {if (!url) returnreturn axios({method: 'post',url: prefix + url,data: qs.stringify(data),timeout: 30000}).then(checkStatus).then(checkCode)},postFile(url, data) {if (!url) returnreturn axios({method: 'post',url: prefix + url,data}).then(checkStatus).then(checkCode)} }面包屑 / 標簽路徑
- 通過檢測路由來把當前路徑轉換成面包屑
-
把訪問過的路徑儲存在本地,記錄下來,通過標簽直接訪問
// 面包屑 getBreadcrumb() {let matched = this.$route.matched.filter(item => item.name);let first = matched[0],second = matched[1];if (first && first.name !== '首頁' && first.name !== '') {matched = [{name: '首頁', path: '/'}].concat(matched);}if (second && second.name === '首頁') {this.levelList = [second];}else{this.levelList = matched;} }// 檢測路由變化 watch: {$route() {this.getBreadcrumb();} }
上面介紹了幾個主要以及必備的后臺管理功能,其余的功能模塊 按照需求增加就好
前臺
前臺展示的頁面跟后臺管理界面差不多, 也是用vue+webpack搭建,基本的結構都差不多,具體代碼實現的可以直接在github下載便行
server端
權限
主要是通過 jsonwebtoken 的verify方法檢測cookie 里面的token 驗證它的合法性
import jwt from 'jsonwebtoken' import conf from '../../config'export default () => {return async (ctx, next) => {if ( conf.auth.blackList.some(v => ctx.path.indexOf(v) >= 0) ) { // 檢測是否在黑名單內let token = ctx.cookies.get(conf.auth.tokenKey);try {jwt.verify(token, conf.auth.admin_secret);}catch (e) {if ('TokenExpiredError' === e.name) {ctx.sendError('token已過期, 請重新登錄!');ctx.throw(401, 'token expired,請及時本地保存數據!');}ctx.sendError('token驗證失敗, 請重新登錄!');ctx.throw(401, 'invalid token');}console.log("鑒權成功");}await next();} }日志
日志是采用 log4js 來進行管理的,
log4js 算 nodeJs 常用的日志處理模塊,用起來額也比較簡單
-
log4js 的日志分為九個等級,各個級別的名字和權重如下:
圖 - 設置 Logger 實例的類型 logger = log4js.getLogger('cheese')
- 通過 Appender 來控制文件的 名字、路徑、類型
- 配置到 log4js.configure
-
便可通過 logger 上的打印方法 來輸出日志了 logger.info(JSON.stringify(currTime: 當前時間為${Date.now()}s))
//指定要記錄的日志分類 let appenders = {} appenders.all = {type: 'dateFile', //日志文件類型,可以使用日期作為文件名的占位符filename: `${dir}/all/`, //日志文件名,可以設置相對路徑或絕對路徑 pattern: 'task-yyyy-MM-dd.log', //占位符,緊跟在filename后面 alwaysIncludePattern: true //是否總是有后綴名 } let logConfig = {appenders,/*** 指定日志的默認配置項* 如果 log4js.getLogger 中沒有指定,默認為 cheese 日志的配置項*/categories: {default: {appenders: Object.keys(appenders),level: logLevel}} } log4js.configure(logConfig)
定制書寫規范(API)
-
設計思路
//other.js const path = require('path');module.exports = {async markdown_upload_img (ctx, next) {console.log('----------------添加圖片 markdown_upload_img-----------------------');let opts = {path: path.resolve(__dirname, '../../../../public')}let result = await ctx.uploadFile(ctx, opts)ctx.send(result)},async del_markdown_upload_img (ctx, next) {console.log('----------------刪除圖片 del_markdown_upload_img-----------------------');let id = ctx.request.query.idtry {ctx.remove(musicModel, {_id: id})ctx.send()}catch(e){ctx.sendError(e)}// console.log(id)} }
當應用程序啟動時候,讀取指定目錄下的 js 文件,以文件名作為屬性名,掛載在實例 app 上,然后把文件中的接口函數,擴展到文件對象上讀取出來的便是以下形式:
async markdown_upload_img (ctx, next) {console.log('----------------添加圖片 markdown_upload_img-----------------------');let opts = {path: path.resolve(__dirname, '../../../../public')}let result = await ctx.uploadFile(ctx, opts)ctx.send(result) }
app.controller.admin.other.markdown_upload_img 便能讀取到 markdown_upload_img 方法在把該形式的方法 賦值過去就行
router.post('/markdown_upload_img', app.controller.admin.other.markdown_upload_img)
通過 mongoose 鏈接 mongodb
import mongoose from 'mongoose' import conf from './config' // const DB_URL = `mongodb://${conf.mongodb.address}/${conf.mongodb.db}` const DB_URL = `mongodb://${conf.mongodb.username}:${conf.mongodb.pwd}@${conf.mongodb.address}/${conf.mongodb.db}`; // 賬號登陸 mongoose.Promise = global.Promise mongoose.connect(DB_URL, { useMongoClient: true }, err => {if (err) {console.log("數據庫連接失敗!")}else{console.log("數據庫連接成功!")} }) export default mongoose封裝返回的send函數
export default () => {let render = ctx => {return (json, msg) => {ctx.set("Content-Type", "application/json");ctx.body = JSON.stringify({code: 1,data: json || {},msg: msg || 'success'});}}let renderError = ctx => {return msg => {ctx.set("Content-Type", "application/json");ctx.body = JSON.stringify({code: 0,data: {},msg: msg.toString()});}}return async (ctx, next) => {ctx.send = render(ctx);ctx.sendError = renderError(ctx);await next() } }通過 koa-static 管理靜態文件入口
注意事項:
登錄后臺管理時需要在數據庫 創建 users 集合注冊一個賬號進行登錄
db.users.insert({"name" : "cd","pwd" : "e10adc3949ba59abbe56e057f20f883e","username" : "admin","roles" : [ "admin"] })// 賬號: admin 密碼: 123456參考文章
個人博客github
基于Koa2搭建Node.js實戰項目教程
手摸手,帶你用vue擼后臺
總結
以上是生活随笔為你收集整理的vue+node+mongodb 搭建一个完整博客的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: k8s 创建资源的两种方式 - 每天5分
- 下一篇: 机器学习实战笔记(Python实现)-0