从零开始打造 Mock 平台 - 中心篇

媒介

近来一直在捣鼓毕设,预备做的是一个基于前后端开辟的Mock平台,前期花了许多时刻完成了功用模块的交互。如今进度推到怎样设想中心功用,也就是Mock数据的剖析。

依据之前的需求设定加上一些思索,用户能够像写json平常轻松完成数据的mock,也能够经由过程在mock数据模型之上举行构建出庞杂的数据模型并在项目中援用。

这看似简朴的需求实在须要处置惩罚几个差别的模块功用以及交互设想。该怎样处置惩罚剖析差别mock数据并举行构造?前端交互中模仿数据该怎样处置惩罚?数据构造时怎样加载用户设定的数据模型?毛病捕获与处置惩罚?

这些都临时没有一个好的处置惩罚效果。因而想要完成中心功用我们须要明白需求,而且经由过程同类产品是怎样处置惩罚的,经由过程阅读它们的源码来进修头脑并到场。

明白需求

在明白该功用模块之前我们能够经由过程模仿流程来明白。

用户 -> 增加数据模型 – > 及时看到构造构造

用户 -> 增加接口 -> 构造json花样返回参数 -> 预览

构造json花样返回参数 不仅包括返回的正文,同时也设定了 header 和 method。

阅读源码

相符大部分需求的开源项目有

  1. mock.js
  2. easy-mock
  3. eolinker
  4. YAPI
  5. DOCCLEVER

MOCK.JS篇

起首我们须要明白现阶段大部门的 Mock 平台或多或少都是遭到 Mock.js 的头脑或许是其加强版。

我们能够用下面简朴的 json 经由过程 Mock.js来构造数据:


example:

{
    "status|0-1": 0, //接口状况
    "message": "胜利", //音讯提醒
    "data": {
        "counts":"@integer", //统计数目
        "totalSubjectType|1-4": [ //4-10意味着能够随机天生4-10组数据
            { 
              "subjectName|regexp": "大数据|机械进修|东西", //主落款
              "subjectType|+1": 1 //范例
            }
        ],
        "data":[
            {
                "name": "@name", //用户名
                "cname":"@cname",
                "email": "@email", //email
                "time": "@datetime" //时刻
            }
        ]}
}

返回效果


{
    "status": 0,
    "message": "胜利",
    "data": {
        "counts": 2216619884890228,
        "totalSubjectType": [
            {
                "subjectNameregexp": "大数据|机械进修|东西",
                "subjectType": 1
            },
            {
                "subjectNameregexp": "大数据|机械进修|东西",
                "subjectType": 2
            },
            {
                "subjectNameregexp": "大数据|机械进修|东西",
                "subjectType": 3
            },
            {
                "subjectNameregexp": "大数据|机械进修|东西",
                "subjectType": 4
            }
        ],
        "data": [
            {
                "name": "Ruth Thompson",
                "cname": "鲁克",
                "email": "z.white@young.gov",
                "time": "1985-02-06 05:45:21"
            }
        ]
    }
}

而且能够经由过程其 Mock.Random.extend() 来扩大自定义占位符.


example:

Random.extend({
    weekday: function(date) {
        var weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
        return this.pick(weekdays);
    },
    sex: function(date) {
        var sexes = ['男', '女', '中性', '未知'];
        return this.pick(sexes);
    }
});

console.log(Random.weekday());  // 效果: Saturday
console.log(Mock.mock('@weekday'));  // 效果: Tuesday
console.log(Random.sex());  // 效果: 男
console.log(Mock.mock('@sex'));  // 效果: 未知

来延长所需进的拓展。

这个能够将自定义数据模型先举行剖析,然后经由过程extend将其到场。

easy-mock

easy-mock 是我参考的重要项目之一,它的UI交互异常相符我的设定,而且作为开源项目能够从它的源码中学到许多。

直接来看它供应接口编辑的页面

《从零开始打造 Mock 平台 - 中心篇》


{
  data: {
    img: function({
      _req,
      Mock
    }) {
      return _req.body.fileName + '_' + Mock.mock('@image')
    }
  }
}

能够从上得之它既能够处置惩罚Mock数据模仿也能够处置惩罚函数,而且它内部有一套能处置惩罚req的内容。

