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 相关文章推荐
javascript div 弹出可拖动窗口
Feb 26 Javascript
『jQuery』取指定url格式及分割函数应用
Apr 22 Javascript
PHP 数组current和next用法分享
Mar 05 Javascript
jQuery1.9.1源码分析系列(十六)ajax之ajax框架
Dec 04 Javascript
JavaScript中两个字符串的匹配
Jun 08 Javascript
利用Angularjs和原生JS分别实现动态效果的输入框
Sep 01 Javascript
关于JavaScript和jQuery的类型判断详解
Oct 08 Javascript
JavaScript的数据类型转换原则(干货)
Mar 15 Javascript
Koa2 之文件上传下载的示例代码
Mar 29 Javascript
Vue实现内部组件轮播切换效果的示例代码
Apr 07 Javascript
使用angularjs.foreach时return的问题解决
Sep 30 Javascript
layui表格 返回的数据状态异常的解决方法
Sep 10 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
ThinkPHP实现将本地文件打包成zip下载
2014/06/26 PHP
php将从数据库中获得的数据转换成json格式并输出的方法
2018/08/21 PHP
javascript 全选与全取消功能的实现代码
2012/12/23 Javascript
Vue.js基础知识小结
2017/01/13 Javascript
Angular.JS实现无限级的联动菜单(使用demo)
2017/02/08 Javascript
js记录点击某个按钮的次数-刷新次数为初始状态的实例
2017/02/15 Javascript
Angular2管道Pipe及自定义管道格式数据用法实例分析
2017/11/29 Javascript
在Vue组件中使用 TypeScript的方法
2018/02/28 Javascript
微信小程序实现简单评论功能
2018/11/28 Javascript
小程序根据手机机型设置自定义底部导航距离
2019/06/04 Javascript
vue组件中实现嵌套子组件案例
2020/08/31 Javascript
[00:15]TI9地铁玩家打卡
2019/08/11 DOTA
python实现xlsx文件分析详解
2018/01/02 Python
使用Python通过win32 COM实现Word文档的写入与保存方法
2018/05/08 Python
Python实现随机创建电话号码的方法示例
2018/12/07 Python
Django给admin添加Action的步骤详解
2019/05/01 Python
Python学习笔记之函数的定义和作用域实例详解
2019/08/13 Python
OpenCV 表盘指针自动读数的示例代码
2020/04/10 Python
python简单实现最大似然估计&amp;scipy库的使用详解
2020/04/15 Python
python操作redis数据库的三种方法
2020/09/10 Python
解决使用Pandas 读取超过65536行的Excel文件问题
2020/11/10 Python
利用html5 file api读取本地文件示例(如图片、PDF等)
2018/03/07 HTML / CSS
HTML5全屏(Fullscreen)API详细介绍
2015/04/24 HTML / CSS
高级运动鞋:GREATS
2019/07/19 全球购物
公司合作协议范文
2014/10/01 职场文书
离婚财产分配协议书
2014/10/21 职场文书
党的群众路线教育实践活动个人对照检查材料(企业)
2014/11/05 职场文书
2014年教育教学工作总结
2014/11/13 职场文书
大学生助学金感谢信
2015/01/21 职场文书
史上最牛辞职信
2015/05/13 职场文书
卫生保健工作总结2015
2015/05/18 职场文书
Python列表删除重复元素与图像相似度判断及删除实例代码
2021/05/07 Python
python缺失值的解决方法总结
2021/06/09 Python
redis sentinel监控高可用集群实现的配置步骤
2022/04/01 Redis
Go语言的协程上下文的几个方法和用法
2022/04/11 Golang
提高系统的吞吐量解决数据库重复写入问题
2022/04/23 MySQL