摘要:開發一個完整博客流程前言前段時間剛把自己的個人網站寫完,于是這段時間因為事情不是太多,便整理了一下,寫了個簡易版的博客系統服務端用的是框架進行開發技術棧目錄結構講解的配置文件放置代碼文件項目參數配置的文件日志打印文件項目依賴模塊
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 - 后臺管理界面源碼
src - 代碼區域 1. components - 組件 2. filters - 過濾器 3. font - 字體/字體圖標 4. images - 圖片 5. router - 路由 6. store - vuex狀態管理 7. styles - 樣式表 8. utils - 請求封裝 9. views - 頁面模塊 10. App.vue - app組件 11. custom-components.js - 自定義組件導出 12. main.js - 入口JS index.html - webpack 模板文件
2.client - web端界面源碼
跟后臺管理界面的結構基本一樣
3.server - 服務端源碼
1. controller: 所有接口邏輯代碼 2. middleware: 所有的中間件 3. models: 數據庫model 4. router: 路由/接口 5. app.js: 入口 6. config.js: 配置文件 7. index.js: babel編譯 8. mongodb.js: mongodb配置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所有模塊頁面
storevuex用來統一管理公用屬性, 和統一管理接口
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, //用戶ID username: 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()) { //存在token if (to.path === "/login") { //當前頁是登錄直接跳過進入主頁 next("/") }else{ if (!store.state.user.roles) { //拉取用戶信息 store.dispatch("getUserInfo").then( res => { let roles = res.data.roles store.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.name NProgress.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 permissionaxios 請求封裝, 統一對請求進行管理
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) return return axios({ method: "get", url: prefix + url, params, timeout: 30000 }).then(checkStatus).then(checkCode) }, post(url, data) { if (!url) return return axios({ method: "post", url: prefix + url, data: qs.stringify(data), timeout: 30000 }).then(checkStatus).then(checkCode) }, postFile(url, data) { if (!url) return return 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下載便行
主要是通過 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)
設計思路
當應用程序啟動時候,讀取指定目錄下的 js 文件,以文件名作為屬性名,掛載在實例 app 上,然后把文件中的接口函數,擴展到文件對象上
//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.id try { ctx.remove(musicModel, {_id: id}) ctx.send() }catch(e){ ctx.sendError(e) } // console.log(id) } }
讀取出來的便是以下形式:
app.controller.admin.other.markdown_upload_img 便能讀取到 markdown_upload_img 方法
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) }
在把該形式的方法 賦值過去就行
router.post("/markdown_upload_img", app.controller.admin.other.markdown_upload_img)
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 管理靜態文件入口 注意事項:
cnpm run server 啟動服務器
啟動時,記得啟動mongodb數據庫,賬號密碼 可以在 server/config.js 文件下進行配置
db.createUser({user:"cd",pwd:"123456",roles:[{role:"readWrite",db:"test"}]}) (mongodb 注冊用戶)
cnpm run dev:admin 啟動后臺管理界面
登錄后臺管理界面錄制數據
登錄后臺管理時需要在數據庫 創建 users 集合注冊一個賬號進行登錄
db.users.insert({ "name" : "cd", "pwd" : "e10adc3949ba59abbe56e057f20f883e", "username" : "admin", "roles" : [ "admin" ] }) // 賬號: admin 密碼: 123456
cnpm run dev:client 啟動前臺頁面
參考文章
個人博客
github
基于Koa2搭建Node.js實戰項目教程
手摸手,帶你用vue擼后臺
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/19170.html
摘要:項目地址這個項目是為了學習而建的,從前端到后端一手包辦。相對來說,還是有一定難度的,適合有一定編程基礎的人進階學習。教程一教程二在安裝完后,克隆項目。 項目地址 這個項目是為了學習 node 而建的,從前端到后端一手包辦。相對來說,還是有一定難度的,適合有一定編程基礎的人進階學習。 如果有問題,歡迎提 issues 注意,本項目的前后端代碼都是放在一起的,前端代碼放在 src 目錄,后...
摘要:搭建個人博客很久以來就特別想搭建一個,但是都是由于技術原因沒有搭建起來。學習,并對代碼進行重構。因為整個博客要完整的從項目構建到項目上線的確比較繁瑣,這里只是給想要試試與一個代碼參考。 vue+koa+mongodb 搭建個人博客 很久以來就特別想搭建一個blog,但是都是由于技術原因沒有搭建起來。以前學習github的時候準備采用github與hexo來搭建。但是后來想了一下自己也在...
摘要:本來不想推的,看到上有個項目很簡單,都有,推推看咯。雖然這個項目很簡單,但是還蠻有趣,用來入門和以及再好不過了。 本來不想推的,看到github上有個項目很簡單,都有300 star,推推看咯。雖然這個項目很簡單,但是還蠻有趣,用來入門vue2和nodejs以及mongodb再好不過了。 等這幾天把公司手頭的事情忙完,再把vuex的部分強化下。 基于vue2/vuex/vue-rout...
摘要:搭建后臺的全過程近期基于搭建前端項目,搭建后臺,遇到了不少問題,總結博客如下,有什么不正確的地方,請大家批評指正是非關系型數據庫。是用來啟動的,是的命令行客戶端。 Node + mongoDB 搭建后臺的全過程 近期基于 vue-cil 搭建前端項目, express + mongoose 搭建后臺,遇到了不少問題,總結博客如下,有什么不正確的地方,請大家批評指正^?_?^! mong...
摘要:前言在今年三月開學就萌生了做自己個人網站的想法,也了解到有等靜態博客工具。期間網站做了三次升級應該達到了一個開源的要求。這是我的個人網站,歡迎關注建站教程,我會同步更新歡迎關注主要功能介紹發表修改保存刪除文章。 前言 在今年三月開學就萌生了做自己個人網站的想法,也了解到有HEXO等靜態博客工具。但是作為一個想要進入前端坑的新人來講,沒有什么是比自己手動建站更有意義的事了。感謝論壇各位同...
閱讀 2066·2019-08-30 15:53
閱讀 3064·2019-08-30 15:44
閱讀 2913·2019-08-30 14:11
閱讀 2910·2019-08-30 14:01
閱讀 2694·2019-08-29 15:16
閱讀 3719·2019-08-29 13:10
閱讀 1239·2019-08-29 10:56
閱讀 2526·2019-08-26 13:58