对于已有前端开发经验的朋友来讲,深刻学习node.js而且掌握它是一个必要的过程;对于即将毕业的软件开发学生来讲,创造拥有本身的开源小产品是对大学最好的总结。javascript
那么想和你们分享一个运用Vue框架写界面+Express框架写后端接口的这样一个开箱即用的先后端分离的项目,能更好更快的帮助你们学习node+vue,而且也可在项目开发中使用,减轻你们的工做负担。html
node版本:10.16.3前端
express版本:4.16.1vue
npm版本:6.9.0java
vue版本: ^2.6.10node
@vue/cli版本:^3.4.0mysql
express-demo文件夹下的目录以下:ios
| app.js express 入口
│ package-lock.json
│ package.json 项目依赖包配置
│
├─bin
│ www
|
|——common 公共方法目录
| |——corsRequest.js
| |——errorLog.js
| |——loginFilter.js
| |——request.log
│
|
|——config 配置文件
| |——index.js
|
|——log 日志文件夹
|
├─public 静态文件目录
│ |——images
│ |——javascripts
│ └─stylesheets
│
├─routes url路由配置
│
│—sql 数据库基本配置目录
| |——db.js
|__
复制代码
vue-demo文件夹下的目录以下:git
|——src 源码目录 | |——api 请求文件 | |——components vue公共组件 | |——pages 页面组件文件 | |——login 登陆页面 | |——default.vue 默认组件展现区 | |——router vue的路由管理文件 | |——store vue的状态管理文件 | |——App.vue 页面入口文件 | |—— main.js 程序入口文件,加载各类公共组件 | |—— public 静态文件,好比一些图片,json数据等 | |—— favicon.ico 图标文件 | |—— index.html 入口页面 |—— vue.config.js 是一个可选的配置文件,包含了大部分的vue项目配置 |—— .babel.config.js ES6语法编译配置 |—— .gitignore git上传须要忽略的文件格式 |—— README.md 项目说明 |—— package.json 项目基本信息,包依赖信息等 复制代码
(1)安装插件,在安装插件以前要进入express-demo目录,运行命令行:github
npm install
复制代码
(2)开发环境启动,进入express-demo 目录,运行如下命令:
npm start
复制代码
(3)生产或测试环境,可在config文件夹配置路径,运行如下命令:
npm test //测试环境 npm build //生产环境 复制代码
(1)安装插件,在安装插件以前要进入vue-demo目录,运行命令行:
npm install
复制代码
(2)开发环境启动,进入vue-demo 目录,运行如下命令:
npm run serve
复制代码
在common文件夹的corsRequest.js对跨域进行了配置,而且将它在app.js 文件进行引用,这样客户端例如:单页面应用开发、移动应用等, 就能够跨域访问服务端对应的接口。
corsRequest.js模块的代码以下:
function corsRequest(app){ app.all("*",(req,res,next)=>{ //设置容许跨域的域名,*表明容许任意域名跨域 res.header("Access-Control-Allow-Origin",'http://localhost:8081'); //容许的header类型 // res.header("Access-Control-Allow-Headers","X-Requested-With,Content-Type,content-type"); res.header('Access-Control-Allow-Headers:Origin,X-Requested-With,Authorization,Content-Type,Accept,Z-Key') //跨域容许的请求方式 res.header("Access-Control-Allow-Methods","DELETE,PUT,POST,GET,OPTIONS"); res.header("X-Powered-By","3.2.1"); res.header("Content-Type","application/json;charset=utf-8"); res.header("Access-Control-Allow-Credentials",true); res.header("Cache-Control","no-store"); if (req.method.toLowerCase() == 'options'){ res.send(200); //让options尝试请求快速结束 }else{ next(); } }) } module.exports=corsRequest; 复制代码
以后再app.js引用该模块,配置以下:
let corsRequest=require('./common/corsRequest.js'); //跨域配置 corsRequest(app); 复制代码
客户端经过登陆请求成功以后,服务端保存用户信息,而且将信息经过cookie返给客户端这整个过程我把它认为是种session存cookie;利用的是cookie-parser中间件和express-session中间件,如何使用它可详细看cookie-parser和express-session中间件使用,那么在该项目中的配置以下:
//种session存cookie配置 app.use(cookieParser('123456')); app.use(session({ secret: '123456', resave: false, saveUninitialized: true })); 复制代码
这里不具体介绍 mysql 的安装配置,具体操做能够参照配置文档,建议安装一个 mysql 数据库管理工具 navicat for mysql ,平时用来查看数据库数据增删改查的状况;
在 sql/db.js 文件中配置 mysql 的基本信息,相关配置项以下,能够对应更改配置项,改为你本身的配置便可:
//在sql目录下的db.js文件 let host=option.host||'127.0.0.1'; //数据库所在的服务器的ip地址 let user=option.user||'root'; //用户名 let password=option.password||'1qaz@WSX'; //密码 let database=option.database ||null //你的数据库名 let db=mysql.createConnection({ host:host, user:user, password:password, database:database }); //建立链接 复制代码
app.use(express.static(path.join(__dirname, 'public'))); 复制代码
客户端在每次请求时,服务端处理每一个请求以前须要对用户身份进行校验,这样使得每一个接口更具备安全性,在common文件夹下的loginFilter.js的相关代码逻辑以下:
function loginFilter(app){ app.use(function (req,res,next){ if(!(req.session.auth_username && req.session.auth_password)){ if(req.signedCookies.username && req.signedCookies.password){ let {username,password}=req.signedCookies; req.session.auth_username=username; req.session.auth_password=password; //将cookie的值存在session里 next(); }else{ let arr=req.url.split('/'); let index=arr && arr.findIndex((item)=>{ return (item.indexOf('login')!=-1 || item.indexOf('relogin')!=-1); }); if(index!==-1){ next(); }else{ return res.status(401).json({ msg: '没有登入,请先登入' }) } } }else{ next(); } }) } module.exports=loginFilter; 复制代码
以后在app.js进行引用该模块,配置以下:
//登陆拦截器 loginFilter(app); 复制代码
在项目中按模块划分请求处理,咱们在 routes 目录下分别新建每一个模块路由配置,例如用户模块,则为 user.js 文件,咱们在 app.js 主入口文件中引入每一个模块的路由,以用户模块进行举例,相关代码逻辑以下:
let user=require('./routes/user'); //路由配置 app.use('/api/user',user); 复制代码
(1)经过morgan记录接口请求日志 morgan 是 express 默认的日志中间件,也可脱离 express,做为 node.js 的日志组件单独使用。详细学习可看GitHub库上的morgan模块。
项目在 app.js 文件中进行了如下配置:
// 输出日志到目录 let accessLogStream = fs.createWriteStream(path.join(__dirname,'./log/request.log'), {flags:'a',encoding: 'utf8' }); app.use(logger('combined', { stream: accessLogStream })) 复制代码
(2)经过winston记录错误日志 morgan 只能记录 http 请求的日志,因此还须要 winston来记录其它想记录的日志,例如:访问数据库出错等。winston 中的每个 logger 实例在不一样的日志级别能够存在多个传输配置。详细学习可看GitHub库上的winston模块。
在项目中,在 common/errorLog.js 文件中进行了如下配置:
const { createLogger, format, transports } = require('winston'); const { combine, timestamp, printf } = format; const path = require('path'); const myFormat = printf(({ level, message, label, timestamp }) => { return `${timestamp}-${level}- ${message}`; }); const option={ file:{ level:'error', filename: path.join(__dirname, '../log/error.log'), handleExceptions:true, json:true, maxsize:5242880, maxFiles:5, colorize:true, }, console:{ level:'debug', handleExceptions:true, json:false, colorize:true, } } const logger=createLogger({ format: combine( timestamp(), myFormat ), transports:[ new transports.File(option.file), new transports.Console(option.console), ], exitOnError:false, }); logger.stream={ write:function(message,encoding){ logger.error(message) } } module.exports=logger; 复制代码
以后在app.js使用logger模块,代码配置以下:
let winston=require('./common/errorLog.js'); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env')=== 'development' ? err : {}; winston.error(`${err.status || 500} - ${err.message} - ${req.originalUrl} -${req.method} - ${req.ip}`); // render the error page res.status(err.status || 500); res.render('error');}); 复制代码
在项目中,使用multiparty模块实现文件的上传功能。包括如何将数据返回给前端,前端可经过url将图片显示。multiparty模块具体用法可详细阅读github,这里就不详细介绍。在文件夹routes的upload.js是实现文件上传的功能。
node提供的内置文件操做模块fs,fs的功能强大。包括读写文件/文件夹,以及如何读流写流。在使用过程当中可具体参考node官网,便于快速上手。
环境变量的统一管理配置能够更方便的维护项目,在package.json文件经过命令行给全局变量process.env添加新的属性,从而可配置项目环境变量:
let obj=Object.create(null); if (process.env.NODE_ENV='development'){ obj.baseUrl="http://localhost:3000"; } else if(process.env.NODE_ENV = 'test') { console.log('当前是测试环境') }else if(process.env.NODE_ENV='production'){ console.log('当前是生产环境'); } module.exports=obj; 复制代码
当启动项目时,更改项目中的任何一个文件代码,运用nodemon模块它将会自动帮你从新编译代码,节省了咱们开发的时间。这个模块只要在package.json这个文件作一下配置便可。
vue项目中,经过vue-router实现页面之间的跳转,也能够经过路由进行参数的收发,vue-router的相关代码配置以下:
import Router from 'vue-router'; import Vue from 'vue'; Vue.use(Router); export default new Router({ mode:'hash', routes:[ { path:'/', redirect:'/login', component:()=>import('@/pages/login/index.vue') }, ] }) 复制代码
vue项目中,经过vuex实现状态的统一管理,也可运用于多层嵌套组件的通讯,以及如何持久化保存state状态值。vuex的相关代码配置以下:
import Vuex from 'vuex'; import Vue from 'vue'; import * as mutaionsType from './mutations-TYPE'; Vue.use(Vuex); let persits=(store)=>{ let state; if(state=sessionStorage.getItem('vuex-state')) store.replaceState(JSON.parse(state)); store.subscribe((mutations,state)=>{ sessionStorage.setItem('vuex-state',JSON.stringify(state)); }) } export default new Vuex.Store({ plugins:[persits], state:{ cancelArray:[], //存放axios取消函数容器 }, mutations:{ [mutaionsType.clear_cancel]:(state,payload)=>{ state.cancelArray.forEach(fn=>{ fn.call(fn); }) state.cancelArray=[]; }, [mutaionsType.filter_cancel]:(state,payload)=>{ let arr=state.cancelArray.filter(item=>!(item.url.includes(payload))); state.cancelArray=[...arr]; }, [mutaionsType.push_cancel]:(state,payload)=>{ state.cancelArray.push(payload); }, }, actions:{}, modules:{} }) 复制代码
经过运用axios来请求接口,在项目当中对axios进行了二次封装,主要的功能包括:实现aixos请求拦截器和响应拦截,请求先后loading的开启和关闭,利用发布订阅实现取消请求函数的配置。相关代码以下:
import { baseURL } from './config.js' import axios from 'axios' import store from '@/store' import { Loading, Message } from 'element-ui' //每请求一次建立一个惟一的axios class AjaxFetch { constructor() { this.config = { withCredentials: true, //跨域凭证 responseType: 'json', baseURL: baseURL, timeout: 3000, } this.queue = {} } request(option) { //建立一个axios实例 let config = { ...this.config, ...option, } let instance = axios.create() this.interceptors(instance,config.url) return instance(config) } interceptors(instance,url) { instance.interceptors.request.use( (config) => { let CancelToken = axios.CancelToken //设置取消函数 config.cancelToken = new CancelToken((c) => { //c是一个函数 store.commit('push_cancel', { fn: c, url:url }) //存放取消的函数实例 }) if (Object.keys(this.queue).length == 0) { this._loading = Loading.service({ lock: true, text: 'Loading', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)', }) } this.queue[url] = url; return config; }, (err) => { return Promise.reject(err) } ) instance.interceptors.response.use( (response) => { let {data} = response; store.commit('filter_cancel',url) //存放取消的函数实例 delete this.queue[url] if (Object.keys(this.queue).length == 0) { this._loading.close() } switch (data.code) { case 500: Message({ type: 'error', message: data.msg, }) break; case 401: Message({ type: 'warning', message: data.msg, }) break; } return data; }, (err) => { delete this.queue[url]; if (Object.keys(this.queue).length == 0) { this._loading.close(); } return Promise.reject(err) } ) } } export default new AjaxFetch() 复制代码
在vue项目中,咱们想直接经过this使用第三方库,可将第三方库直接挂载到Vue类的原型(prototype)上。若是是vue生态圈的模块,则直接经过选项注册在根组件上。
在vue项目中,使用路由的前置守卫实现的功能主要包括:登陆受权,切换页面将发布axios取消函数。固然你还能够编写更多的前置守卫业务代码。以下是hook.js的代码:
import store from '@/store' export default { permitterRouter: function(to, from, next) { let { username } = store.state; let flag=Object.keys(username).length; //判断是否登陆过的标识 if(!flag){ if(to.path.includes('/login')){ next(); }else{ next('/login'); } }else{ if(to.path.includes('/login')){ next('/home'); }else{ next(); } } }, cancelAjax: (to, from, next) => { store.commit('clear_cancel') next() }, } 复制代码
那么写好hook.js在文件夹route下的index.js进行配置以下:
//路由前置守卫 Object.values(hookRouter).forEach(hook=>{ //使用bind可在hook函数获取this=>router router.beforeEach(hook.bind(router)) }) 复制代码
本文介绍了开源的先后端分离项目(开箱即用),完善了先后端的各类功能。但愿能经过这篇文档对开源代码进行更直接的介绍,帮助使用者减轻工做量,更高效完成工做,有更多时间提高本身的能力。 辛苦整理了很久,还望手动点赞鼓励小琳同窗~~