手动编写mock服务(ma-mock)

上一篇文章json-server的实践与自定义配置化提到过,json-server在我看来不太适用;之前有赞开源的zan-proxy我也尝试用过,其痛点在于mock数据保存在第三方,这个特性使得公司项目不适合使用zan-proxy,所以尝试自己搭建一个mock服务——ma-mock

背景

项目中需要对两个开发地址进行代理,部分数据也需要使用mock数据,所以可以参照zan-proxy做代理和mock的切换按钮,鉴于之前是使用koa编写后端服务的,所以这次使用koa编写可用于mock和proxy的可视化服务

构思

虽说是参照zan-proxy,但是我还是保留着自己的想法;首先,与zan-proxy不同,zan-proxy使用浏览器插件进行地址代理,其主要目的是用于调试线上页面,但我们只在dev环境使用,使用webpack的proxyTable将后端接口都代理到mock服务,由mock服务统一分发代理还是返回mock数据即可;其次,数据保存在本地,构建一个本地文件增删查改的操作,mock服务只在dev开发中使用,io的损耗其实没有太大的区别;

后端

主要有三个功能,分发mock和proxy提供可视化界面的后端接口部署前端资源,因为主要是给前端人员使用,所以维护一份全局变量(lib/Global.js)替代redis

三个功能的执行顺序为 分发mock和proxy -> 返回单页面资源 -> 可视化界面的后端接口

分发功能可以利用koa中间件特性:

'use strict';

const { Logger, fsHandler, Global } = require('../lib/index');
const axios = require('axios');
const pathToRegexp = require('path-to-regexp');
/**
 *
 * @param  {object} options 配置项
 *         {object} options.prefix mock数据的url前缀
 * @return {function}
 *
 */
module.exports = options => {
  // 进行中间件参数的配置,最终返回一个中间件函数
  let prefix = options.prefix;

  // 兼容prefix格式的写法 "/__DEV__/xxx" 或者 "/__DEV__/xxx/"
  if (prefix.lastIndexOf('/') + 1 !== prefix.length) {
    prefix = prefix + '/';
  }

  return async function(ctx, next) {
    let curPath = ctx.path;
    // 不符合prefix的接口地址直接跳过
    if (curPath.indexOf(prefix) !== 0) return await next();

    let pathArr = curPath.split(prefix);

    // 如果prefix之后不再有path则请求不合法
    // 例如:prefix为 __DEV__/pay/但请求路径为http://*/__DEV__/pay/
    if (pathArr.length < 2) {
      ctx.body = '请求路径不合法';
    }

    // 判断是否使用MOCK数据
    const find = Global.mockList.find(it => {
      const re = pathToRegexp(it.url);
      return re.test(`/${pathArr[1]}`);
    });

    // 规则是mock优先级大于proxy
    if (find && find.enable) {
      ctx.body = handlerMock(find.url.slice(1));
    } else if (Global.enableProxy) {
      ctx.body = await handlerProxySync(`/${pathArr[1]}`, ctx);
    } else {
      ctx.body = '未开启proxy';
    }
    // 此处没有next(),直接返回数据
  };

  // 用mock数据
  function handlerMock(filePath) {
    let result = '';

    try {
      result = fsHandler.getMockFile(filePath);
    } catch (e) {
      result = e;
    }
    Logger.debug(result);

    return { ...result, type: 'MOCK' };
  }

  // 后端代理
  async function handlerProxySync(api, ctx) {
    const options = {
      ...ctx.request,
      url: `${Global.currentProxyUrl}/${api}`,
      params: ctx.query,
    };

    try {
      const res = await axios(options);
      return { ...res, type: 'PROXY' };
    } catch (e) {
      return {
        message: e.message,
        type: 'PROXY',
      };
    }
  }
};

中间件使用

// 配置mock
app.use(
  mockProxy({
    prefix: '__DEV__',
  })
);

可视化界面的后端接口

常规后端restful接口,此处略过不讲。

前端静态资源部署

因为是开发环境使用,所以不必部署到nginx上,自己编写了基于koa-statickoa-spa-static

使用方法,配置采用vue打包出来的目录,react可能需要自行按情况修改:

