【实践篇】node实现mock小工具

写在前面

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

express中间件介绍

由于插件至关因而实现了一个express的中间件的方式,因此这里简单对express中间件的使用作一个说明:html

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

这里列举一个示例:java

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能够看到控制台打印:
1.png-12.3kBnode

能够发如今执行的时候先执行了use注册的中间件,而后再执行到get路由的时候,又执行了app.use注册的中间件。webpack

详细的express中间件能够在express官网查看ios

实现dev-server

devServer可使用webpack-dev-server而后经过before的回调去作一层拦截,这样也可以实如今响应以前对后台的数据作一些处理。git

我这儿选择本身实现一个devServer,在以前使用webpack-dev-server的服务大概须要配置,port, proxy,以及跨域https等。固然本身实现devServer就不必实现那么多功能了,正常在开发场景下不少也不必定用得上,这里我主要使用了webpack-dev-middlewarewebpack-hot-middleware达到自动编译和热更新的目的,以及能够本身在中间添加express中间件.github

贴上代码:web

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

如下是我本身画的一个逻辑图,有点丑见谅:
2.png-23.6kB

请求代理

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

具体效果能够看下图:
34.gif-270kB
前端在访问的时候会将后台响应的数据自动写入到Mock目录下。

结语

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

相关文章
相关标签/搜索