一、背景
前后端分離薄料,在日常開發(fā)中缩功,一直是開發(fā)人員的剛需。
作為前端都办,期望能夠在開發(fā)過程中嫡锌,不依賴后臺接口的開發(fā)進度,直接自己模擬后臺返回數(shù)據(jù)琳钉,在沒有真實接口和數(shù)據(jù)的情況下势木,跑通接口調(diào)用邏輯,對返回數(shù)據(jù)進行處理歌懒、或是界面展示啦桌。
筆者所在團隊的解決方案:
使用內(nèi)部在線mock平臺,由后臺同學在平臺上定義各個接口名稱及其返回的數(shù)據(jù)結(jié)構(gòu)及皂,前端同學根據(jù)平臺上給出的接口甫男,進行相應開發(fā),及接口調(diào)用邏輯測試验烧。
筆者開發(fā)體驗:
1.接口名稱及返回數(shù)據(jù)結(jié)構(gòu)板驳,都由后臺同學定義給出,按照規(guī)范碍拆,前端同學除添加數(shù)據(jù)外若治,不可以擅自操作mock平臺上的接口。(避免造成混亂)因此感混,mock數(shù)據(jù)的創(chuàng)建端幼,十分依賴后臺同學的工作,前端同學沒有自主性弧满。實際開發(fā)中婆跑,也常出現(xiàn)需要等待mock數(shù)據(jù)的情況,造成前端接口調(diào)用相關(guān)邏輯被阻滯庭呜、無法流暢開發(fā)的問題滑进。
2.在線的mock平臺要切換到瀏覽器界面操作摹迷。當需要對mock數(shù)據(jù)進行編輯,需將頁面切換到編輯頁郊供、并進行保存等相關(guān)操作峡碉,效率比較低下。
二驮审、期望開發(fā)體驗
筆者期望的開發(fā)體驗是鲫寄,當我需要調(diào)用某個接口的時候,可以自主模擬這樣一個接口疯淫,以及它返回的數(shù)據(jù)結(jié)構(gòu)+數(shù)據(jù)地来,而不需要依賴后臺同學的工作。并且熙掺,對接口返回數(shù)據(jù)的編輯未斑,全部放在我們最熟悉的編輯器里。
【最優(yōu)體驗】
本地mock數(shù)據(jù)的文件夾層級币绩,與接口url相對應蜡秽。可以通過文件夾名稱缆镣,快速定位到指定接口返回的mock數(shù)據(jù)芽突。
如:接口 /jsonServer/user/getUserName ,對應的mock數(shù)據(jù)董瞻,存放在 mock文件夾下的 jsonServer/user/getUserName.json 中寞蚌。
有人可能會說,不知道接口的url和返回的數(shù)據(jù)結(jié)構(gòu)钠糊,憑自己的想象模擬挟秤,和后臺同學實際設計的接口大概率不會吻合,拿到真實接口之后抄伍,還需要做修改艘刚,這是一種浪費。
我認為逝慧,這種浪費昔脯,相比被動等待啄糙、依賴別人給mock接口笛臣,高效太多。況且隧饼,很多接口返回的數(shù)據(jù)結(jié)構(gòu)沈堡,是完全可以推測出來的(類似返回列表、詳情等)燕雁,對不上的诞丽,可能只是字段名稱而已鲸拥,修改成本很低。
同時僧免,如果有了自主mock的能力刑赶,我們甚至可以拿著自己推測的數(shù)據(jù)結(jié)構(gòu),找后臺同學對接懂衩,最起碼撞叨,這個時候,我們有主動推進的資本浊洞,而不是完全被動等待牵敷。
三、創(chuàng)建mock-server.js
const jsonServer = require('json-server');
// mark:要提前創(chuàng)建db.js法希,返回我們的mock數(shù)據(jù)
const $db = require('./db');
const server = jsonServer.create();
const middlewares = jsonServer.defaults();
const router = jsonServer.router($db);
server.use(router);
// Set default middlewares (logger, static, cors and no-cache)
server.use(middlewares);
// To handle POST, PUT and PATCH you need to use a body-parser
server.use(jsonServer.bodyParser);
server.listen(3001, () => {? ? ?
????console.log('JSON Server is running at 3001');
});
四枷餐、整合出db.json
根據(jù)json-server的原理,它返回的數(shù)據(jù)全部存儲在db.json這一個json文件中苫亦。
而我們開發(fā)用的mock數(shù)據(jù)毛肋,顯然不能全部手動塞到這一個json中。
為了達到前面說的最優(yōu)體驗屋剑,在創(chuàng)建好各個接口對應的json文件后村生,我們需要用簡單的文件操作,將這些json文件合并為db.json饼丘。
我的mock目錄(mock位于根目錄下)結(jié)構(gòu)如下(因為要進行文件操作趁桃,所以使用db.js):
db.js如下:
const $fs = require('fs');
const mockData = {
? ? DB: { },
????readDir(path) {
? ??????const stats = $fs.statSync(path);? ?
????????if (stats.isDirectory()) {? ??????
? ??????????const files = $fs.readdirSync(path);
? ??????????files.forEach(file => {
? ??????????????this.readDir(`${path}/${file}`);
? ??????????});
? ??????} else {
? ??????????const data = $fs.readFileSync(path);
? ? ? ? ? ? // 把json文件的路徑作為key
? ??????????const key = path.replace('.json', '');
? ??????????this.DB[key] = JSON.parse(data);
? ? ? ? }
????}
};
mockData.readDir('mock/data');
module.exports = mockData.DB;
ok,至此肄鸽,mock-server和mock數(shù)據(jù)全部準備好了卫病,node mock-server.js,啟動試一下典徘。
錯誤寫的很明白蟀苛,database、也就是db.json的屬性里逮诲,不能包含字符 / 帜平。
很顯然,是 this.DB[key] = JSON.parse(data); 這句話梅鹦,造成的報錯裆甩。
怎么辦?
key值里面不能有 / 齐唆,改造唄嗤栓。
?const key = path.replace('.json', '');
改成
const key = path.replace('.json', '').replace(/\//g, '_');
再啟動,不報錯。
五茉帅、讀取db.json的數(shù)據(jù)
成功生成db.json后叨叙,看一下它的結(jié)構(gòu)(瀏覽器訪問:localhost:3001/db):
顯然,這時候堪澎,當我們想獲取某一個接口對應的數(shù)據(jù)時擂错,需要這樣訪問:
http://localhost:3001/mock_data_steps_step1
這自然不是我們期望的訪問方式。
我們期望的是樱蛤,訪問:http://localhost:3001/steps/step1
可以拿到上圖所示的數(shù)據(jù)马昙。
這才是模擬接口調(diào)用該有的樣子。
六刹悴、做些努力行楞,實現(xiàn)更真實的接口調(diào)用方式
1.首先去掉當前key的mock_data_字樣,簡化key值
const key = path.replace('.json', '').replace(/\//g, '_');
改成
const key = path.replace('.json', '').replace('mock/data/', '').replace(/\//g, '_');
再看一下db.json:
嗯土匀,清爽多了子房。
2.將接口url映射成db.json的key值
還記得自定義路由嗎(參考:json-server全攻略)?說到路由映射就轧,用自定義路由唄
(非最終解決方案证杭。存在bug,筆者未解決妒御,如有方案解愤,歡迎留言討論!最終解決方案見【七 — 4】)
其中乎莉,routeHandler.js需要返回一個json對象送讲,指明路由配置規(guī)則。
這里的routeHandler.js:
function routeHandler(db) {
? ??const rewriter = {};
? ??Object.keys(db).forEach(key => {????????
????????const routeKey = `/${key.replace(/_/g, '/')}`;????????
????????rewriter[routeKey] = `/${key}`;????
????});
????return rewriter;
}
module.exports = routeHandler;
打印一下返回的rewiriter:
訪問 http://localhost:3001/steps/step1:
成功拿到所需數(shù)據(jù)惋啃。
但是
但是
但是
從頁面發(fā)送請求哼鬓,會有問題!詳情見下文边灭。
七异希、從頁面發(fā)請求,拉取mock數(shù)據(jù)
mock服務和數(shù)據(jù)準備好绒瘦,接下來自然是要使用了称簿。
同時啟動vue-cli-service和mock-server兩個服務(一個跑頁面,一個跑數(shù)據(jù))惰帽,并在頁面發(fā)送請求憨降。
1.首先,將所有請求代理到3001端口上:
vue.config.js配置:
devServer: {
????????port: 3000,
????????proxy: 'http://localhost:3001'
}
2.簡單封裝request.js善茎。這里使用axios發(fā)送異步請求:
import $axios from 'axios';
function request(options) {
????let conf = Object.assign({
????????url: '',
????????method: 'get',
????????responseType: 'text',
????????ContentType: 'application/json'
????}, options);
????let pm = $axios(conf).then(xhr => {
????????console.log('xhr:', xhr);
????????return Promise.resolve(xhr.data);
????}).catch(err => {
????????console.log('err:', err);
????????return Promise.reject(new Error('request 拋出錯誤'));
????});
????return pm;
}
export default request;
3.頁面模擬請求發(fā)送:
page.vue:
$request({
????url: '/steps/step2'
????method: 'get',
????params: { name: 'test' }
}).then(rs => {
????console.log('rs:', rs);
}).catch(err => {
????console.log('err:', err);
}
404券册,推測兩種可能,要么是代理沒生效垂涯,要么是路由映射出了問題烁焙。
將請求的 url: '/steps/step2' 改成? url: '/steps_step2':
成功拿到mock數(shù)據(jù)。說明3001端口代理生效耕赘。
4.放棄自定義路由jsonServer.rewriter骄蝇,改為手動映射路由(路由問題最終解決方案)
更改mock-server.js:
const jsonServer = require('json-server');
const $db = require('./db');
const server = jsonServer.create();
const middlewares = jsonServer.defaults();
const router = jsonServer.router($db);
// Set default middlewares (logger, static, cors and no-cache)
server.use(middlewares);
//? To handle POST, PUT and PATCH you need to use a body-parser
server.use(jsonServer.bodyParser);
// 攔截客戶端請求,進行自定義處理
server.use((req, res, next) => {
????// 手動映射操骡,更改請求url(/steps/step1 => /steps_step1)
????req.url = req.url.replace(/\//g, '_').replace('_', '/');
????next();
});
server.use(router);
server.listen(3001, () => {
????console.log('JSON Server is running at 3001');
});
再次嘗試發(fā)送請求:
成功拿到mock數(shù)據(jù)九火。
八、優(yōu)化mock數(shù)據(jù)設計
經(jīng)過前面的配置册招,我們已經(jīng)可以自由地使用json-server岔激,進行完全由我們自己掌控的mock體驗。
但是是掰,后端給我們返回的數(shù)據(jù)結(jié)構(gòu)虑鼎,通常如前面例子中所示。往往是一個對象的形式键痛,其中包含code炫彩、data,這樣兩個字段絮短。
這就造成了一些問題:
(1)當接口返回的data是數(shù)組數(shù)據(jù)(通常是列表)江兢,由于數(shù)組被包在 { code: 0, data: [ xxx ] } 這個數(shù)據(jù)結(jié)構(gòu)里面,我們就沒有辦法使用json-server各種便捷的數(shù)組過濾功能丁频。
(2)我們無法使用json-server的功能杉允,對db.json的數(shù)據(jù)進行期望的寫入操作。一旦操作席里,就會破壞掉 { code: 0, data: [ xxx ] } 這個數(shù)據(jù)結(jié)構(gòu)夺颤。
顯而易見,在mock數(shù)據(jù)中直接使用code+data的數(shù)據(jù)結(jié)構(gòu)胁勺,并不合適世澜。
或許你會說,很簡單啊署穗,創(chuàng)建mock數(shù)據(jù)的時候寥裂,不按照這種格式,直接假設返回的是data的值案疲,不就可以了嗎封恰?
沒錯,這樣是可以完美解決上面兩個問題褐啡。
但是造成了另一個問題:如何模擬code不為0的返回結(jié)果呢诺舔?
【解決方案】
筆者想到的解決方案是,在data目錄下,創(chuàng)建success和fail文件夾低飒,分別存放模擬成功和失敗的數(shù)據(jù)许昨。
目錄結(jié)構(gòu)可參考“四”中截圖。
這樣褥赊,success文件夾下的數(shù)據(jù)糕档,默認都是 code: 0 的,fail下的數(shù)據(jù)拌喉,依然采用 { code: xxx, data: xxx } 的結(jié)構(gòu)速那。
優(yōu)化后的目錄結(jié)構(gòu):
success下的step1.json:
fail下的step1.json:
九、一次開發(fā)尿背,無限復用端仰。健壯清爽的mock體驗,你值得擁有
最后田藐,總結(jié)一下各文件:
1.mock-server.js
const jsonServer = require('json-server');
const $db = require('./db');
const server = jsonServer.create();
const middlewares = jsonServer.defaults();
const router = jsonServer.router($db);
// Set default middlewares (logger, static, cors and no-cache)
server.use(middlewares);
//? To handle POST, PUT and PATCH you need to use a body-parser
server.use(jsonServer.bodyParser);
// 攔截客戶端請求荔烧,進行自定義處理
server.use((req, res, next) => {
????// 手動映射,更改請求url(/steps/step1 => /steps_step1)
????req.url = req.url.replace(/\//g, '_').replace('_', '/');
????next();
});
server.use(router);
server.listen(3001, () => {
????console.log('JSON Server is running at 3001');
});
2.db.js
const $fs = require('fs');
const mockData = {
????DB: {},
????readDir(path) {
????????const stats = $fs.statSync(path);
????????if (stats.isDirectory()) {
????????????const files = $fs.readdirSync(path);
????????????files.forEach(file => {
????????????????this.readDir(`${path}/${file}`);
????????????});
????????} else {
????????????const data = $fs.readFileSync(path);
????????????const key = path.replace('.json', '').replace('mock/data/', '').replace(/\//g, '_');
????????????this.DB[key] = JSON.parse(data);
????????}
????}
};
mockData.readDir('mock/data');
module.exports = mockData.DB;
3.request.js
import $axios from 'axios';
function request(options) {
????// 是否走mock數(shù)據(jù)坞淮。開發(fā)期間手動更改
????const isMock = false;
????let conf = Object.assign({
????????url: '',
????????method: 'get',
????????responseType: 'text',
????????ContentType: 'application/json'
????}, options);
????// 處理mock茴晋。默認返回成功數(shù)據(jù),如指定mockFail回窘,則返回失敗數(shù)據(jù)
????if (options.mockFail) {
????????conf.url = '/fail' + conf.url;
????} else if (isMock) {
????????conf.url = '/success' + conf.url;
????}
????let pm = $axios(conf).then(xhr => {
????????console.log('xhr:', xhr);
????????return Promise.resolve(xhr.data);
????}).catch(err => {
????????console.log('err:', err);
????????return Promise.reject(new Error('request 拋出錯誤'));
????});
????return pm;
}
export default request;
4.頁面請求示例
$request({
????url: '/test',
????method: 'get',
????params: { name: '111' },
? ? mockFail: true
}).then(rs => {
????console.log('rs:', rs);
}).catch(err => {
????console.log('err:', err);
});
【附:mock方案設計中的其他問題】
db.json是由db.js生成并返回的诺擅,因此,每次重啟mock-server啡直,都會根據(jù)mock文件夾下的json文件重新生成db.json烁涌。也就是說,上一次對db.json的任何寫入酒觅、刪除等操作撮执,都不會被保存,只要重啟服務舷丹,數(shù)據(jù)就會恢復原樣抒钱。這里,可以根據(jù)自己的需求颜凯,自行選擇要不要在操作db.json的同時谋币,同步改寫mock文件夾保存的json數(shù)據(jù)。
#菜鳥一枚症概,如有錯誤蕾额、或更優(yōu)方案等,誠請指出彼城、指導诅蝶。另退个,jsonServer.rewriter未生效問題,求指導调炬。