最近在使用Mockjs
做爲項目裏面mock
數據的工具,發現mockjs
作的攔截部分是本身實現摸擬了一個XMLHttpRequest
的方法作的攔截,使用Mockjs
攔截請求後,在chrome
的network
上沒法看到請求(具體mockjs使用方法能夠查看他的api,mockjs-api這裏我很少作闡述),但爲了更加真實的像後臺返回數據,我本身使用Node
做爲中間代理去實現了一個mock-plugin
.javascript
由於插件至關因而實現了一個express的中間件的方式,因此這裏簡單對express中間件的使用作一個說明:html
express
中間件經過app.use
(也有app.get,app.post
等方法)的方式註冊到express
的實例某個屬性上,將執行函數存放在棧內部,而後在回調執行的時候調用next()
方法將執行下一個存在棧內的方法。前端
這裏列舉一個示例:java
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(`起動服務成功!`) });
經過node
執行以上代碼後,在瀏覽器上經過訪問http://locahost:4000
能夠看到控制檯打印:
node
能夠發如今執行的時候先執行了use
註冊的中間件,而後再執行到get
路由的時候,又執行了app.use
註冊的中間件。webpack
詳細的express中間件能夠在express官網查看ios
devServer
可使用webpack-dev-server
而後經過before
的回調去作一層攔截,這樣也可以實如今響應以前對後臺的數據作一些處理。git
我這兒選擇本身實現一個devServer
,在以前使用webpack-dev-server
的服務大概須要配置,port
, proxy
,以及跨域https
等。固然本身實現devServer
就不必實現那麼多功能了,正常在開發場景下不少也不必定用得上,這裏我主要使用了webpack-dev-middleware和webpack-hot-middleware達到自動編譯和熱更新的目的,以及能夠本身在中間添加express中間件.github
貼上代碼:web
const 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開發環境配置 const mockPlugin = require('./mock-plugin'); const config = { prd: 8800 }; // 註冊webpack-dev-middleware中間件 app.use( devMiddleware(compiler, { publicPath: webpackConfig.output.publicPath }) ); // 註冊webpack-hot-middleware中間件 app.use( hotMiddleware(compiler) ); // 註冊mockPlugin插件 app.use( mockPlugin({ routes: { '/app': 'http://locahost:3002', // 測試代理到服務器的地址 '/api': 'http://localhost:3003' // 測試代理到服務器的地址 }, root: path.resolve(__dirname) // 項目根目錄 }) ); app.listen(config.prd, function () { console.log('訪問地址:', `http://localhost:${config.prd}`); });
具體的一些演示操做,這裏也很少講了(這不是實現mock-plugin的重點),網上也有不少若是經過webpack-dev-middleware
和webpack-hot-middleware
的教程,惟一的區別是代理部分,網上可能用的是http-proxy
之類已現有的工具,由於咱們這兒須要在請求代理中間還須要處理一層,因此這兒咱們本身實現mockPlugin
註冊進去。
由於mock
工具包含了一個請求後臺的結果自動寫入到Mock目錄下。因此這裏將目錄層級設置爲與請求路徑保持一致:
api接口:/app/home/baseInfo
=> 目錄:mock\app\home\baseInfo.js
對應 baseInfo.js
模板:
// mock 開關 exports.check = function () { return true; } // mock 數據 exports.mockData = function () { return { "success": true, "errorMsg": "", "data": { name: 'test' } } }
當check
爲true
時對就請求將會取mockData
的數據。
mock-plugin
主要暴露一個高階函數,第一層爲請求代理配置,返回的函數的參數與app.get('/')
的回調參數一致,不描述細節,大概輸出主要的邏輯。
// 獲取mock文件的mock數據 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); // 不是請求地址直接return掉 if (!isReq) { next(); return; } // 若是存在Mock對應的文件 let filePath = await valid.isMockFileName(root, req.path); if (filePath) { // 檢驗本地mock文件開關是否開啓 let check = await valid.inspectMockCheck(filePath); if (check) { // 發送本地mock數據 return res.send(setMockData(filePath)) } else { // 請求結果 let body = await request(host, req, res).catch(proxyRes => { res.status(proxyRes.statusCode); }); // 發送請求的結果信息 return res.send(body); } } else { // 請求返回主體 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); // 響應返回主體 return res.send(body); } } }; };
如下是一些校驗方法,詳細代碼就不貼了,具體源碼可查看:https://github.com/moxaIce/lo...
isRequestPath
校驗是否爲api
接口請求, 返回 Promise
包含isReq
布爾值,host
請求域名, route
請求路由的對象。isMockFileName
是否存在對應的mock
文件,返回Promise
返回匹配路徑或者空字符串inspectMockCheck
校驗模擬文件請求,開關是否開起, 返回布爾值至於request方法和writeMockFile方法看下面的小結。
如下是我本身畫的一個邏輯圖,有點醜見諒:
代理的做用不用多說,都知道是解決了前端起的服務和直接請求後臺的跨域問題。我這兒主要是在中間件內部經過http.request
方法發起一個http請求,對於http.request
方法的使用能夠看這裏, 裏面也有比較詳細的示例,我這兒貼上我寫的代碼:
/** * @description 請求方法 */ 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字段內直接響應錯誤 , 在主邏輯裏處理 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) { // 將響應結果返回,在主文件作異常回調 reject(proxyRes) } }).on('error', (err) => { console.log(`error is`, err); }) }); proxy.on('error', (e) => { console.error(`請求報錯:${e.message}`) }); proxy.end() }) };
代理的實現比較簡單,主要經過外層傳入host
和requset
, response
在內部用url
解析獲得ip
而後配置request
的options
, 經過監聽data
與end
事件將獲得的主體報文resolve
出去,以及中間對非200
段內的響應處理。
經過中間傳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
模板
const echoTpl = (data) => { return `exports.check = function () { return false } exports.mockData = function () { return ${data} } ` };
dirExists
經過遞歸的方式寫入文件目錄
// 獲取文件信息,報錯則文件不存在 const getStat = (path) => { return new Promise((resolve) => { fs.stat(path, (err, stats) => { if (err) { resolve(false); } else { resolve(stats); } }) }) }; // 建立目錄 const mkdir = (dir) => { return new Promise((resolve) => { fs.mkdir(dir, err => { if (err) { resolve(false); } else { resolve(true); } }) }) }; // 寫入文件 const dirExists = async (dir) => { let isExists = await getStat(dir); //若是該路徑且不是文件,返回true if (isExists && isExists.isDirectory()) { return true; } else if (isExists) {//若是該路徑存在可是文件,返回false return false; } //若是該路徑不存在 let tempDir = path.parse(dir).dir; //遞歸判斷,若是上級目錄也不存在,則會代碼會在此處繼續循環執行,直到目錄存在 let status = await dirExists(tempDir); let mkdirStatus; if (status) { mkdirStatus = await mkdir(dir); } return mkdirStatus; };
使用koa起一個node服務而且暴露以下路由和其中的數據,具體代碼能夠看這兒,我這兒只貼上了關鍵代碼
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' } }; };
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' } }) }
具體效果能夠看下圖:
前端在訪問的時候會將後臺響應的數據自動寫入到Mock目錄下。
感受在寫文章的過程當中發現寫入mock
文件的時候可能使用stream
會更好一點,年末了業務需求不太多,避免上班划水,隨便想了寫一寫~