打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
【实践篇】node实现mock小工具

写在前面

最近在使用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可以看到控制台打印:

可以发现在执行的时候先执行了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中间件.

贴上代码:

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-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方法看下面的小结。

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

请求代理

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

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

具体效果可以看下图:


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

结语

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

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
web前端教程之kbone高级-事件系统
Express框架
webpack之tree shaking
vue项目Cesium配置详解(适用于Cesium1.60以上版本)
vue中使用骨架 vue-skeleton-webpack-plugin
webpack教程八:构建简单web服务器
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服