开箱即用:Vue和Express搭建的一个功能完善的前后端分离项目

7,424 阅读10分钟

前言

对于已有前端开发经验的朋友来说,深入学习node.js并且掌握它是一个必要的过程;对于即将毕业的软件开发学生来说,创造拥有自己的开源小产品是对大学最好的总结。

那么想和大家分享一个运用Vue框架写界面+Express框架写后端接口的这样一个开箱即用的前后端分离的项目,能更好更快的帮助大家学习node+vue,并且也可在项目开发中使用,减轻大家的工作负担。

整理已久的文档和项目,还望大家手动点赞鼓励哈~~

因我之前是用博客记录自己的成长,我的博客地址为:Miss_hhl的博客,汇总了我的所有博客,也欢迎大家的关注。

本项目 github 地址为:project项目

一、项目结构

1.1、项目运行环境

node版本:10.16.3

express版本:4.16.1

npm版本:6.9.0

vue版本: ^2.6.10

@vue/cli版本:^3.4.0

1.2、后端Express框架的项目目录

express-demo文件夹下的目录如下:


|  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
|__

1.3、前端Vue框架的项目目

vue-demo文件夹下的目录如下:

|——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               项目基本信息,包依赖信息等

二、如何在编辑器使用该项目

2.1、后端Express框架的使用

(1)安装插件,在安装插件之前要进入express-demo目录,运行命令行:

npm install

(2)开发环境启动,进入express-demo 目录,运行以下命令:

npm start

(3)生产或测试环境,可在config文件夹配置路径,运行以下命令:

npm test   //测试环境
npm build  //生产环境

2.2、前端Vue框架的使用

(1)安装插件,在安装插件之前要进入vue-demo目录,运行命令行:

npm install

(2)开发环境启动,进入vue-demo 目录,运行以下命令:

npm run serve

三、项目提供的功能点梳理

3.1、后端Express框架的功能点

3.1.1、开发环境解决跨域配置

在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);

3.1.2、保存用户信息——种session存cookie

客户端通过登录请求成功之后,服务端保存用户信息,并且将信息通过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
}));

3.1.3、结合数据库mysql

安装配置mysql

这里不具体介绍 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 
});     //创建连接

3.1.4、静态文件目录配置

app.use(express.static(path.join(__dirname, 'public')));

3.1.5、登录拦截器

客户端在每次请求时,服务端处理每个请求之前需要对用户身份进行校验,这样使得每个接口更具有安全性,在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);

3.1.6、请求路由配置处理

在项目中按模块划分请求处理,我们在 routes 目录下分别新建每个模块路由配置,例如用户模块,则为 user.js 文件,我们在 app.js 主入口文件中引入每个模块的路由,以用户模块进行举例,相关代码逻辑如下:

let user=require('./routes/user');
//路由配置
app.use('/api/user',user);

3.1.7、日志处理

(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');});

3.1.8、文件上传处理

在项目中,使用multiparty模块实现文件的上传功能。包括如何将数据返回给前端,前端可通过url将图片显示。multiparty模块具体用法可详细阅读github,这里就不详细介绍。在文件夹routes的upload.js是实现文件上传的功能。

3.1.9、文件操作处理

node提供的内置文件操作模块fs,fs的功能强大。包括读写文件/文件夹,以及如何读流写流。在使用过程中可具体参考node官网,便于快速上手。

3.1.10、环境变量统一配置

环境变量的统一管理配置可以更方便的维护项目,在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;

3.1.11、自动更新

当启动项目时,更改项目中的任何一个文件代码,运用nodemon模块它将会自动帮你重新编译代码,节省了我们开发的时间。这个模块只要在package.json这个文件做一下配置即可。

3.2、前端Vue框架的功能点

3.2.1、配置页面路由

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')
		},
	]
})

3.2.2、配置公共仓库

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:{}
})

3.2.3、二次封装axios

通过运用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()

3.2.4、配置第三方库

在vue项目中,我们想直接通过this使用第三方库,可将第三方库直接挂载到Vue类的原型(prototype)上。如果是vue生态圈的模块,则直接通过选项注册在根组件上。

3.2.5、使用路由守卫

在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))
})

四、总结

本文介绍了开源的前后端分离项目(开箱即用),完善了前后端的各种功能。希望能通过这篇文档对开源代码进行更直接的介绍,帮助使用者减轻工作量,更高效完成工作,有更多时间提升自己的能力。 辛苦整理了好久,还望手动点赞鼓励小琳同学~~

博客地址为:Miss_hhl的博客,汇总了我所有的博客,欢迎大家关注~~

本项目 github 地址为:project项目,还望大家点个star鼓励~~(项目会持续更新迭代哦~希望大家持续关注哈)