摘要:前言最近一直在搗鼓畢設(shè),準(zhǔn)備做的是一個(gè)基于前后端開發(fā)的平臺(tái),前期花了很多時(shí)間完成了功能模塊的交互。核心代碼就是這么一句。經(jīng)過各種猜想和測(cè)試,發(fā)現(xiàn)是模擬有問題。其實(shí)用的最終核心思路還是一樣的。
前言
最近一直在搗鼓畢設(shè),準(zhǔn)備做的是一個(gè)基于前后端開發(fā)的Mock平臺(tái),前期花了很多時(shí)間完成了功能模塊的交互。現(xiàn)在進(jìn)度推到如何設(shè)計(jì)核心功能,也就是Mock數(shù)據(jù)的解析。
根據(jù)之前的需求設(shè)定加上一些思考,用戶可以像寫json一般輕松完成數(shù)據(jù)的mock,也可以通過在mock數(shù)據(jù)模型之上進(jìn)行構(gòu)建出復(fù)雜的數(shù)據(jù)模型并在項(xiàng)目中引用。
這看似簡(jiǎn)單的需求其實(shí)需要處理幾個(gè)不同的模塊功能以及交互設(shè)計(jì)。該如何處理解析不同mock數(shù)據(jù)并進(jìn)行構(gòu)造?前端交互中模擬數(shù)據(jù)該如何處理?數(shù)據(jù)構(gòu)造時(shí)如何加載用戶設(shè)定的數(shù)據(jù)模型?錯(cuò)誤捕捉與處理?
這些都暫時(shí)沒有一個(gè)好的處理結(jié)果。因此想要完成核心功能我們需要明確需求,并且通過同類產(chǎn)品是如何處理的,通過閱讀它們的源碼來學(xué)習(xí)思想并加入。
明確需求在明確該功能模塊之前我們可以通過模擬流程來明確。
用戶 -> 添加數(shù)據(jù)模型 - > 實(shí)時(shí)看到構(gòu)造結(jié)構(gòu)用戶 -> 添加接口 -> 構(gòu)造json格式返回參數(shù) -> 預(yù)覽
構(gòu)造json格式返回參數(shù) 不僅包含返回的正文,同時(shí)也設(shè)定了 header 和 method。
閱讀源碼符合大部分需求的開源項(xiàng)目有
mock.js
easy-mock
eolinker
YAPI
DOCCLEVER
MOCK.JS篇首先我們需要明確現(xiàn)階段大部門的 Mock 平臺(tái)或多或少都是受到 Mock.js 的思想或者是其增強(qiáng)版。
我們可以用下面簡(jiǎn)單的 json 通過 Mock.js來構(gòu)造數(shù)據(jù):
example: { "status|0-1": 0, //接口狀態(tài) "message": "成功", //消息提示 "data": { "counts":"@integer", //統(tǒng)計(jì)數(shù)量 "totalSubjectType|1-4": [ //4-10意味著可以隨機(jī)生成4-10組數(shù)據(jù) { "subjectName|regexp": "大數(shù)據(jù)|機(jī)器學(xué)習(xí)|工具", //主題名 "subjectType|+1": 1 //類型 } ], "data":[ { "name": "@name", //用戶名 "cname":"@cname", "email": "@email", //email "time": "@datetime" //時(shí)間 } ]} }
返回結(jié)果
{ "status": 0, "message": "成功", "data": { "counts": 2216619884890228, "totalSubjectType": [ { "subjectNameregexp": "大數(shù)據(jù)|機(jī)器學(xué)習(xí)|工具", "subjectType": 1 }, { "subjectNameregexp": "大數(shù)據(jù)|機(jī)器學(xué)習(xí)|工具", "subjectType": 2 }, { "subjectNameregexp": "大數(shù)據(jù)|機(jī)器學(xué)習(xí)|工具", "subjectType": 3 }, { "subjectNameregexp": "大數(shù)據(jù)|機(jī)器學(xué)習(xí)|工具", "subjectType": 4 } ], "data": [ { "name": "Ruth Thompson", "cname": "魯克", "email": "z.white@young.gov", "time": "1985-02-06 05:45:21" } ] } }
而且可以通過其 Mock.Random.extend() 來擴(kuò)展自定義占位符.
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()); // 結(jié)果: Saturday console.log(Mock.mock("@weekday")); // 結(jié)果: Tuesday console.log(Random.sex()); // 結(jié)果: 男 console.log(Mock.mock("@sex")); // 結(jié)果: 未知
來延伸所需進(jìn)的拓展。
這個(gè)可以將自定義數(shù)據(jù)模型先進(jìn)行解析,然后通過extend將其加入。
easy-mockeasy-mock 是我參考的主要項(xiàng)目之一,它的UI交互非常符合我的設(shè)定,而且作為開源項(xiàng)目可以從它的源碼中學(xué)到很多。
直接來看它提供接口編輯的頁面
{ data: { img: function({ _req, Mock }) { return _req.body.fileName + "_" + Mock.mock("@image") } } }
可以從上得之它既可以處理Mock數(shù)據(jù)模擬也可以處理函數(shù),而且它內(nèi)部有一套能處理req的內(nèi)容。
先是在源碼中找了一下,找到幾個(gè)疑似點(diǎn),但是不確定,還是在本地裝好環(huán)境,主要是需要按照redis.然后啟動(dòng)服務(wù)去打幾個(gè)斷點(diǎn)輸出。
根據(jù)經(jīng)驗(yàn)先確定 controllersmock.js 應(yīng)該是處理數(shù)據(jù)模擬的地方。通過瀏覽源碼并分析,最終定位于 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 都應(yīng)該清楚 ctx.body 的含義。然后我在上面寫了句 console.log(apiData)。
然后在瀏覽器端發(fā)送請(qǐng)求。看下 node 端輸出和瀏覽器端拿到的數(shù)據(jù),基本可以肯定最終輸出就是這個(gè)。
然后我們往上翻,可以看到這么一段代碼:
const vm = new VM({ timeout: 1000, sandbox: { Mock: Mock, mode: api.mode, template: new Function(`return ${api.mode}`) // eslint-disable-line } }) console.log("數(shù)據(jù)驗(yàn)證") console.log(mode) vm.run("Mock.mock(new Function("return " + mode)())") // 數(shù)據(jù)驗(yàn)證,檢測(cè) setTimeout 等方法 apiData = vm.run("Mock.mock(template())") // 解決正則表達(dá)式失效的問題
通過查詢了解到 VM 是一個(gè)沙盒,可以運(yùn)行不受信任的代碼。
大概就能了解 easy-mock 通過 vm 沙盒模式運(yùn)行 mode 代碼解析后返回結(jié)果。
核心代碼就是 Mock.mock( template ) 這么一句。根據(jù)數(shù)據(jù)模板生成模擬數(shù)據(jù)。
通過查文檔了解 template 是可以直接內(nèi)部寫函數(shù)然后執(zhí)行的。
這樣解析的難度大大下降,發(fā)現(xiàn)原來并沒有特別復(fù)雜的,依舊是依賴了 Mock.js 的原生方法。
然后我們可以看到 easy-mock 另一的操作就是可以獲取 請(qǐng)求參數(shù)_req。也就是可以通過以下代碼來根據(jù)請(qǐng)求參數(shù)返回指定數(shù)據(jù)。
{ success: true, data: { default: "hah", _req: function({ _req }) { return _req }, name: function({ _req }) { return _req.query.name || this.default } } }
_req 一看就是從請(qǐng)求參數(shù)中獲得的對(duì)象。
Mock.js是沒有這個(gè)對(duì)象的,我們來找找源碼中是哪里注入了這個(gè)對(duì)象。
還是在 mock.js 這個(gè)文件中第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是處理數(shù)據(jù)模板的地方,打個(gè)斷點(diǎn)再輸出一次可以發(fā)現(xiàn)其實(shí)是在 Mock.mock(new Function("return " + mode)())" 之后傳入的參數(shù)。
options._req = ctx.request 這句代碼告訴了我們所謂的 _req是從哪里來的。
因此這個(gè)技術(shù)點(diǎn)我們也了解了是怎么做的,那么剩下一個(gè)靈活的支持 restful 通過閱讀源碼發(fā)現(xiàn)其實(shí)也沒怎么處理,只是用 pathToRegexp 進(jìn)行了一次驗(yàn)證。它先是在 middlewares/index.js 中 的 mockFilter 進(jìn)行了路徑正則。
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("接口請(qǐng)求頻率太快,已被限制訪問") return } console.log("通過篩選") ctx.pathNode = { projectId: pathNode[1], mockURL: "/" + (pathNode[2] || "") } return next() }
然后通過存在 redis 里的接口內(nèi)容再進(jìn)行了驗(yàn)證匹配。
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匹配檢驗(yàn)") 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)
基本不匹配的路徑請(qǐng)求都是在 item.method === method && pathToRegexp(url).test(mockURL) 這句代碼里被攔截的。
非常優(yōu)秀的代碼。通讀下來,加上斷點(diǎn)對(duì)其思路邏輯學(xué)到了很多。
eolinker它的后端代碼是 PHP 的,這就略過不看了。
YAPI它的核心后端處理代碼是在 mockServer.js 里
有了之前的閱讀經(jīng)驗(yàn)很快找到處理 Mock 數(shù)據(jù)的地方
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") } }
非常簡(jiǎn)單粗暴的處理方法。。。
對(duì)增強(qiáng)功能比較好奇在, 于是在 commonmock-extra.js 里找到了 mock(mockJSON, context) 方法。根據(jù)參數(shù)其實(shí)就能了解綁定上下文然后做了一些動(dòng)作。這里就不展開詳細(xì)。等之后開發(fā)的時(shí)候用到再去細(xì)讀。因?yàn)檫@是做了其自己的增強(qiáng)的Mock功能,而暫時(shí)不需要這方面的考慮。
DOClecer這個(gè)項(xiàng)目是國內(nèi)一個(gè)創(chuàng)業(yè)團(tuán)隊(duì)做的,我也加入了其官方群。雖然還沒有用過。不過不妨礙閱讀其源碼了解思路。不過講道理這個(gè)代碼組織風(fēng)格是挺糟糕的。。。
而且源碼中不止一次出現(xiàn)了eval... 于是放棄參考。
寫個(gè)小模塊開心一下通過閱讀以上項(xiàng)目的源碼,其實(shí)主要是前三個(gè),感覺可以完成自己想要的需求了。那么先寫一個(gè)小的來作為基礎(chǔ)模塊。
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 // 獲取接口路徑內(nèi)容 console.log("ctx.pathNode", ctx.pathNode) // 匹配內(nèi)容是否一致 console.log("驗(yàn)證內(nèi)容中...") // 模擬數(shù)據(jù) 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)())") // 數(shù)據(jù)驗(yàn)證,檢測(cè) setTimeout 等方法, 順便將內(nèi)部的函數(shù)執(zhí)行了 // 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": "你調(diào)用了get方法"}) break; case "post": result = success({"msg": "你調(diào)用了post方法"}) break; case "put" : result = success({"msg": "你調(diào)用了put方法"}) break; case "patch" : result = success({"msg": "你調(diào)用了patch方法"}) break; case "delete" : result = success({"msg": "你調(diào)用了delete方法"}) break; default: result = error() } // console.log(result) return ctx.body = result }
這里調(diào)試的遇到一些問題,主要是一開始測(cè)試的時(shí)候發(fā)現(xiàn) Mock 只將規(guī)則的數(shù)據(jù)模擬出,發(fā)現(xiàn) function 類型的函數(shù)都沒執(zhí)行,一開始定位以為是Mock.Handler.function 在 ts 中未執(zhí)行。于是在里面寫了一個(gè)輸出,發(fā)現(xiàn)的確沒有。經(jīng)過各種猜想和測(cè)試,發(fā)現(xiàn)是模擬mode有問題。
一開始我是這么寫的
const testcode = { "array1|1": ["AMD", "CMD", "KMD", "UMD"], "array2|1-10": ["Mock.js"], "array3|3": ["Mock.js"], "function": function() { return this.title } }
事實(shí)上應(yīng)該這么寫
const testcode = `{ "array1|1": ["AMD", "CMD", "KMD", "UMD"], "array2|1-10": ["Mock.js"], "array3|3": ["Mock.js"], "function": function() { return this.title } }`
參照 easy-mock 的思路可以實(shí)現(xiàn)一個(gè)基礎(chǔ)的 Mock數(shù)據(jù)解析器,而且可以根據(jù) koa 的特性同時(shí)支持 _req 的一些參數(shù),這里先不加進(jìn)去。
如何支持自定義的數(shù)據(jù)模型也有了基本的思路,在之前沒有考慮 redis 情況下還是用傳統(tǒng)的數(shù)據(jù)庫查詢。具體實(shí)現(xiàn)等后期再搗鼓出來再寫出來。
結(jié)尾通過這兩天的學(xué)習(xí),總算把一個(gè)Mock的核心模塊該如何實(shí)現(xiàn)的思路給理順了。
其實(shí)無論你是用戶自定義數(shù)據(jù),比如
{ "user": User, // User是用戶自定義的數(shù)據(jù)類型 "string2|3": "value", "number1|+1": 100, _req: function({ _req }) { return _req }, name: function({ _req }) { return _req.query.name || this.default } }
還是 Mock.js 原生的語法,你最終轉(zhuǎn)換過來需要執(zhí)行的是一樣的內(nèi)容,無非是在其轉(zhuǎn)換前需要做一定的處理。只有搞懂了基本的數(shù)據(jù)模擬實(shí)現(xiàn),基本上你可以將各個(gè)參數(shù)都做定制化。比如有的平臺(tái)會(huì)將用戶自己編寫的函數(shù)一起和 json 拼接。其實(shí)用的最終核心思路還是一樣的。
參考資料Mock.js使用
mockjs官方文檔
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/115609.html
摘要:前言最近一直在搗鼓畢設(shè),準(zhǔn)備做的是一個(gè)基于前后端開發(fā)的平臺(tái),前期花了很多時(shí)間完成了功能模塊的交互。核心代碼就是這么一句。經(jīng)過各種猜想和測(cè)試,發(fā)現(xiàn)是模擬有問題。其實(shí)用的最終核心思路還是一樣的。 前言 最近一直在搗鼓畢設(shè),準(zhǔn)備做的是一個(gè)基于前后端開發(fā)的Mock平臺(tái),前期花了很多時(shí)間完成了功能模塊的交互。現(xiàn)在進(jìn)度推到如何設(shè)計(jì)核心功能,也就是Mock數(shù)據(jù)的解析。 根據(jù)之前的需求設(shè)定加上一些思考...
摘要:前言最近一直在搗鼓畢設(shè),準(zhǔn)備做的是一個(gè)基于前后端開發(fā)的平臺(tái),前期花了很多時(shí)間完成了功能模塊的交互。核心代碼就是這么一句。經(jīng)過各種猜想和測(cè)試,發(fā)現(xiàn)是模擬有問題。其實(shí)用的最終核心思路還是一樣的。 前言 最近一直在搗鼓畢設(shè),準(zhǔn)備做的是一個(gè)基于前后端開發(fā)的Mock平臺(tái),前期花了很多時(shí)間完成了功能模塊的交互。現(xiàn)在進(jìn)度推到如何設(shè)計(jì)核心功能,也就是Mock數(shù)據(jù)的解析。 根據(jù)之前的需求設(shè)定加上一些思考...
摘要:只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙只有動(dòng)手,你才能真正掌握一門技術(shù)持續(xù)更新中項(xiàng)目地址求求求源碼系列跟一起學(xué)如何寫函數(shù)庫中高級(jí)前端面試手寫代碼無敵秘籍如何用不到行代碼寫一款屬于自己的類庫原理講解實(shí)現(xiàn)一個(gè)對(duì)象遵循規(guī)范實(shí)戰(zhàn)手摸手,帶你用擼 Do it yourself!!! 只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙 只有動(dòng)手,你才能真正掌握一門技術(shù) 持續(xù)更新中…… 項(xiàng)目地址 https...
摘要:重要功能一些擴(kuò)展的重要功能將在這里一點(diǎn)點(diǎn)從零開始進(jìn)行思考。假設(shè)現(xiàn)在的數(shù)據(jù)是這樣的演示項(xiàng)目接口示例超長字符串測(cè)試項(xiàng)目描述前端工程師宋青樹后端工程師獲取接口描述增加接口描述刪除接口描述更新接口描述我們需要將里面所有的字段數(shù)組去除。 前言 二月初想想這個(gè)月還得搗鼓一篇文章,也沒啥好的想法那還是記錄一下畢設(shè)的一些思路吧。 重要功能 一些擴(kuò)展的重要功能將在這里一點(diǎn)點(diǎn)從零開始進(jìn)行思考。 項(xiàng)目導(dǎo)入導(dǎo)...
閱讀 3915·2021-11-16 11:50
閱讀 925·2021-11-11 16:55
閱讀 3657·2021-10-26 09:51
閱讀 856·2021-09-22 15:03
閱讀 3409·2019-08-30 15:54
閱讀 3260·2019-08-30 15:54
閱讀 2467·2019-08-30 14:04
閱讀 919·2019-08-30 13:53