node實(shí)現(xiàn)mock-plugin中間件的方法
寫(xiě)在前面
最近在使用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)?a href='http://www.jygsgssxh.com/dedecms_mk/' target='_blank'>插件相當(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之類(lèi)已現(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)寫(xiě)入到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) {
// 定義需要寫(xiě)入文件路徑
const filePath = path.resolve(root, `mock${req.path}.js`);
// 寫(xiě)入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é)。
以下是我自己畫(huà)的一個(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ì)的示例,我這兒貼上我寫(xiě)的代碼:
/**
* @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)處理。
文件寫(xiě)入
通過(guò)中間傳options傳入的root, 可以得到完整的mock路徑path.resolve(__dirname, mock${req.path}.js)。傳入到寫(xiě)入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(`寫(xiě)入文件失敗`)
}
});
}
定義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(`寫(xiě)入文件失敗`)
}
});
}
dirExists通過(guò)遞歸的方式寫(xiě)入文件目錄
module.exports = async function (filePath, body) {
await dirExists(path.dirname(filePath));
fs.writeFile(filePath, echoTpl(JSON.stringify(body)), function (err) {
if (err) {
console.log(`寫(xiě)入文件失敗`)
}
});
}
效果演示
使用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)寫(xiě)入到Mock目錄下。
結(jié)語(yǔ)
感覺(jué)在寫(xiě)文章的過(guò)程中發(fā)現(xiàn)寫(xiě)入mock文件的時(shí)候可能使用stream會(huì)更好一點(diǎn),年底了業(yè)務(wù)需求不太多,避免上班劃水,隨便想了寫(xiě)一寫(xiě)~ 覺(jué)得有用的可以點(diǎn)個(gè)收藏+贊~
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持我們。
上一篇:element-ui中按需引入的實(shí)現(xiàn)
欄 目:JavaScript
下一篇:JavaScript實(shí)現(xiàn)英語(yǔ)單詞題庫(kù)
本文標(biāo)題:node實(shí)現(xiàn)mock-plugin中間件的方法
本文地址:http://www.jygsgssxh.com/a1/JavaScript/9389.html
您可能感興趣的文章
- 04-02java后端代碼分頁(yè) java后端實(shí)現(xiàn)分頁(yè)page
- 01-10Echarts實(shí)現(xiàn)單條折線可拖拽效果
- 01-10在Vue項(xiàng)目中使用Typescript的實(shí)現(xiàn)
- 01-10js實(shí)現(xiàn)上傳圖片并顯示圖片名稱
- 01-10Vue中使用Lodop插件實(shí)現(xiàn)打印功能的簡(jiǎn)單方法
- 01-10echarts實(shí)現(xiàn)折線圖的拖拽效果
- 01-10d3.js實(shí)現(xiàn)圖形縮放平移
- 01-10小程序簡(jiǎn)單兩欄瀑布流效果的實(shí)現(xiàn)
- 01-10H5實(shí)現(xiàn)手機(jī)拍照和選擇上傳功能
- 01-10Echarts實(shí)現(xiàn)多條折線可拖拽效果


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02javascript點(diǎn)線,點(diǎn)線的代碼
- 04-02javascript潛力,javascript強(qiáng)大嗎
- 04-02javascript替換字符串,js字符串的替換
- 04-02javascript移出,js 移入移出
- 04-02包含javascript舍的詞條
- 04-02javascript并行,深入理解并行編程 豆瓣
- 04-02javascript匿名,js匿名方法
- 04-02javascript警報(bào),JavaScript警告
- 04-02javascript遮蓋,JavaScript遮蓋PC端頁(yè)面
- 04-02javascript前身,javascript的前身
隨機(jī)閱讀
- 04-02jquery與jsp,用jquery
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-10delphi制作wav文件的方法
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 01-10C#中split用法實(shí)例總結(jié)
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載


