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 相关文章推荐
jQuery制作可自定义大小的拼图游戏
Mar 30 Javascript
c#程序员对TypeScript的认识过程
Jun 19 Javascript
ReactNative-JS 调用原生方法实例代码
Oct 08 Javascript
微信小程序之仿微信漂流瓶实例
Dec 09 Javascript
jQuery中on方法使用注意事项详解
Feb 15 Javascript
js中对象与对象创建方法的各种方法
Feb 27 Javascript
vue中 数字相加为字串转化为数值的例子
Nov 07 Javascript
vue中实现回车键登录功能
Feb 19 Javascript
原生javascript制作的拼图游戏实现方法详解
Feb 23 Javascript
详解JavaScript的this指向和绑定
Sep 08 Javascript
详解JavaScript执行模型
Nov 16 Javascript
Vue仿百度搜索功能
Dec 28 Vue.js
微信小程序停止其他视频播放当前视频的实例代码
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/10 日漫
PHP生成静态HTML页面最简单方法示例
2015/04/09 PHP
UTF-8正则表达式如何匹配汉字
2015/08/03 PHP
Laravel5.4框架中视图共享数据的方法详解
2019/09/05 PHP
DOM下的节点属性和操作小结
2009/05/14 Javascript
JQuery中getJSON的使用方法
2010/12/13 Javascript
kmock javascript 单元测试代码
2011/02/06 Javascript
javascript动画浅析
2012/08/30 Javascript
JavaScript数据类型判定的总结笔记
2015/07/31 Javascript
js简单实现Select互换数据的方法
2015/08/17 Javascript
jQuery插件dataTables添加序号列的方法
2016/07/06 Javascript
jquery之别踩白块游戏的简单实现
2016/07/25 Javascript
Bootstrap中datetimepicker使用小结
2016/12/28 Javascript
微信小程序封装http访问网络库实例代码
2017/05/24 Javascript
详解关于react-redux中的connect用法介绍及原理解析
2017/09/11 Javascript
JS实现读取xml内容并输出到div中的方法示例
2018/04/19 Javascript
详解微信小程序scroll-view横向滚动的实践踩坑及隐藏其滚动条的实现
2019/03/14 Javascript
Vue.set 全局操作简单示例
2019/09/19 Javascript
vue 数据遍历筛选 过滤 排序的应用操作
2020/11/17 Javascript
Python实现批量把SVG格式转成png、pdf格式的代码分享
2014/08/21 Python
Pycharm之快速定位到某行快捷键的方法
2019/01/20 Python
python yield关键词案例测试
2019/10/15 Python
Python利用全连接神经网络求解MNIST问题详解
2020/01/14 Python
pycharm 2020.2.4 pip install Flask 报错 Error:Non-zero exit code的问题
2020/12/04 Python
越南综合购物网站:Lazada越南
2019/06/10 全球购物
德国Discount-Apotheke中文官网:DC德式康线上药房
2020/02/18 全球购物
年终考核评语
2014/01/19 职场文书
高中学生干部学习的自我评价
2014/02/21 职场文书
活动总结新闻稿
2014/08/30 职场文书
党支部党的群众路线对照检查材料
2014/09/24 职场文书
党校学习个人总结
2015/02/15 职场文书
《狼牙山五壮士》读后感:宁死不屈,视死如归
2019/08/16 职场文书
MySQL COUNT函数的使用与优化
2021/05/10 MySQL
PyTorch 如何自动计算梯度
2021/05/23 Python
python 算法题——快乐数的多种解法
2021/05/27 Python
详解Redis复制原理
2021/06/04 Redis