最近接手了一個Angular項(xiàng)目件炉,因?yàn)樵许?xiàng)目缺乏Mock機(jī)制,因此重新搭建了一套矮湘,記錄一下斟冕。
項(xiàng)目現(xiàn)狀:
該項(xiàng)目是使用angular框架進(jìn)行開發(fā)的MIS后臺管理系統(tǒng)。接手后缅阳,我首先對項(xiàng)目進(jìn)行了架構(gòu)上的調(diào)整宫静,主要參考了angular項(xiàng)目的最佳實(shí)踐,參考文章如下:
https://medium.com/@motcowley/angular-folder-structure-d1809be95542
https://itnext.io/planning-the-architecture-of-your-angular-app-a4840bfec13b
https://itnext.io/choosing-a-highly-scalable-folder-structure-in-angular-d987de65ec7
https://medium.com/dev-jam/5-tips-best-practices-to-organize-your-angular-project-e900db08702e
具體的不詳述券时,最終優(yōu)化后的項(xiàng)目結(jié)構(gòu)如下:
├── e2e
├── src
│ ├── app
│ │ ├── core
│ │ │ ├── models
│ │ │ └── services
│ │ ├── shared
│ │ │ ├── components # container of all general components
│ │ │ └── utils # container of all general functions
│ │ ├── views # container of all business pages
│ │ │ ├── alarm
│ │ │ └── ......
│ │ ├── app-routing.module.ts
│ │ ├── app.component.css
│ │ ├── app.component.less
│ │ ├── app.component.html
│ │ ├──app.component.ts
│ ├── assets
│ │ ├── json # container of mock data assets
│ │ ├── i18n # container of internationalization assets
│ │ └── images
│ ├── environments
│ ├── favicon.ico
│ ├── index.html
│ ├── style.css
│ ├── style.less
│ ├── my-theme.css
│ ├── my-theme.less
│ ├── main.ts
│ ├── polyfill.ts
│ ├── test.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ ├── typing.d.ts
├── .angular-cli.json
├── CHANGELOG.md # recorder of all the important changes
├── karma.conf.js
├── localproxy.conf.json # config for mock server proxy
├── proxy.conf.json # config for server proxy
├── tsconfig.json
├── package.json
└── README.md
其中孤里,最重要的部分是app文件夾,承載了項(xiàng)目中最重要的業(yè)務(wù)代碼橘洞。除此之外捌袜,值得注意的是,原有項(xiàng)目將mock數(shù)據(jù)資源放在 /assets/json 路徑下進(jìn)行統(tǒng)一管理炸枣,每次調(diào)用文件時虏等,都需要從這個路徑下讀取數(shù)據(jù)。
優(yōu)化原因:
如上所述适肠,目前的項(xiàng)目將mock數(shù)據(jù)放在assets中進(jìn)行管理霍衫,通過改變各個模塊的service中的數(shù)據(jù)請求路徑來實(shí)現(xiàn)mock功能。因此侯养,每次需要mock數(shù)據(jù)時敦跌,都需要修改每個service中的路徑,引入本地的json文件逛揩。例如:
//home.service.ts -------------local代碼
…………
baseUrl = "./assets/json/"
url = {
home_serviceData: this.baseUrl + "/home_serviceData.json",
home_alarmData: this.baseUrl + "/home_alarmData.json",
home_alarmChartData: this.baseUrl + "/home_alarmChartData.json",
home_servicebarData:this.baseUrl + "/home_servicebar.json",
sourceNames: this.baseUrl + "/SourceName.json",
listSortMasters:this.baseUrl+"/listSortMsters.json",
}
//home.service.ts -------------線上代碼
…………
url = {
home_serviceData: this.baseUrl + "/uui-lcm/serviceNumByCustomer",
home_alarmData: this.baseUrl + "/alarm/statusCount",
home_alarmChartData: this.baseUrl + "/alarm/diagram",
home_servicebarnsData: this.baseUrl + "/uui-lcm/ns-packages",
sourceNames: this.baseUrl + "/alarm/getSourceNames",
listSortMasters: this.baseUrl + "/listSortMasters",
}
如上所示柠傍,如果需要實(shí)現(xiàn)home模塊的mock功能,需要編寫線上和本地兩套代碼辩稽,在本地開發(fā)時將線上代碼刪除惧笛,而線上聯(lián)調(diào)時,又需要將local地址刪除逞泄。
這樣的開發(fā)方式使mock數(shù)據(jù)十分麻煩患整。為了減輕負(fù)擔(dān),原有的開發(fā)方式是將項(xiàng)目拆為線上和線下兩套代碼喷众,這樣省去了反復(fù)注釋和刪除代碼的麻煩各谚。但是這種做法同樣存在很大弊端:1. 在兩套代碼中來回粘貼代碼使得開發(fā)效率降低,且很容易因?yàn)檎`粘貼或是漏粘貼出現(xiàn)功能錯誤侮腹;2. 每次mock均需要大量修改service代碼嘲碧,service模塊是angular項(xiàng)目的核心模塊,一旦誤刪會導(dǎo)致嚴(yán)重的問題父阻;3. 這種做法使無關(guān)代碼參與到mock數(shù)據(jù)中來愈涩,不夠優(yōu)雅望抽;
因此,需要建立一套mock數(shù)據(jù)機(jī)制履婉,改變現(xiàn)有的兩套代碼的開發(fā)方式煤篙。
優(yōu)化目標(biāo):
為解決現(xiàn)有mock方式存在的弊端,同時滿足項(xiàng)目開發(fā)的新需求毁腿,新的mock方式需要達(dá)成以下目標(biāo):
- 建立一鍵啟動mock服務(wù)機(jī)制辑奈,使一套代碼可以同時兼容線上和線下兩種開發(fā)環(huán)境,廢棄之前兩套代碼間相互粘貼的開發(fā)模式已烤;
- 隔離業(yè)務(wù)代碼和mock數(shù)據(jù)功能鸠窗,使mock數(shù)據(jù)部分的代碼成為一個獨(dú)立的模塊,從而避免對業(yè)務(wù)代碼的干擾胯究;
- 通過mock數(shù)據(jù)實(shí)現(xiàn)真正的前后端分離稍计,使項(xiàng)目既能兼容原有的mock數(shù)據(jù),又能滿足新功能快速開發(fā)的需求裕循;
技術(shù)選型:
json-server+faker.js
這兩個都是好東西臣嚣,具體不詳述,可以看官網(wǎng)文檔和參考文章:
https://github.com/typicode/json-server
https://github.com/marak/Faker.js/
https://www.npmjs.com/package/faker
https://segmentfault.com/a/1190000008574028
https://juejin.im/post/5b06e6426fb9a07aa34aaa8d
項(xiàng)目優(yōu)化難點(diǎn):
雖然json-server
可以解決大部分的請求數(shù)據(jù)返回剥哑,但是本項(xiàng)目還是有些特殊的情況硅则,這給mock數(shù)據(jù)造成了困難:
- 項(xiàng)目的請求路徑中存在變量,因此無法通過本地json文件直接模擬
- 項(xiàng)目原有接口遵循RESTful規(guī)范株婴,即:相同api怎虫,不同請求方式,則返回的數(shù)據(jù)不同督暂,實(shí)現(xiàn)的功能也不同
解決方法:
采用如下方法可以解決項(xiàng)目優(yōu)化中出現(xiàn)的難點(diǎn):
rewrite揪垄,routes,json-server接口攔截機(jī)制(post to get)
項(xiàng)目優(yōu)化思路:
以下是使用json-server
優(yōu)化項(xiàng)目的思路:
解釋一下思路逻翁。為了達(dá)到我們的優(yōu)化目標(biāo),解決項(xiàng)目中的難點(diǎn)捡鱼,采用了如圖所示的五個步驟進(jìn)行八回。
- 配置
package.json
文件,安裝json-server
驾诈,設(shè)置啟動服務(wù)的命令缠诅,同時配置代理,將本地服務(wù)代理至mock服務(wù)器上乍迄; - 創(chuàng)建
server.js
文件管引,用來豐富mock服務(wù)的配置內(nèi)容; - 引入
faker.js
闯两,用來支持新老功能同時開發(fā)褥伴; - 配置
routes.js
谅将,用來支持多字段接口轉(zhuǎn)發(fā)和可變請求路徑; - 攔截接口重慢,對非GET類的請求實(shí)現(xiàn)路徑重拼和接口轉(zhuǎn)發(fā)饥臂;
ok,可以開搞了…...
具體做法:
具體做法分為基礎(chǔ)配置和自定義配置兩部分似踱。其中隅熙,自定義配置是針對我們項(xiàng)目的特有情況對json-server
進(jìn)行的配置。
基礎(chǔ)操作
- package.json文件配置
安裝json-server:
npm install -g json-server
在package.json文件中使用json-server核芽,添加serve的運(yùn)行命令囚戚,指定mock服務(wù)接口并啟動本地代理服務(wù):
"mockproxy": "ng serve --proxy-config localproxy.conf.json”, //代理本地請求至mock服務(wù)器
"mockconfig": "node ./src/app/mock/server.js --port 3002”, //啟動本地mock服務(wù)器
"mock": "npm run mockconfig | npm run mockproxy",
其中,npm run mock命令是啟動mock服務(wù)和代理服務(wù)的集合轧简。
以上弯淘,實(shí)現(xiàn)了通過npm命令一鍵啟動mock服務(wù)的需求。接下來吉懊,需要對server.js文件進(jìn)行配置庐橙。
- server配置
在app目錄下,創(chuàng)建mock文件夾借嗽,用來管理mock的配置文件态鳖。這樣,實(shí)現(xiàn)了項(xiàng)目mock數(shù)據(jù)與業(yè)務(wù)代碼的隔離恶导。
在mock文件夾中浆竭,創(chuàng)建server.js文件,用作對json-server服務(wù)做自定義配置惨寿,這也是整個mock系統(tǒng)中最終要的一個文件邦泄。
server.js文件的基礎(chǔ)編寫思路是:導(dǎo)入原有mock數(shù)據(jù)文件,之后將請求路徑通過json-server的use方法進(jìn)行使用裂垦,同時對接口進(jìn)行轉(zhuǎn)發(fā)顺囊。具體的編寫步驟,不詳述蕉拢,上代碼:
const jsonServer = require('json-server');
const server = jsonServer.create();
const middlewares = jsonServer.defaults();
const customersRouters = require('./routes');
const baseUrl = "/usecaseui-server/v1”;
// Set default middlewares
server.use(middlewares);
// Get mock data
const fs = require('fs');
const path = require('path');
let localJsonDb = {}; //import mock datas
const mockFolder = './src/app/mock/json'; //mock json path folder
const filePath = path.resolve(mockFolder);
fileDisplay(filePath);
function fileDisplay(filePath) {
let fileList = [];
// Return filelist on based of filePath
const files = fs.readdirSync(filePath);
files.forEach((filename) => {
// Get filename's absolute path
let filedir = path.join(filePath, filename);
// Get the file information according to the file path and return an fs.Stats object
fs.stat(filedir, (err, stats) => {
if (err) {
console.warn('Get files failed......');
} else {
let isFile = stats.isFile(); // files
let isDir = stats.isDirectory(); //files folder
if (isFile) {
fileList.push(path.basename(filedir, '.json'));
fileList.forEach(item => {
localJsonDb[item] = getjsonContent(item);
})
}
if (isDir) {
fileDisplay(filedir);
}
Object.keys(fakeoriginalData).map(item => {
localJsonDb[item] = fakeoriginalData[item];
})
}
})
})
setTimeout(() => {
runServer(localJsonDb);
}, 100)
}
function getjsonContent(path) {
let newpath = `./src/app/mock/json/${path}.json`;
let result = JSON.parse(fs.readFileSync(newpath));
return result;
}
function serverRewrite() {
server.use(jsonServer.rewriter(customersRouters))
}
function runServer(db) {
server.use(jsonServer.router(db));
}
server.listen(3002, () => {
console.log('Mock Server is successfully running on port 3002 ??')
});
有幾點(diǎn)需要說明:
第一特碳,為了盡量減小修改成本,本項(xiàng)目直接讀取了mock文件夾中的數(shù)據(jù)晕换,同時午乓,將這些文件的文件名直接引用為接口的轉(zhuǎn)發(fā)路徑:
………
let fileList = [];
// Return filelist on based of filePath
const files = fs.readdirSync(filePath);
files.forEach((filename) => {
// Get filename's absolute path
let filedir = path.join(filePath, filename);
// Get the file information according to the file path and return an fs.Stats object
fs.stat(filedir, (err, stats) => {
if (err) {
console.warn('Get files failed......');
} else {
let isFile = stats.isFile(); // files
let isDir = stats.isDirectory(); //files folder
if (isFile) {
fileList.push(path.basename(filedir, '.json'));
fileList.forEach(item => {
localJsonDb[item] = getjsonContent(item);
})
}
if (isDir) {
fileDisplay(filedir);
}
Object.keys(fakeoriginalData).map(item => {
localJsonDb[item] = fakeoriginalData[item];
})
}
})
})
………
function getjsonContent(path) {
let newpath = `./src/app/mock/json/${path}.json`;
let result = JSON.parse(fs.readFileSync(newpath));
return result;
}
………
上面這段代碼做的事情,就是文件名轉(zhuǎn)路徑闸准。通過調(diào)用node中的fs模塊中的readdirSync和stat方法益愈,讀取文件夾內(nèi)容,判斷文件狀態(tài)夷家,之后將符合條件的文件push到fileList數(shù)組中蒸其,同時敏释,通過readFileSync方法讀取文件內(nèi)容,并組成如下對象枣接,供server啟動服務(wù)用:
{
………
'uui-sotn_getPinterfaceByVpnId': { 'vpn-binding': [ [Object] ] },
'uui-sotn_getPnfInfo': {
'pnf-name': 'pnf1000',
'pnf-id': '79',
'in-maint': true,
'resource-version': '195',
'admin-status': 'up',
'operational-status': 'up',
'relationship-list': { relationship: [Array] }
},
'uui-sotn_getSpecificLogicalLink': {
'link-name': 'nodeId-79-ltpId-4_nodeId-78-ltpId-4',
'in-maint': false,
'link-type': 'some type',
'speed-value': 'some speed',
'resource-version': '13031',
'operational-status': 'up',
'relationship-list': { relationship: [Array] }
},
xuran_test_data: {
'esr-system-info-id': 'xuran',
'service-url': 'http://10.10.10.10:8080/',
'user-name': 'demo',
password: 'demo123456!',
'system-type': 'ONAP',
'resource-version': '18873'
}
………
}
如上例颂暇,通過配置,將json文件的文件名作為導(dǎo)入對象的key值但惶,將文件內(nèi)容作為導(dǎo)入文件的value值耳鸯。經(jīng)過這樣配置的對象,就是可以被json-server所使用的數(shù)據(jù)了膀曾。
值得注意的是县爬,這樣的操作方式需要將原有的mock文件進(jìn)行重命名,使其符合接口的規(guī)則添谊〔圃基本要求是:文件名=下劃線連接的請求路徑名。這樣做有些麻煩斩狱,但是只需要修改一次耳高,日后有新的數(shù)據(jù)修改需求就變得很方便,同時對于其他開發(fā)者來說所踊,只需要做在json文件夾中添加符合規(guī)范的文件就可以實(shí)現(xiàn)mock數(shù)據(jù)功能泌枪,性價(jià)比還是可以的。
第二秕岛,本項(xiàng)目引入了json-server中間件碌燕,使用了router將拼接好的路徑對象應(yīng)用起來,搭建項(xiàng)目服務(wù)继薛。將上述對象導(dǎo)入進(jìn)來就可以實(shí)現(xiàn)接口的轉(zhuǎn)發(fā)了修壕。
function runServer(db) {
server.use(jsonServer.router(db));
}
第三,導(dǎo)入的對象的key值是用下劃線連接的遏考,但是真實(shí)請求下各路徑間使用/連接的慈鸠,因此,導(dǎo)入服務(wù)的接口需要通過一層轉(zhuǎn)發(fā)才能真正達(dá)到模擬請求的目的诈皿×质可以通過在mock文件夾下添加routes.js文件來配置路徑轉(zhuǎn)發(fā)方式。同時稽亏,使用json-server的另外一個中間件 rewriter 來對重新配置后的路徑進(jìn)行轉(zhuǎn)發(fā):
//導(dǎo)入routes已經(jīng)重寫路徑
const customersRouters = require('./routes’);
………
function serverRewrite() {
server.use(jsonServer.rewriter(customersRouters))
}
//routes.js路徑配置
…………
module.exports =
{
///////<-------------general interface--------->/////
"/api/*": "/$1",
"/*/*": "/$1_$2",
"/*/*/*": "/$1_$2_$3",
"/*/*/*/*": "/$1_$2_$3_$4",
/////////////////////////
}
經(jīng)過以上的配置,一個初步可用的server服務(wù)器已經(jīng)搭建起來了缕题,這時運(yùn)行npm run mock追迟,可以發(fā)現(xiàn)大部分原有的接口已經(jīng)運(yùn)行起來了官脓,大吉大利。
- fake數(shù)據(jù)
當(dāng)然铐拐,一個前后端分離的項(xiàng)目是要支持前端同學(xué)在脫離后端接口支持的情況下進(jìn)行獨(dú)立開發(fā)的。為了使項(xiàng)目能夠?qū)崿F(xiàn)敏捷開發(fā)浙值,我們還需要借助一些工具。mock.js是很常見的工具,但是對于json-server來說记罚,似乎最佳搭配還是faker.js。faker.js是個很強(qiáng)大的創(chuàng)建mock數(shù)據(jù)的工具壳嚎。雖然對中文的支持還不夠好桐智,但是由于本項(xiàng)目屬于國際化的開源項(xiàng)目,因此這個工具很適合此項(xiàng)目的開發(fā)烟馅。
首先安裝faker.js:
npm install faker
接著说庭,創(chuàng)建mck/fake文件夾用于管理faker.js生成的fake數(shù)據(jù)。本項(xiàng)目將fake數(shù)據(jù)按功能分成了兩部分郑趁。
第一部分是fake數(shù)據(jù)生成功能刊驴。
const faker = require("faker");
const _ = require("lodash");
faker.locale = "en";
module.exports = {
customer: _.times(20, function (n) {
return {
id: n,
name: faker.name.findName(),
phone: faker.phone.phoneNumber(),
address: faker.address.streetAddress(),
avatar: faker.internet.avatar()
}
}),
language: { language: faker.random.locale() }
}
另一部分是fake對象生成功能。
const fakeData = require('./fakedata.js');
module.exports = {
//Mock json
'customer_info': fakeData.customer,
'alarm_formdata_multiple': fakeData.customer,
'home': fakeData.home,
'language': fakeData.language,
}
最后一步是將生成的fake數(shù)據(jù)導(dǎo)入到現(xiàn)有的server服務(wù)中來寡润。
// 引入fake數(shù)據(jù)對象捆憎,并合并入生成的數(shù)據(jù)對象中
const fakeoriginalData = require('./fake/mock.js'); //import datas created in fakedata.js
………..
files.forEach((filename) => {
…………….
if (isDir) {
fileDisplay(filedir);
}
Object.keys(fakeoriginalData).map(item => {
localJsonDb[item] = fakeoriginalData[item];
})
})
總言之,思路就是首先通過faker.js生成所需數(shù)據(jù)梭纹,然后將其封裝成符合規(guī)范的api對象躲惰,最后引入到server.js中,并將其和本地的json文件一同處理為router可識別的路徑栗柒。
通過上述三步操作礁扮,基礎(chǔ)操作部分已經(jīng)完成,一個前后端分離的json-server服務(wù)器基本搭建完成瞬沦,可以撒花了太伊。
自定義配置
之所有還有這一節(jié),是因?yàn)樵趯?shí)際使用的過程中逛钻,遇到了問題僚焦,即之前提到的優(yōu)化過程中出現(xiàn)的難點(diǎn)。所以需要在原有基礎(chǔ)上再進(jìn)行一些自定義的配置來解決曙痘。
第一個問題是自定義資源請求
json-server
可以自動讀取規(guī)范的資源請求路徑芳悲,但是,如果請求路徑中存在變量边坤,基礎(chǔ)的功能就不能滿足這個要求了名扛。比如這種類型的請求:
createServiceType: this.baseUrl + "/uui-lcm/customers/*_*/service-subscriptions/*+*",
getCustomerresourceVersion: this.baseUrl + "/uui-lcm/customers/*_*",
getServiceTypeResourceVersion: this.baseUrl + "/uui-lcm/customers/*_*/service-subscriptions/*+*",
路徑中的_和+代表變量。而上述路徑中茧痒,無論是中間還是尾部都存在變量肮韧,這種路徑無法被json-server自動檢測,需要手動配置。因此弄企,我們需要在routes中進(jìn)行配置:
//routes.js
"/uui-lcm/serviceNumByServiceType/:customer": "/CustomersColumn",
"/uui-lcm/customers/:customer": "/getCustomerresourceVersion”,
"/uui-lcm/customers/:customer/service-subscriptions/:id": "/getServiceTypeResourceVersion",
如上所示超燃,將特殊的路徑在routes中進(jìn)行配置,冒號后面是需要代理到的地址拘领。這樣就可以實(shí)現(xiàn)存在變量的api地址的資源轉(zhuǎn)發(fā)了意乓。
第二個問題是post put delete請求的轉(zhuǎn)換
項(xiàng)目中存在相同url不同請求方式的接口,且參數(shù)拼接方式相同约素,單純使用json-server無法滿足需求届良。比如:
customers: this.baseUrl + "/uui-lcm/customers", /* get */
deleteCustomer: this.baseUrl + "/uui-lcm/customers", /* delete */
createCustomer: this.baseUrl + "/uui-lcm/customers/", /* put */
……………
getAllCustomers() {
return this.http.get<any>(this.url.customers);
}
createCustomer(customer, createParams) {
let url = this.url.createCustomer + customer;
return this.http.put(url, createParams);
}
deleteSelectCustomer(paramsObj) {
let url = this.url.deleteCustomer;
let params = new HttpParams({ fromObject: paramsObj });
return this.http.delete(url, { params });
}
如上述所示,/uui-lcm/customers這個地址通過請求方式和參數(shù)的不同业汰,實(shí)現(xiàn)了不同的功能伙窃。json-server有特定的語法,對post样漆、put等請求有既定的返回?cái)?shù)據(jù)格式的規(guī)范为障。但是在本項(xiàng)目中,卻需要返回既有數(shù)據(jù)放祟,因此鳍怨,需要對數(shù)據(jù)請求接口進(jìn)行攔截,改造成需要的路徑跪妥,同時更改這些接口的請求方式鞋喇,來需要解決相同url但不同請求方式的問題:
const fs = require('fs');
const path = require('path');
const jsonServer = require('json-server');
const server = jsonServer.create();
const middlewares = jsonServer.defaults();
const customersRouters = require('./routes');
const baseUrl = "/usecaseui-server/v1";
…………
server.post(`${baseUrl}/*`, (req, res, next) => {
const prefix = req.url.replace(baseUrl, "");
req.url = `${baseUrl}/POST${prefix}`;
req.method = 'GET';
next();
})
server.put(`${baseUrl}/*`, (req, res, next) => {
const prefix = req.url.replace(baseUrl, "");
req.url = `${baseUrl}/PUT${prefix}`;
req.method = 'GET';
next();
})
server.delete(`${baseUrl}/*`, (req, res, next) => {
const prefix = req.url.replace(baseUrl, "");
req.url = `${baseUrl}/DELETE${prefix}`;
req.method = 'GET';
next();
})
……………
上述代碼使用了json-server來攔截不同類型的服務(wù),攔截之后改變了請求地址和請求方式眉撵。通過在地址中添加PUT侦香、DELETE等字段,將這些請求與原有的請求隔離開纽疟,之后將請求方式改為GET罐韩,最后再在routes文件和json文件中進(jìn)行配置即可滿足需求。之所以這樣操作污朽,是因?yàn)閷τ谇岸碎_發(fā)來說散吵,在項(xiàng)目的開發(fā)階段,需要關(guān)注的只有請求參數(shù)和返回結(jié)果蟆肆,換句話說矾睦,在這個階段,無論前后端采用什么樣的請求方式炎功,都與前端關(guān)系不大枚冗,前端只需要知道是向什么接口發(fā)了什么參數(shù)并返回了什么結(jié)果就可以了,因此蛇损,這里將其他類型的請求全部轉(zhuǎn)換為GET就是基于這個原因官紫。
//routes.js
"/PUT/uui-lcm/customers/:name/service-subscriptions/:id": "/PUT_uui-lcm_customers_service-subscriptions”,
"/DELETE/uui-lcm/customers/:customer/service-subscriptions/:id": "/DELETE_uui-lcm_customers_service-subscriptions",
如此這般肛宋,就解決了項(xiàng)目在mock數(shù)據(jù)過程中的全部問題州藕,一個比較完整的Mock數(shù)據(jù)系統(tǒng)也搭建完成了束世。自此,可以徹底摒棄之前兩套代碼的開發(fā)模式床玻,通過一個簡單的npm run mock命令來啟動本地mock開發(fā)了毁涉。
最后上一遍server.js的完整代碼:
const jsonServer = require('json-server');
const server = jsonServer.create();
const middlewares = jsonServer.defaults();
const customersRouters = require('./routes');
const baseUrl = "/usecaseui-server/v1";
// Set default middlewares (logger, static, cors and no-cache)
server.use(middlewares);
// Get mock data
const fs = require('fs');
const path = require('path');
let localJsonDb = {}; //import mock datas
const fakeoriginalData = require('./fake/mock.js'); //import datas created in fakedata.js
const mockFolder = './src/app/mock/json'; //mock json path folder
const filePath = path.resolve(mockFolder);
fileDisplay(filePath);
function fileDisplay(filePath) {
let fileList = [];
// Return filelist on based of filePath
const files = fs.readdirSync(filePath);
files.forEach((filename) => {
// Get filename's absolute path
let filedir = path.join(filePath, filename);
// Get the file information according to the file path and return an fs.Stats object
fs.stat(filedir, (err, stats) => {
if (err) {
console.warn('Get files failed......');
} else {
let isFile = stats.isFile(); // files
let isDir = stats.isDirectory(); //files folder
if (isFile) {
fileList.push(path.basename(filedir, '.json'));
fileList.forEach(item => {
localJsonDb[item] = getjsonContent(item);
})
}
if (isDir) {
console.warn("=====> DO NOT support mock data in folder");
fileDisplay(filedir);
}
Object.keys(fakeoriginalData).map(item => {
localJsonDb[item] = fakeoriginalData[item];
})
}
})
})
setTimeout(() => {
serverRewrite();
runServer(localJsonDb);
}, 100)
}
function getjsonContent(path) {
let newpath = `./src/app/mock/json/${path}.json`;
let result = JSON.parse(fs.readFileSync(newpath));
return result;
}
//only multi router data needs jsonServer.rewriter
function serverRewrite() {
server.use(jsonServer.rewriter(customersRouters))
}
function runServer(db) {
server.use(jsonServer.router(db));
}
server.post(`${baseUrl}/*`, (req, res, next) => {
const prefix = req.url.replace(baseUrl, "");
req.url = `${baseUrl}/POST${prefix}`;
req.method = 'GET';
next();
})
server.put(`${baseUrl}/*`, (req, res, next) => {
const prefix = req.url.replace(baseUrl, "");
req.url = `${baseUrl}/PUT${prefix}`;
req.method = 'GET';
next();
})
server.delete(`${baseUrl}/*`, (req, res, next) => {
const prefix = req.url.replace(baseUrl, "");
req.url = `${baseUrl}/DELETE${prefix}`;
req.method = 'GET';
next();
})
server.listen(3002, () => {
console.log('Mock Server is successfully running on port 3002 ??')
});
拜拜。今晚回家吃雞??