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 相关文章推荐
IE与FireFox的兼容性问题分析
Apr 22 Javascript
一款Jquery 分页插件的改造方法(服务器端分页)
Jul 11 Javascript
jquery datatable后台封装数据示例代码
Aug 07 Javascript
Node.js中使用Log.io在浏览器中实时监控日志(等同tail -f命令)
Sep 17 Javascript
浅谈Node.js中的定时器
Jun 18 Javascript
js获取鼠标点击的对象,点击另一个按钮删除该对象的实现代码
May 13 Javascript
通过bootstrap全面学习less
Nov 09 Javascript
jquery.multiselect多选下拉框实现代码
Nov 11 Javascript
canvas雪花效果核心代码分享
Feb 19 Javascript
ES6中Class类的静态方法实例小结
Oct 28 Javascript
对vue里函数的调用顺序介绍
Mar 17 Javascript
js实现扫雷源代码
Nov 27 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
日本十大科幻动漫 宇宙骑士垫底,第一已成经典
2020/03/04 日漫
drupal 代码实现URL重写
2011/05/04 PHP
基于PHP 面向对象之成员方法详解
2013/05/04 PHP
PHP使用Pear发送邮件(Windows环境)
2016/01/05 PHP
php session的锁和并发
2016/01/22 PHP
模拟用户操作Input元素,不会触发相应事件
2007/05/11 Javascript
几个比较实用的JavaScript 测试及效验工具
2010/04/18 Javascript
javascript针对DOM的应用分析(二)
2012/04/15 Javascript
用js写了一个类似php的print_r输出换行功能
2013/02/18 Javascript
jquery中one()方法的用法实例
2015/01/16 Javascript
JavaScript实现MIPS乘法模拟的方法
2015/04/17 Javascript
js获取浏览器高度 窗口高度 元素尺寸 偏移属性的方法
2016/11/21 Javascript
详解Angular-Cli中引用第三方库
2017/05/21 Javascript
js实现鼠标拖拽多选功能示例
2017/08/01 Javascript
详解EasyUi控件中的Datagrid
2017/08/23 Javascript
jQuery+koa2实现简单的Ajax请求的示例
2018/03/06 jQuery
vue中axios防止多次触发终止多次请求的示例代码(防抖)
2020/02/16 Javascript
Python enumerate遍历数组示例应用
2008/09/06 Python
基于Python数据可视化利器Matplotlib,绘图入门篇,Pyplot详解
2017/10/13 Python
Python 删除连续出现的指定字符的实例
2018/06/29 Python
Linux下python3.6.1环境配置教程
2018/09/26 Python
美国最顶级的精品店之一:Hampden Clothing
2016/12/22 全球购物
美国首屈一指的礼品篮供应商:GiftTree
2018/01/06 全球购物
在使用非全零作为空指针内部表达的机器上, NULL是如何定义
2014/11/09 面试题
大学生个人简历自我评价
2013/11/16 职场文书
四年级学生评语大全
2014/04/21 职场文书
广告宣传策划方案
2014/05/21 职场文书
小学关爱留守儿童活动方案
2014/08/25 职场文书
乡镇防汛工作汇报
2014/10/28 职场文书
2014年餐厅服务员工作总结
2014/11/18 职场文书
汤姆叔叔的小屋读书笔记
2015/06/30 职场文书
运动会口号霸气押韵
2015/12/24 职场文书
小学家庭教育心得体会
2016/01/14 职场文书
2016北大自主招生自荐信模板
2016/01/28 职场文书
护士年终工作总结不会写?各科护士模板总结
2020/01/02 职场文书
Redis延迟队列和分布式延迟队列的简答实现
2021/05/13 Redis