Vue + Node + Mongodb 开发一个完整博客流程
Posted SegmentFault
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 - 代码区域:
components - 组件
filters - 过滤器
font - 字体/字体图标
images - 图片
router - 路由
store - vuex状态管理
styles - 样式表
utils - 请求封装
views - 页面模块
App.vue - app组件
custom-components.js - 自定义组件导出
main.js - 入口JS
index.html - webpack 模板文件
2.client - web端界面源码
跟后台管理界面的结构基本一样。
3.server - 服务端源码
说明:
controller: 所有接口逻辑代码
middleware: 所有的中间件
models: 数据库model
router: 路由/接口
app.js: 入口
config.js: 配置文件
index.js: babel编译
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
所有模块页面。
store
vuex 用来统一管理公用属性, 和统一管理接口。
登陆
登陆是采用 jsonwebtoken方案 来实现整个流程的。
1. jwt.sign(payload,secretOrPrivateKey,[options,callback])
生成TOKEN
2. jwt.verify(token,secretOrPublicKey,[options,callback])
验证TOKEN
3.获取用户的账号密码。
4.通过 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 })
5.每次请求数据的时候通过 jwt.verify
检测token的合法性 jwt.verify(token,secret)
。
权限
通过不同的权限来动态修改路由表。
通过 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 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) 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下载便行。
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 的日志分为九个等级,各个级别的名字和权重如下:
1.图。
2.设置 Logger 实例的类型 logger=log4js.getLogger('cheese')
。
3.通过 Appender
来控制文件的 名字、路径、类型 。
4.配置到 log4js.configure
。
5.便可通过 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)
通过 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 管理静态文件入口
注意事项:
1. cnpm run server
启动服务器
2.启动时,记得启动mongodb数据库,账号密码 可以在 server/config.js 文件下进行配置
3. db.createUser({user:"cd",pwd:"123456",roles:[{role:"readWrite",db:'test'}]})
(mongodb 注册用户)
4. cnpm run dev:admin
启动后台管理界面
5.登录后台管理界面录制数据
6.登录后台管理时需要在数据库 创建 users 集合注册一个账号进行登录
db.users.insert({
"name" : "cd",
"pwd" : "e10adc3949ba59abbe56e057f20f883e",
"username" : "admin",
"roles" : [
"admin"
]
})
// 账号: admin 密码: 123456
7. cnpm run dev:client
启动前台页面
参考文章
个人博客:http://dzblog.cn/article/5a69609c3c04164b0bd4b964
github:https://github.com/cd-dongzi/vue-node-blog
基于Koa2搭建Node.js实战项目教程:https://github.com/ikcamp/koa2-tutorial
手摸手,带你用vue撸后台:https://segmentfault.com/a/1190000010043013
相关文章推荐
以上是关于Vue + Node + Mongodb 开发一个完整博客流程的主要内容,如果未能解决你的问题,请参考以下文章
Part1用JS写一个Blog(node + vue + mongoDB)
推荐!前端 Vue+Node+MongoDB高级全栈开发(完结)