先是在源码中找了一下,找到几个疑似点,然则不肯定,照样在当地装好环境,重如果须要根据redis.然后启动效劳去打几个断点输出。

依据履历先肯定 controllers\mock.js 应当是处置惩罚数据模仿的处所。经由过程阅读源码并剖析,终究定位于 297行处的代码


    await redis.lpush('mock.count', api._id)
    if (jsonpCallback) {
      ctx.type = 'text/javascript'
      ctx.body = `${jsonpCallback}(${JSON.stringify(apiData, null, 2)})`
        .replace(/\u2028/g, '\\u2028')
        .replace(/\u2029/g, '\\u2029') // JSON parse vs eval fix. https://github.com/rack/rack-contrib/pull/37
    } else {
      ctx.body = apiData
    }

起首是看到终究返回的 apiData 。用过 koa 或许 express 都应当清晰 ctx.body 的寄义。然后我在上面写了句 console.log(apiData)

然后在阅读器端发送要求。看下 node 端输出和阅读器端拿到的数据,基本能够肯定终究输出就是这个。

《从零开始打造 Mock 平台 - 中心篇》

然后我们往上翻,能够看到这么一段代码:


 const vm = new VM({
        timeout: 1000,
        sandbox: {
          Mock: Mock,
          mode: api.mode,
          template: new Function(`return ${api.mode}`) // eslint-disable-line
        }
      })
      console.log('数据考证')
      console.log(mode)
      vm.run('Mock.mock(new Function("return " + mode)())') // 数据考证,检测 setTimeout 等要领
      apiData = vm.run('Mock.mock(template())') // 处理正则表达式失效的题目

经由过程查询相识到 VM 是一个沙盒,能够运转不受信托的代码。

也许就可以相识 easy-mock 经由过程 vm 沙盒形式运转 mode 代码剖析后返回效果。

中心代码就是 Mock.mock( template ) 这么一句。依据数据模板天生模仿数据。

经由过程查文档相识 template 是能够直接内部写函数然后实行的。

如许剖析的难度大大下落,发明本来并没有迥殊庞杂的,依旧是依靠了 Mock.js 的原生要领。

然后我们能够看到 easy-mock 另一的操纵就是能够猎取 要求参数_req。也就是能够经由过程以下代码来依据要求参数返回指定数据。


{
  success: true,
  data: {
    default: "hah",
    _req: function({
      _req
    }) {
      return _req
    },
    name: function({
      _req
    }) {
      return _req.query.name || this.default
    }
  }
}

_req 一看就是从要求参数中取得的对象。

Mock.js是没有这个对象的,我们来找找源码中是那里注入了这个对象。

照样在 mock.js 这个文件中第234行处找到


   Mock.Handler.function = function (options) {
      const mockUrl = api.url.replace(/{/g, ':').replace(/}/g, '') // /api/{user}/{id} => /api/:user/:id
      options.Mock = Mock
      options._req = ctx.request
      options._req.params = util.params(mockUrl, mockURL)
      options._req.cookies = ctx.cookies.get.bind(ctx)
      return options.template.call(options.context.currentContext, options)
    }

经由过程阅读 MockJS 的源码,相识到 Handler是处置惩罚数据模板的处所,打个断点再输出一次能够发明实际上是在 Mock.mock(new Function("return " + mode)())' 以后传入的参数。

options._req = ctx.request 这句代码通知了我们所谓的 _req是从那里来的。

因而这个手艺点我们也相识了是怎样做的,那末剩下一个天真的支撑 restful 经由过程阅读源码发明实在也没怎样处置惩罚,只是用 pathToRegexp 举行了一次考证。它先是在 middlewares/index.js 中 的 mockFilter 举行了途径正则。


static mockFilter (ctx, next) {
    console.log(ctx.path)
    const pathNode = pathToRegexp('/mock/:projectId(.{24})/:mockURL*').exec(ctx.path)
    console.log(pathNode)
    if (!pathNode) ctx.throw(404)
    if (blackProjects.indexOf(pathNode[1]) !== -1) {
      ctx.body = ctx.util.refail('接口要求频次太快,已被限定接见')
      return
    }
    console.log('经由过程挑选')

    ctx.pathNode = {
      projectId: pathNode[1],
      mockURL: '/' + (pathNode[2] || '')
    }

    return next()
  }

