由于在正常的业务中,与后台的连接是基于pb协议的,接口中的数据字段和对应的数据类型已经在pb协议中进行了定义。基于此,提出了一种mockingfakedata的方案,可以大大提高开发效率。Protocol-buffer是一种数据传输格式。数据结构是通过proto文件定义的。编解码过程是基于二进制的,速度比http协议快。内网服务器之间的通信数据通信和客户端与服务器之间的通信会更有优势,但是代价是双方需要维护同一个proto文件(以下简称pb文件)。如果不知道pb协议,可以查看https://developers.google.cn/protocol-buffers/由于团队业务开发过程中使用pb协议,下面将着重介绍pb协议的选择模拟解决方案和具体实施过程。方案选择常见的mock数据方案有很多,如何mock?在系统的哪一层模拟?是我们需要思考的问题之一。以下是几种常见的mocking解决方案:1、前端直接hack请求接口,重写fetch和ajax接口,不发送真实请求,直接在前端页面生成假数据;2、在前端页面配置代理,请求进入抓包工具,类似fiddler、whistle等,然后替换抓包内容。(严格来说不是mock方案,因为数据不能随机自动生成);3.服务器生成模拟数据。由于不想对真实后台造成额外的影响,我们可以在业务节点的中间层生成mock数据。4.单独搭建mock平台。所有项目的所有接口都可以先在mock系统上进行配置。接下来要做的就是将请求转发给mock平台,mock平台会响应数据。最后生成的数据只需要借助mockjs生成即可。想详细了解mockjs可以点击http://mockjs.com/进行选择:因为该协议不是普通的http协议,它是基于pb协议的,我们需要解析pb协议获取其中的字段请求,所以我们可以在节点的中间层进行,而前两种方案在前端结束请求,无法解析pb协议。最后一个方案的成本太高,所以我们选择了前三个方案。解决方案实践1、解析pb文件,获取AST由于节点中间层需要做转发工作,将前端HTTP请求封装成相应的pb协议包发送给业务后台,所以我们可以做一层在节点中间层模拟这里。如果我们要走mock路径,只需要解析pb文件,获取文件对应的AST,然后生成接口对应的数据(命令字)即可。已经有一些现成的工具可以让我们解析pb文件获取AST,比如protobufjs和gulp-protobufjs,这里我们选择protobufjs,我们需要获取pb文件的AST,以json的形式表示。戳github链接:https://github.com/protobufjs/protobuf.js使用protobuf解析一个pb文件是这样的:varpbjs=require("protobufjs/cli/pbjs");//或者require("protobufjs/cli").pbjs/.pbtspbjs.main(["--target","json","./myproto.proto"],function(err,output){if(err)throwerr;//对输出做一些事情});这样就可以解析得到AST对应的json了,json长这样:下面我们以这个pb文件为例,看看解析后的AST长什么样:packageMY_NAMESPACE;import"common.proto";messagesuperMessage{可选字符串testOne=1;需要subMessagetestTwo=2;需要NS_COMM.testtestThree=3;messagesubMessage{需要的字符串testName=1;}}消息otherMessage{可选int32otherName=1;}解析后的JSON看起来像这样::1},“testTwo”:{“规则”:“必需”,“类型”:“subMessage","id":2},"testThree":{"rule":"required","type":"NS_COMM.test","id":3}},"nested":{"subMessage":{“字段”:{“testName”:{“规则”:“必需”,“类型”:“字符串”,“id”:1}}}}},“otherMessage”:{“字段”:{“otherName":{"type":"int32","id":1}}}}},"NS_COMM":{........}}}看看这个json,你会发现嵌套频繁出现的字段,protobufjs解析的AST使用nested表示嵌套关系,nested的第一层是所有解析出来的命名空间,命名空间下嵌套的嵌套消息或枚举类型,消息类型可以继续嵌套消息或枚举,以此类推,形成一棵AST树来表示这个数据结构的层级关系2.根据AST生成mock数据。接下来需要做的就是根据AST生成相应的mock数据。以命令词TEST_NAMESPACE.superMessage为例,假设需要生成命令词对应的mock数据,我们可以大致实现成这样constcmd=`TEST_NAMESPACE.superMessage`constmessage=findMessage(cmd)constresult=handleMessage(message)functionhandleMessage(message){constfinalObject={}for(letkeyinmessage.fields){lettype=message.fields[key].typefinalObject[key]=getMockData(type)}returnfinalObject}functiongetMockData(field){switch(field){case"string":returnString("test");}case"int32":返回1024;默认值:返回未定义;}}如果只是简单的遍历key,看似很简单,但是pb文件的规则就没那么简单了,因为消息是允许嵌套的,消息中的字段类型,除了常见的类型string,int32,它也可以是消息或枚举。其次,这些自定义类型可以嵌套在message中,也可以在当前命名空间中,也可以从其他命名空间中引用。所以我们需要进一步重写它??,像这样:finalObject[key]=getMockData(type)}else{letmessage=findMessage(type)finalObject[key]=handleMessage(message)}}returnfinalObject}可以看到有一个findMessage方法可以找到对应的message(或者enum),该方法的查找过程为:1.查找当前消息下的嵌套,如果没有嵌套,则转第二步;2、查找当前命名空间中的nested,如果没有nested,转第三步;3、查找嵌套在所有命名空间下,是否有对应的命令词,如果没有,则抛出错误。(具体查找nested的过程可以递归查找对应的属性是否存在,笔者为了偷懒,直接使用eval查找对象上的属性,具体代码就不展示了)最important问题解决了,接下来要处理的是其他语法规则:1.enum类型的处理;2、语法重复处理,返回数组;3.extend、oneof、reserved、map语法的处理。3、丰富配置项,增加钩子函数由于我们需要将其打包成npm包供他人使用,因此需要做一些额外的改进工作。假设只能生成假数据是远远不够的。比如用户想要自定义字符串类型的数据,或者在接口层面重新修改各个接口返回的数据,那么我们就需要丰富我们模块的配置项,将一些钩子函数暴露给用户。最后的用法是这样的:constmocker=require("pbmock")varreq={},res={send:()=>{console.log("done!")}varresult=mocker({cmd:"superMessage",whiteList:["superMessage"],disabled:!process.env.development,entry:path=>path.resolve(__dirname,"./pb/mytest.proto"),configureType:{"int32":mock=>{returnmock.Random.integer(-2,-1)},"string":"oleiwa"},hook:{"superMessage":(source,Mock)=>{source.code=0;}},times:2,logger:true,exposeVar:{req,res,},intercept:({req,res},data)=>{res.send(data)}})最后这样,我们命令字superMessage解析完毕,愉快的进行了一些配置,返回了我们想要的数据。接下来,我们只需要将配置项提取到一个单独的config.js文件中即可。在业务中,我们只需要维护和修改配置文件即可。总结回顾本文讨论的内容:1.常见的mocking方案以及基于pb协议的mocking方案的选择。2、pb协议的mocking方案的具体实现过程,包括AST的生成,根据AST的mock数据,以及mocking过程中需要处理的几个问题。3、最后我们将其封装成一个模块,暴露出配置项,供业务开发过程中使用。感谢观看~最后附上项目地址:https://github.com/handsomeguy/pbmocknpm包已经发布,使用:npmi--save-devpbmock
