node實(shí)現(xiàn)mock-plugin中間件的方法
寫在前面
最近在使用Mockjs
作為項(xiàng)目里面mock
數(shù)據(jù)的工具,發(fā)現(xiàn)mockjs
做的攔截部分是自己實(shí)現(xiàn)摸擬了一個(gè)XMLHttpRequest
的方法做的攔截,使用Mockjs
攔截請(qǐng)求后,在chrome
的network
上無(wú)法看到請(qǐng)求(具體mockjs使用方法可以查看他的api,mockjs-api這里我不多做闡述),但為了更加真實(shí)的像后臺(tái)返回?cái)?shù)據(jù),我自己使用Node
作為中間代理去實(shí)現(xiàn)了一個(gè)mock-plugin
.
express中間件介紹
因?yàn)椴寮喈?dāng)于是實(shí)現(xiàn)了一個(gè)express的中間件的方式,所以這里簡(jiǎn)單對(duì)express中間件的使用做一個(gè)說(shuō)明:
express
中間件通過(guò)app.use
(也有app.get,app.post
等方法)的方式注冊(cè)到express
的實(shí)例某個(gè)屬性上,將執(zhí)行函數(shù)存放在棧內(nèi)部,然后在回調(diào)執(zhí)行的時(shí)候調(diào)用next()
方法將執(zhí)行下一個(gè)存在棧內(nèi)的方法。
這里列舉一個(gè)示例:
const express = require('express'); const app = express(); app.use(function (req, res, next) { console.log('first all use'); next() }); app.use(function (req, res, next){ setTimeout(() => { console.log(`two all use`) next() }, 1000) }); app.use(function (req, res, next) { console.log('end all use') next() }); app.use('/', function (req, res, next) { res.end('hello use') }); app.listen(4000, function () { console.log(`起動(dòng)服務(wù)成功!`) });
通過(guò)node
執(zhí)行以上代碼后,在瀏覽器上通過(guò)訪問(wèn)http://locahost:4000
可以看到控制臺(tái)打?。?br />
可以發(fā)現(xiàn)在執(zhí)行的時(shí)候先執(zhí)行了use
注冊(cè)的中間件,然后再執(zhí)行到get
路由的時(shí)候,又執(zhí)行了app.use
注冊(cè)的中間件。
詳細(xì)的express中間件可以在express官網(wǎng)查看
實(shí)現(xiàn)dev-server
devServer
可以使用webpack-dev-server
然后通過(guò)before
的回調(diào)去做一層攔截,這樣也能夠?qū)崿F(xiàn)在響應(yīng)之前對(duì)后臺(tái)的數(shù)據(jù)做一些處理。
我這兒選擇自己實(shí)現(xiàn)一個(gè)devServer
,在之前使用webpack-dev-server
的服務(wù)大概需要配置,port
, proxy
,以及跨域https
等。當(dāng)然自己實(shí)現(xiàn)devServer
就沒(méi)必要實(shí)現(xiàn)那么多功能了,正常在開(kāi)發(fā)場(chǎng)景下很多也不一定用得上,這里我主要使用了webpack-dev-middleware和webpack-hot-middleware達(dá)到自動(dòng)編譯和熱更新的目的,以及可以自己在中間添加express中間件.
貼上代碼:
onst path = require('path'); const express = require('express'); const webpack = require('webpack'); const webpackConfig = require('./webpack.dev'); const devMiddleware = require('webpack-dev-middleware'); const hotMiddleware = require('webpack-hot-middleware'); const app = express(); const compiler = webpack(webpackConfig); // webpack開(kāi)發(fā)環(huán)境配置 const mockPlugin = require('./mock-plugin'); const config = { prd: 8800 }; // 注冊(cè)webpack-dev-middleware中間件 app.use( devMiddleware(compiler, { publicPath: webpackConfig.output.publicPath }) ); // 注冊(cè)webpack-hot-middleware中間件 app.use( hotMiddleware(compiler) ); // 注冊(cè)mockPlugin插件 app.use( mockPlugin({ routes: { '/app': 'http://locahost:3002', // 測(cè)試代理到服務(wù)器的地址 '/api': 'http://localhost:3003' // 測(cè)試代理到服務(wù)器的地址 }, root: path.resolve(__dirname) // 項(xiàng)目根目錄 }) ); app.listen(config.prd, function () { console.log('訪問(wèn)地址:', `http://localhost:${config.prd}`); });
具體的一些演示操作,這里也不多講了(這不是實(shí)現(xiàn)mock-plugin的重點(diǎn)),網(wǎng)上也有很多如果通過(guò)webpack-dev-middleware
和webpack-hot-middleware
的教程,唯一的區(qū)別是代理部分,網(wǎng)上可能用的是http-proxy
之類已現(xiàn)有的工具,因?yàn)槲覀冞@兒需要在請(qǐng)求代理中間還需要處理一層,所以這兒我們自己實(shí)現(xiàn)mockPlugin
注冊(cè)進(jìn)去。
摸擬mock文件
因?yàn)?code>mock工具包含了一個(gè)請(qǐng)求后臺(tái)的結(jié)果自動(dòng)寫入到Mock目錄下。所以這里將目錄層級(jí)設(shè)置為與請(qǐng)求路徑保持一致:
api接口:/app/home/baseInfo
=> 目錄:mock\app\home\baseInfo.js
對(duì)應(yīng) baseInfo.js
模板:
// mock 開(kāi)關(guān) exports.check = function () { return true; } // mock 數(shù)據(jù) exports.mockData = function () { return { "success": true, "errorMsg": "", "data": { name: 'test' } } }
當(dāng)check
為true
時(shí)對(duì)就請(qǐng)求將會(huì)取mockData
的數(shù)據(jù)。
主邏輯實(shí)現(xiàn)
mock-plugin
主要暴露一個(gè)高階函數(shù),第一層為請(qǐng)求代理配置,返回的函數(shù)的參數(shù)與app.get('/')
的回調(diào)參數(shù)一致,不描述細(xì)節(jié),大概輸出主要的邏輯。
// 獲取mock文件的mock數(shù)據(jù) const setMockData = (moduleName) => { const {mockData} = require(moduleName); return mockData(); }; // 中間件暴露方法 module.exports = function (options) { const {routes, root} = options; return async (req, res, next) => { let {isReq, host} = await valid.isRequestPath(routes, req); // 不是請(qǐng)求地址直接return掉 if (!isReq) { next(); return; } // 如果存在Mock對(duì)應(yīng)的文件 let filePath = await valid.isMockFileName(root, req.path); if (filePath) { // 檢驗(yàn)本地mock文件開(kāi)關(guān)是否開(kāi)啟 let check = await valid.inspectMockCheck(filePath); if (check) { // 發(fā)送本地mock數(shù)據(jù) return res.send(setMockData(filePath)) } else { // 請(qǐng)求結(jié)果 let body = await request(host, req, res).catch(proxyRes => { res.status(proxyRes.statusCode); }); // 發(fā)送請(qǐng)求的結(jié)果信息 return res.send(body); } } else { // 請(qǐng)求返回主體 let body = await request(host, req, res).catch(proxyRes => { res.status(proxyRes.statusCode); next(); }); if (body) { // 定義需要寫入文件路徑 const filePath = path.resolve(root, `mock${req.path}.js`); // 寫入mock文件 writeMockFile(filePath, body); // 響應(yīng)返回主體 return res.send(body); } } }; };
以下是一些校驗(yàn)方法,詳細(xì)代碼就不貼了,具體源碼可查看:https://github.com/moxaIce/lo...
isRequestPath
校驗(yàn)是否為api
接口請(qǐng)求, 返回Promise
包含isReq
布爾值,host
請(qǐng)求域名,route
請(qǐng)求路由的對(duì)象isMockFileName
是否存在對(duì)應(yīng)的mock
文件,返回Promise
返回匹配路徑或者空字符串inspectMockCheck
校驗(yàn)?zāi)M文件請(qǐng)求,開(kāi)關(guān)是否開(kāi)起, 返回布爾值
至于request方法和writeMockFile方法看下面的小結(jié)。
以下是我自己畫的一個(gè)邏輯圖,有點(diǎn)丑見(jiàn)諒:
請(qǐng)求代理
代理的作用不用多說(shuō),都知道是解決了前端起的服務(wù)和直接請(qǐng)求后臺(tái)的跨域問(wèn)題。我這兒主要是在中間件內(nèi)部通過(guò)http.request
方法發(fā)起一個(gè)http請(qǐng)求,對(duì)于http.request
方法的使用可以看這里, 里面也有比較詳細(xì)的示例,我這兒貼上我寫的代碼:
/** * @description 請(qǐng)求方法 */ const url = require('url'); const http = require('http'); module.exports = function (host, req, res) { let body = ''; return new Promise((resolve, reject) => { const parse = url.parse(host); let proxy = http.request( { host: host.hostname, port: parse.port, method: req.method, path: req.path, headers: req.headers }, (proxyRes) => { // 非200字段內(nèi)直接響應(yīng)錯(cuò)誤 , 在主邏輯里處理 if (proxyRes.statusCode < 200 || proxyRes.statusCode > 300) { reject(proxyRes) } proxyRes.on('data', (chunk) => { body += chunk.toString(); }).on('end', () => { try { resolve(JSON.parse(body)); } catch (e) { // 將響應(yīng)結(jié)果返回,在主文件做異?;卣{(diào) reject(proxyRes) } }).on('error', (err) => { console.log(`error is`, err); }) }); proxy.on('error', (e) => { console.error(`請(qǐng)求報(bào)錯(cuò):${e.message}`) }); proxy.end() }) };
代理的實(shí)現(xiàn)比較簡(jiǎn)單,主要通過(guò)外層傳入host
和requset
, response
在內(nèi)部用url
解析得到ip
然后配置request
的options
, 通過(guò)監(jiān)聽(tīng)data
與end
事件將得到的主體報(bào)文resolve
出去,以及中間對(duì)非200
段內(nèi)的響應(yīng)處理。
文件寫入
通過(guò)中間傳options
傳入的root
, 可以得到完整的mock
路徑path.resolve(__dirname,
mock${req.path}.js)
。傳入到寫入mock
文件方法里
module.exports = async function (filePath, body) { await dirExists(path.dirname(filePath)); fs.writeFile(filePath, echoTpl(JSON.stringify(body)), function (err) { if (err) { console.log(`寫入文件失敗`) } }); }
定義mockjs
模板
module.exports = async function (filePath, body) { await dirExists(path.dirname(filePath)); fs.writeFile(filePath, echoTpl(JSON.stringify(body)), function (err) { if (err) { console.log(`寫入文件失敗`) } }); }
dirExists
通過(guò)遞歸的方式寫入文件目錄
module.exports = async function (filePath, body) { await dirExists(path.dirname(filePath)); fs.writeFile(filePath, echoTpl(JSON.stringify(body)), function (err) { if (err) { console.log(`寫入文件失敗`) } }); }
效果演示
使用koa起一個(gè)node服務(wù)并且暴露如下路由和其中的數(shù)據(jù),具體代碼可以看這兒,我這兒只貼上了關(guān)鍵代碼
服務(wù)端代碼:
router.get('/app/home/baseInfo', user_controller.baseInfo) router.post('/app/login', user_controller.login) const login = async (ctx, next) => { ctx.body = { success: true, message: '', code: 0, data: { a: 1, b: '2' } } }; const baseInfo = async (ctx, next) => { ctx.body = { success: true, errorMsg: '', data: { avatar: 'http://aqvatarius.com/themes/taurus/html/img/example/user/dmitry_b.jpg', total: 333, completed: 30, money: '500' } }; };
client代碼
mounted() { axios.get('/app/home/baseInfo', function (res) { console.log(`res 23`, res) }); axios({ url: '/app/login', method: 'post', headers: { // 'Content-Type': 'application/json;charset=UTF-8', 'a': 'b' } }) }
具體效果可以看下圖:
前端在訪問(wèn)的時(shí)候會(huì)將后臺(tái)響應(yīng)的數(shù)據(jù)自動(dòng)寫入到Mock目錄下。
結(jié)語(yǔ)
感覺(jué)在寫文章的過(guò)程中發(fā)現(xiàn)寫入mock
文件的時(shí)候可能使用stream
會(huì)更好一點(diǎn),年底了業(yè)務(wù)需求不太多,避免上班劃水,隨便想了寫一寫~ 覺(jué)得有用的可以點(diǎn)個(gè)收藏+贊~
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
NodeJs Express框架實(shí)現(xiàn)服務(wù)器接口詳解
最近學(xué)習(xí)了基于前后端分離的開(kāi)發(fā)模式,我前端使用Vue框架,后端使用nodejs開(kāi)發(fā)API接口,下面這篇文章主要給大家介紹了關(guān)于nodejs使用Express框架寫后端接口的相關(guān)資料,需要的朋友可以參考下2022-08-08node.js中的定時(shí)器nextTick()和setImmediate()區(qū)別分析
本文介紹了node.js中的定時(shí)器nextTick()和setImmediate()的區(qū)別分析,非常的不錯(cuò),這里推薦給大家。2014-11-11使用nodejs+express實(shí)現(xiàn)簡(jiǎn)單的文件上傳功能
這篇文章主要介紹了使用nodejs+express完成簡(jiǎn)單的文件上傳功能,需要的朋友可以參考下2017-12-12sublime text配置node.js調(diào)試(圖文教程)
下面小編就為大家分享一篇sublime text配置node.js調(diào)試(圖文教程),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-11-11VsCode無(wú)法識(shí)別node問(wèn)題解決過(guò)程
這篇文章主要給大家介紹了關(guān)于VsCode無(wú)法識(shí)別node問(wèn)題解決的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-07-07node基于async/await對(duì)mysql進(jìn)行封裝
這篇文章主要介紹了node基于async/await對(duì)mysql進(jìn)行封裝,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,,需要的朋友可以參考下2019-06-06