const spaStatic = required('koa-spa-static');
// 挂在静态资源
app.use(
  spaStatic({
    matchReg: /^(?!\/api)/, // 不以"/api"开头的接口地址会返回静态资源
    root: path.join(__dirname, './dist'), // 静态资源目录
    staticReg: /^\/static/, // 前端static资源返回文件,其他返回index.html
  })
);

前端

使用element-ui2的组件构造,属于简单的组装,基于自己编写的vue cli模板,使用命令

vue init masongzhi/vue-template-webpack

其他

ma-mock服务的安装和使用

安装

npm install -D ma-mock

在根目录编写.mamockrc.js配置文件

const path = require('path');
 
// 默认配置
module.exports = {
  prefix: '/__DEV__',
  rootPath: path.resolve(__dirname, './data/mock'),
  proxyPath: path.resolve(__dirname, './data/proxy'),
  proxyFilename: 'config.json',
};

配置webpack proxyTable

// ...省略
module.exports = {
  // ...省略
  dev: {
    // ...省略
    proxyTable: {
      // 填写 .mamockrc.js的prefix,默认为'/__DEV__'
      '/__DEV__': {
        target: 'http://localhost:3001', // 接口的域名
        // secure: false,  // 如果是https接口,需要配置这个参数
        changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
      }
    },
  },
}

package.json添加script命令

mamock [--port 3001]

.mamockrc.js的读取

我们在项目跟目录编写了.mamockrc.js文件,那是怎么在mock服务中读取到这个文件的信息呢,或者说怎样才能将npm包的配置参数写在根目录的文件内。

我们可以使用rc-config-loader,其他类似包也行,像prettier就使用editorconfig和自己编写的editorconfig-to-prettier

const rcfile = require("rc-config-loader");

// 会向上遍历.mamockrc or .mamockrc.json or .mamockrc.js or.<product>rc.yml, .mamockrc.yaml
// 我们需要用到基于跟目录的data文件,所以需要用到__dirname,所以使用js文件
const data = rcfile('mamock');

bin命令的使用

我们在package.json的scripts编写了mamock --port 3001命令,是怎么实现的呢。

在package.json添加

"bin": {
    "ma-mock": "./bin/mock-proxy.js",
    "mamock": "./bin/mock-proxy.js"
}

在bin目录添加mock-proxy.js

#!/usr/bin/env node

'use strict';

// 定义用到的参数
const keys = ['port', 'prefix', 'rootPath', 'proxyPath', 'proxyFilename'];
const argvs = process.argv.slice(2);
function getArgv(key) {
  const index = argvs.findIndex(it => it === `--${key}`);
  return index >= 0 && argvs[index + 1];
}
keys.forEach(key => {
  const value = getArgv(key);
  if (value) process.env[key] = value;
});
// 执行koa的index.js
require('../server/index.js');

自动打开浏览器

启动ma-mock服务时自动打开浏览器

// index.js
const opn = require('opn');

app.listen(PORT);

opn(`http://localhost:${PORT}`, {app: 'google chrome'});

总结

目前还是比较粗糙,后端只做了简单的joi参数校验,没对mock地址进行url校验;mock数据也只支持基本的application/json类型,且不支持mock.js的配置;最重要的,单元测试没补全,后续会慢慢填。

如果有什么建议或者想贡献代码,可以发issue和pr,项目地址: https://github.com/masongzhi/ma-mock

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 前言 最近一直在捣鼓毕设,准备做的是一个基于前后端开发的Mock平台,前期花了很多时间完成了功能模块的交互。现在进...
    临水照影233阅读 9,573评论 0 8
  • 更多干货就在我的个人博客 http://blackblog.tech 欢迎关注! 决策树:构建一个基于属性的树形分...
    BlackBlog__阅读 1,698评论 0 2
  • 1、取消了一些过时的 HTML4的标签 其中包括纯粹显示效果的标记,如 和<centwer>,它们已经被 CSS完...
    Pitfalls阅读 4,237评论 1 0
  • 她总是扎着马尾辫,柔顺的头发在风中像杨柳飘荡,瘦小的身材总喜欢穿着一件黑色外衣。脸色虽然偏黑,却不影响她精致...
    浅绿或玄米阅读 850评论 3 1