然后经由过程存在 redis 里的接口内容再举行了考证婚配。


const { query, body } = ctx.request
    const method = ctx.method.toLowerCase()
    const jsonpCallback = query.jsonp_param_name && (query[query.jsonp_param_name] || 'callback')
    let { projectId, mockURL } = ctx.pathNode
    console.log('ctx.pathNode', ctx.pathNode)
    const redisKey = 'project:' + projectId
    let apiData, apis, api
    console.log('经由过程URL婚配磨练')
    apis = await redis.get(redisKey)
    console.log(apis)
    if (apis) {
      apis = JSON.parse(apis)
      console.log('pure apis', apis)
    } else {
      apis = await MockProxy.find({ project: projectId })
      console.log('find projectId', apis)
      if (apis[0]) await redis.set(redisKey, JSON.stringify(apis), 'EX', 60 * 30)
    }

    if (apis[0] && apis[0].project.url !== '/') {
      mockURL = mockURL.replace(apis[0].project.url, '') || '/'
    }

    api = apis.filter((item) => {
      const url = item.url.replace(/{/g, ':').replace(/}/g, '') // /api/{user}/{id} => /api/:user/:id
      return item.method === method && pathToRegexp(url).test(mockURL)
    })[0]
    console.log('api',api)

    if (!api) ctx.throw(404)

基本不婚配的途径要求都是在 item.method === method && pathToRegexp(url).test(mockURL) 这句代码里被阻拦的。

异常优异的代码。通读下来,加上断点对其思绪逻辑学到了许多。

eolinker

它的后端代码是 PHP 的,这就略过不看了。

YAPI

它的中心后端处置惩罚代码是在 mockServer.js

