使用json-server矗漾,實現(xiàn)更獨立的前后端分離(vue-cli3項目為例)

一、背景

前后端分離薄料,在日常開發(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):

db.json

顯然,這時候堪澎,當我們想獲取某一個接口對應的數(shù)據(jù)時擂错,需要這樣訪問:

http://localhost:3001/mock_data_steps_step1

/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:

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':

/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未生效問題,求指導调炬。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末语盈,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子筐眷,更是在濱河造成了極大的恐慌黎烈,老刑警劉巖习柠,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匀谣,死亡現(xiàn)場離奇詭異,居然都是意外死亡资溃,警方通過查閱死者的電腦和手機武翎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溶锭,“玉大人宝恶,你說我怎么就攤上這事∨客保” “怎么了垫毙?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拱绑。 經(jīng)常有香客問我综芥,道長,這世上最難降的妖魔是什么猎拨? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任膀藐,我火速辦了婚禮,結(jié)果婚禮上红省,老公的妹妹穿的比我還像新娘额各。我一直安慰自己,他們只是感情好吧恃,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布虾啦。 她就那樣靜靜地躺著,像睡著了一般痕寓。 火紅的嫁衣襯著肌膚如雪傲醉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天厂抽,我揣著相機與錄音需频,去河邊找鬼。 笑死筷凤,一個胖子當著我的面吹牛昭殉,可吹牛的內(nèi)容都是我干的苞七。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼挪丢,長吁一口氣:“原來是場噩夢啊……” “哼蹂风!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乾蓬,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤惠啄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后任内,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撵渡,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年死嗦,在試婚紗的時候發(fā)現(xiàn)自己被綠了趋距。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡越除,死狀恐怖节腐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情摘盆,我是刑警寧澤翼雀,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站孩擂,受9級特大地震影響狼渊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肋殴,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一囤锉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧护锤,春花似錦官地、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阔涉。三九已至缓淹,卻和暖如春静尼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背掩缓。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工雪情, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人你辣。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓巡通,卻偏偏與公主長得像尘执,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宴凉,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容