對於已有前端開發經驗的朋友來講,深刻學習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)) }) 複製代碼
本文介紹了開源的先後端分離項目(開箱即用),完善了先後端的各類功能。但願能經過這篇文檔對開源代碼進行更直接的介紹,幫助使用者減輕工做量,更高效完成工做,有更多時間提高本身的能力。 辛苦整理了很久,還望手動點贊鼓勵小琳同窗~~