node实现mock-plugin中间件的方法


Posted in Javascript onDecember 25, 2019

写在前面

最近在使用Mockjs作为项目里面mock数据的工具,发现mockjs做的拦截部分是自己实现摸拟了一个XMLHttpRequest的方法做的拦截,使用Mockjs拦截请求后,在chromenetwork上无法看到请求(具体mockjs使用方法可以查看他的api,mockjs-api这里我不多做阐述),但为了更加真实的像后台返回数据,我自己使用Node作为中间代理去实现了一个mock-plugin.

express中间件介绍

因为插件相当于是实现了一个express的中间件的方式,所以这里简单对express中间件的使用做一个说明:

express中间件通过app.use(也有app.get,app.post等方法)的方式注册到express的实例某个属性上,将执行函数存放在栈内部,然后在回调执行的时候调用next()方法将执行下一个存在栈内的方法。

这里列举一个示例:

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实现mock-plugin中间件的方法

可以发现在执行的时候先执行了use注册的中间件,然后再执行到get路由的时候,又执行了app.use注册的中间件。

详细的express中间件可以在express官网查看

实现dev-server

devServer可以使用webpack-dev-server然后通过before的回调去做一层拦截,这样也能够实现在响应之前对后台的数据做一些处理。

我这儿选择自己实现一个devServer,在之前使用webpack-dev-server的服务大概需要配置,port, proxy,以及跨域https等。当然自己实现devServer就没必要实现那么多功能了,正常在开发场景下很多也不一定用得上,这里我主要使用了webpack-dev-middleware和webpack-hot-middleware达到自动编译和热更新的目的,以及可以自己在中间添加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开发环境配置
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-middlewarewebpack-hot-middleware的教程,唯一的区别是代理部分,网上可能用的是http-proxy之类已现有的工具,因为我们这儿需要在请求代理中间还需要处理一层,所以这儿我们自己实现mockPlugin注册进去。

摸拟mock文件

因为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'
  }
 }
}

checktrue时对就请求将会取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方法看下面的小结。

以下是我自己画的一个逻辑图,有点丑见谅:

node实现mock-plugin中间件的方法

请求代理

代理的作用不用多说,都知道是解决了前端起的服务和直接请求后台的跨域问题。我这儿主要是在中间件内部通过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()
 })
};

代理的实现比较简单,主要通过外层传入hostrequset, response在内部用url解析得到ip然后配置requestoptions, 通过监听dataend事件将得到的主体报文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模板

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通过递归的方式写入文件目录

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起一个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'
  }
 };
};

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

具体效果可以看下图:

node实现mock-plugin中间件的方法

前端在访问的时候会将后台响应的数据自动写入到Mock目录下。

结语

感觉在写文章的过程中发现写入mock文件的时候可能使用stream会更好一点,年底了业务需求不太多,避免上班划水,随便想了写一写~ 觉得有用的可以点个收藏+赞~

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JS启动应用程序的一个简单例子
May 11 Javascript
ASP中Sub和Function的区别说明
Aug 30 Javascript
jQuery学习笔记 操作jQuery对象 属性处理
Sep 19 Javascript
控制文字内容的显示与隐藏示例
Jun 11 Javascript
JavaScript内存管理介绍
Mar 13 Javascript
jquery 中ajax执行的优先级
Jun 22 Javascript
jquery mobile实现可折叠的导航按钮
Mar 11 Javascript
ui-router中使用ocLazyLoad和resolve的具体方法
Oct 18 Javascript
vue 运用mock数据的示例代码
Nov 07 Javascript
微信小程序实现运动步数排行功能(可删除)
Jul 05 Javascript
详解webpack的clean-webpack-plugin插件报错
Oct 16 Javascript
微信小程序停止其他视频播放当前视频的实例代码
Dec 25 #Javascript
vue分页插件的使用方法
Dec 25 #Javascript
继承行为在 ES5 与 ES6 中的区别详解
Dec 24 #Javascript
在JavaScript中实现链式调用的实现
Dec 24 #Javascript
vue实现分页加载效果
Dec 24 #Javascript
微信小程序如何获取地址
Dec 24 #Javascript
浅析vue-router中params和query的区别
Dec 24 #Javascript
You might like
在字符串指定位置插入一段字符串的php代码
2010/02/16 PHP
php防止sql注入代码实例
2013/12/18 PHP
PHP实现登录搜狐广告获取广告联盟数据的方法【附demo源码】
2016/10/14 PHP
php的lavarel框架中join和orWhere的用法
2020/12/28 PHP
Javascript的常规数组和关联数组对比小结
2012/05/24 Javascript
js获取height和width的方法说明
2013/01/06 Javascript
jQuery结合CSS制作漂亮的select下拉菜单
2015/05/03 Javascript
将JavaScript的jQuery库中表单转化为JSON对象的方法
2015/11/17 Javascript
Node.js操作Firebird数据库教程
2016/03/04 Javascript
jQuery实现遮罩层登录对话框
2016/12/29 Javascript
angular.js + require.js构建模块化单页面应用的方法步骤
2017/07/19 Javascript
轻松学习JavaScript函数中的 Rest 参数
2019/05/30 Javascript
浅谈Vue 函数式组件的使用技巧
2020/06/16 Javascript
浅谈JavaScript中this的指向问题
2020/07/28 Javascript
Python笔记(叁)继续学习
2012/10/24 Python
在Python的setuptools框架下生成egg的教程
2015/04/13 Python
Python实现的彩票机选器实例
2015/06/17 Python
Django重装mysql后启动报错:No module named ‘MySQLdb’的解决方法
2018/04/22 Python
使用pandas的DataFrame的plot方法绘制图像的实例
2018/05/24 Python
python 实现倒排索引的方法
2018/12/25 Python
使用Python进行体育竞技分析(预测球队成绩)
2019/05/16 Python
numpy库与pandas库axis=0,axis= 1轴的用法详解
2019/05/27 Python
Python程序暂停的正常处理方法
2019/11/07 Python
python通过链接抓取网站详解
2019/11/20 Python
Django {{ MEDIA_URL }}无法显示图片的解决方式
2020/04/07 Python
python pymysql链接数据库查询结果转为Dataframe实例
2020/06/05 Python
python中threading和queue库实现多线程编程
2021/02/06 Python
护理专科毕业生自荐书范文
2014/02/19 职场文书
公司授权委托书
2014/10/17 职场文书
2014年护士长工作总结
2014/11/11 职场文书
行政申诉状范文
2015/05/20 职场文书
医院病假条怎么写
2015/08/17 职场文书
外出考察学习心得体会
2016/01/18 职场文书
在python中实现导入一个需要传参的模块
2021/05/12 Python
Python OpenCV 图像平移的实现示例
2021/06/04 Python
Python 统计序列中元素的出现频度
2022/04/26 Python