有了之前的阅读履历很快找到处置惩罚 Mock 数据的处所


  let res;

        res = interfaceData.res_body;
        try {
            if (interfaceData.res_body_type === 'json') {
                res = mockExtra(
                    yapi.commons.json_parse(interfaceData.res_body),
                    {
                        query: ctx.request.query,
                        body: ctx.request.body,
                        params: Object.assign({}, ctx.request.query, ctx.request.body)                     
                    }
                );
                try {
                    res = Mock.mock(res);
                } catch (e) {
                    yapi.commons.log(e, 'error')
                }
            }
            

异常简朴粗犷的处置惩罚要领。。。

对加强功用比较猎奇在, 因而在 common\mock-extra.js 里找到了 mock(mockJSON, context) 要领。依据参数实在就可以相识绑定上下文然后做了一些行动。这里就不睁开细致。等以后开辟的时刻用到再去细读。由于这是做了其本身的加强的Mock功用,而临时不须要这方面的斟酌。

DOClecer

这个项目是国内一个创业团队做的,我也到场了其官方群。虽然还没有用过。不过不阻碍阅读其源码相识思绪。不过讲道理这个代码构造作风是挺蹩脚的。。。

而且源码中不止一次涌现了eval… 因而摒弃参考。

写个小模块高兴一下

经由过程阅读以上项目的源码,实在重如果前三个,觉得能够完成本身想要的需求了。那末先写一个小的来作为基本模块。


export const mock = async(ctx: any) => {
  console.log('mock')
  console.log(ctx)
  console.log(ctx.params)
  const method = ctx.request.method.toLowerCase()
  // let { projectId, mockURL } = ctx.pathNode
  // 猎取接口途径内容
  console.log('ctx.pathNode', ctx.pathNode)
  // 婚配内容是不是一致
  console.log('考证内容中...')
  // 模仿数据
  Mock.Handler.function = function (options: any) {
    console.log('start Handle')
    options.Mock = Mock
    // 传入 request cookies,方便运用
    options._req = ctx.request
    return options.template.call(options.context.currentContext, options)
  }
  console.log('Mock.Handler', Mock.Handler.function)
//   const testMode = `{
//     'title': 'Syntax Demo',
//     'string1|1-10': '★',
//     'string2|3': 'value',
//     'number1|+1': 100,
//     'number2|1-100': 100,
//     'number3|1-100.1-10': 1,
//     'number4|123.1-10': 1,
//     'number5|123.3': 1,
//     'number6|123.10': 1.123,
//     'boolean1|1': true,
//     'boolean2|1-2': true,
//     'object1|2-4': {
//         '110000': '北京市',
//         '120000': '天津市',
//         '130000': '河北省',
//         '140000': '山西省'
//     },
//     'object2|2': {
//         '310000': '上海市',
//         '320000': '江苏省',
//         '330000': '浙江省',
//         '340000': '安徽省'
//     },
//     'array1|1': ['AMD', 'CMD', 'KMD', 'UMD'],
//     'array2|1-10': ['Mock.js'],
//     'array3|3': ['Mock.js'],
//     'function': function() {
//         return this.title
//     }
// }`
const testMode = `{success :true, data: { default: "hah", _req: function({ _req }) { return _req }, name: function({ _req }) { return _req.query.name || this.default }}}`
  const vm = new VM({
    timeout: 1000,
    sandbox: {
      Mock: Mock,
      mode: testMode,
      template: new Function(`return ${testMode}`)
    }
  })
  vm.run('Mock.mock(new Function("return " + mode)())') // 数据考证,检测 setTimeout 等要领, 趁便将内部的函数实行了
  // console.log(Mock.Handler.function(new Function('return ' + testMode)()))
  const apiData = vm.run('Mock.mock(template())')
  console.log('apiData2333' , apiData)
  let result
  switch (method) {
    case 'get':
      result = success({'msg': '你调用了get要领'})
      break;
    case 'post':
      result = success({'msg': '你调用了post要领'})
      break;
    case 'put' :
      result = success({'msg': '你调用了put要领'})
      break;
    case 'patch' :
      result = success({'msg': '你调用了patch要领'})
      break;
    case 'delete' :
      result = success({'msg': '你调用了delete要领'})
      break;
    default:
      result = error()
  }
  // console.log(result)
  return ctx.body = result
}

这里调试的碰到一些题目,重如果一开始测试的时刻发明 Mock 只将划定规矩的数据模仿出,发明 function 范例的函数都没实行,一开始定位以为是Mock.Handler.function 在 ts 中未实行。因而在里面写了一个输出,发明确实没有。经由种种猜测和测试,发明是模仿mode有题目。

一开始我是这么写的


const testcode = {
    'array1|1': ['AMD', 'CMD', 'KMD', 'UMD'],
    'array2|1-10': ['Mock.js'],
    'array3|3': ['Mock.js'],
    'function': function() {
        return this.title
    }
}

事实上应当这么写


const testcode = `{
    'array1|1': ['AMD', 'CMD', 'KMD', 'UMD'],
    'array2|1-10': ['Mock.js'],
    'array3|3': ['Mock.js'],
    'function': function() {
        return this.title
    }
}`

参照 easy-mock 的思绪能够完成一个基本的 Mock数据剖析器,而且能够依据 koa 的特征同时支撑 _req 的一些参数,这里先不加进去。

怎样支撑自定义的数据模型也有了基本的思绪,在之前没有斟酌 redis 情况下照样用传统的数据库查询。详细完成等后期再捣鼓出来再写出来。

末端

经由过程这两天的进修,总算把一个Mock的中心模块该怎样完成的思绪给理顺了。

实在不管你是用户自定义数据,比方


{
  'user': User, // User是用户自定义的数据范例
   'string2|3': 'value',
   'number1|+1': 100,
    _req: function({
      _req
    }) {
      return _req
    },
    name: function({
      _req
    }) {
      return _req.query.name || this.default
    }
}

照样 Mock.js 原生的语法,你终究转换过来须要实行的是一样的内容,无非是在其转换前须要做肯定的处置惩罚。只要搞懂了基本的数据模仿完成,基本上你能够将各个参数都做定制化。比方有的平台会将用户本身编写的函数一同和 json 拼接。实在用的终究中心思绪照样一样的。

参考资料

Mock.js运用

mockjs官方文档

    原文作者:linshuizhaoying
    原文地址: https://segmentfault.com/a/1190000013108221
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