前端mock完美解決方案實(shí)戰(zhàn)
寫在前面,本文閱讀需要一定Nodejs的相關(guān)知識(shí),因?yàn)闀?huì)擴(kuò)展webpack的相關(guān)功能,并且實(shí)現(xiàn)需要遵守一定約定和Ajax封裝。沉淀的腳手架也放到Github上供給同學(xué)參考React-Starter, 使用手冊(cè)還沒寫完善, 整體思路和React還是Vue無關(guān),如果對(duì)大家有收獲記得Star下。
它有這些功能:
開發(fā)打包有不同配置 eslint 驗(yàn)證 代碼風(fēng)格統(tǒng)一 commit 規(guī)范驗(yàn)證 接口mock 熱更新 異步組件
Mock功能介紹
mock功能和前端代碼解耦 一個(gè)接口支持多種mock情況 無需依賴另外的后端服務(wù)和第三方庫 能在network看到mock接口的請(qǐng)求且能區(qū)分 mock數(shù)據(jù)、接口配置和頁面在同一個(gè)目錄下 mock配置改變無需重啟前端dev 生產(chǎn)打包可以把mock數(shù)據(jù)注入到打包的js中走前端mock 對(duì)于后端已有的接口也能快速把Response數(shù)據(jù)轉(zhuǎn)化為mock數(shù)據(jù)
對(duì)于第7點(diǎn)的作用是后續(xù)項(xiàng)目開發(fā)完成,在完全沒有開發(fā)后端服務(wù)的情況下,也可以進(jìn)行演示。這對(duì)于一些ToB定制的項(xiàng)目來沉淀項(xiàng)目地圖(案例)很有作用。
對(duì)于第8點(diǎn)在開發(fā)環(huán)境后端服務(wù)經(jīng)常不穩(wěn)定下,不依賴后端也能做頁面開發(fā),核心是能實(shí)現(xiàn)一鍵生成mock數(shù)據(jù)。
配置解耦
耦合情況
webpack-dev后端測(cè)試環(huán)境變了需要改git跟蹤的代碼 dev和build的時(shí)候 需要改git跟蹤的代碼 開發(fā)的時(shí)候想這個(gè)接口mock 需要改git跟蹤的代碼 mockUrl ,mock?
如何解決
├──?config
│???├──?conf.json????????????????????????????????????#?git?不跟蹤
│???├──?config.js????????????????????????????????????#?git?不跟蹤
│???├──?config_default.js
│???├──?index.js
│???└──?webpack.config.js
├──?jsconfig.json
├──?mock.json????????????????????????????????????????????#?git?不跟蹤
//?config/index.js
const?_?=?require("lodash");
let?config?=?_.cloneDeep(require("./config_default"))
try?{
??const?envConfig?=?require( ./config )?//?eslint-disable-line
??config?=?_.merge(config,?envConfig);
}?catch?(e)?{
????//?
}
module.exports?=?config;
//?config/config_default.js
const?pkg?=?require("../package.json");
module.exports?=?{
??projectName:?pkg.name,
??version:?pkg.version,
??port:?8888,
??proxy:?{
????"/render-server/api/*":?{
??????target:?`http://192.168.1.8:8888`,
??????changeOrigin:?true,?//?支持跨域請(qǐng)求
??????secure:?true,?//?支持?https
????},
??},
??...
??conf:?{
????dev:?{
??????title:?"前端模板",
??????pathPrefix:?"/react-starter",?//?統(tǒng)一前端路徑前綴
??????apiPrefix:?"/api/react-starter",?//
??????debug:?true,
??????delay:?500,????//?mock數(shù)據(jù)模擬延遲
??????mock:?{
????????//?"global.login":?"success",
????????//?"global.loginInfo":?"success",
??????}
????},
????build:?{
??????title:?"前端模板",
??????pathPrefix:?"/react-starter",
??????apiPrefix:?"/api/react-starter",
??????debug:?false,
??????mock:?{}
????}
??}
};
//?package.json
{
??"name":?"react-starter",
??"version":?"1.0.0",
??"description":?"react前端開發(fā)腳手架",
??"main":?"index.js",
??"scripts":?{
????"start":?"webpack-dev-server?--config? ./config/webpack.config.js ?--open?--mode?development",
????"build":?"cross-env?BUILD_ENV=VERSION?webpack?--config? ./config/webpack.config.js ?--mode?production?--progress?--display-modules?&&?npm?run?tar",
????"build-mock":?"node?./scripts/build-mock.js?"
??},
??...
}
//?config/webpack.config.js
const?config?=?require( . )
const?env?=?process.env.BUILD_ENV??? build ?:? dev
const?confJson?=?env?===? build ???config.conf.build?:?config.conf.dev
fs.writeFileSync(path.join(__dirname,? ./conf.json ),??JSON.stringify(confGlobal,?null,? ))
引用配置
//?src/common/utils.jsx
import?conf?from? @/config/conf.json
export?const?config?=?Object.assign(conf,?window.conf)
import?{config}?from? @src/common/utils
class?App?extends?Component?{
??render()?{
????return?(
??????history={history}>
????????
??????????${config.pathPrefix}`}?component={Home}?/>
??????????"/"?to={`${config.pathPrefix}`}?/>
????????
??????
????)
??}
}
ReactDOM.render(
?????,
??document.getElementById( root ),
)
Mock實(shí)現(xiàn)
效果
//?src/pages/login/login-io.js
import?{createIo}?from? @src/io
const?apis?=?{
??//?登錄
??login:?{
????method:? POST ,
????url:? /auth/login ,
??},
??//?登出
??logout:?{
????method:? POST ,
????url:? /auth/logout ,
??},
}
export?default?createIo(apis,? login )?//?對(duì)應(yīng)login-mock.json
//?src/pages/login/login-mock.json
{
????"login":?{
????????"failed":?{
????????????"success":?false,
????????????"code":?"ERROR_PASS_ERROR",
????????????"content":?null,
????????????"message":?"賬號(hào)或密碼錯(cuò)誤!"
????????},
????????"success":?{
????????????"success":?true,
????????????"code":?0,
????????????"content":?{
????????????????"name":?"admin",
????????????????"nickname":?"超級(jí)管理員",
????????????????"permission":?15
????????????},
????????????"message":?""
????????}
????},
????"logout":?{
????????"success":?{
????????????"success":?true,
????????????"code":?0,
????????????"content":?null,
????????????"message":?""
????????}
????}
}
//?config/conf.json
{
????"title":?"前端后臺(tái)模板",
????"pathPrefix":?"/react-starter",
????"apiPrefix":?"/api/react-starter",
????"debug":?true,
????"delay":?500,
????"mock":?{
????????"login.logout":?"success"
????}
}
思路
??proxy:?{
????"/api/react-starter/*":?{
??????target:?`http://192.168.90.68:8888`,
??????changeOrigin:?true,
??????secure:?true,
??????//?onError:?(),
??????//?onProxyRes,
??????//?onProxyReq??
????},
??},
option.onError?出現(xiàn)錯(cuò)誤 option.onProxyRes?后端響應(yīng)后 option.onProxyReq?請(qǐng)求轉(zhuǎn)發(fā)前 option.onProxyReqWs option.onOpen option.onClose
onProxyReq
mock-key?來匹配mock文件如login-mock.json的內(nèi)容, 如login mock-method?來匹配對(duì)應(yīng)文件內(nèi)容的方法項(xiàng) 如logout
onProxyRes
├──?api-cache????????????????????????????????????#?git?不跟蹤
│???├──?login.login.json
│???└──?login.logout.json
//?api-cache/global.logout.json
{
????"success":?{
????????"date":?"2020-11-17?05:32:17",
????????"method":?"POST",
????????"path":?"/render-server/api/logout",
????????"url":?"/render-server/api/logout",
????????"resHeader":?{
????????????"content-type":?"application/json;?charset=utf-8",
??????...
????????},
????????"reqHeader":?{
????????????"host":?"127.0.0.1:8888",
????????????"mock-key":?"login",
????????????"mock-method":?"logout"
??????...
????????},
????????"query":?{},
????????"reqBody":?{},
????????"resBody":?{
????????????"success":?true,
????????????"code":?0,
????????????"content":?null,
????????????"message":?""
????????}
????}
}
前端接口封裝
使用
//?src/pages/login/login-io.js
import?{createIo}?from? @src/io
const?apis?=?{
??//?登錄
??login:?{
????method:? POST ,
????url:? /auth/login ,
??},
??//?登出
??logout:?{
????method:? POST ,
????url:? /auth/logout ,
??},
}
export?default?createIo(apis,? login )?//?login注冊(cè)到header的mock-key
//?src/pages/login/login-store.js
import?{observable,?action,?runInAction}?from? mobx
import?io?from? ./login-io
//?import?{config,?log}?from? ./utils
export?class?LoginStore?{
??//?用戶信息
??@observable?userInfo
??//?登陸操作
[email protected]
??async?login(params)?{
????const?{success,?content}?=?await?io.login(params)
????if?(!success)?return
????runInAction(()?=>?{
??????this.userInfo?=?content
????})
??}
}
export?default?LoginStore
createIo 請(qǐng)求封裝
//?src/io/index.jsx
import?{message,?Modal}?from? antd
import?{config,?log,?history}?from? @src/common/utils
import?{ERROR_CODE}?from? @src/common/constant
import?creatRequest?from? @src/common/request
let?mockData?=?{}
try?{
??//?eslint-disable-next-line?global-require,?import/no-unresolved
??mockData?=?require( @/mock.json )
}?catch?(e)?{
??log(e)
}
let?reloginFlag?=?false
//?創(chuàng)建一個(gè)request
export?const?request?=?creatRequest({
??//?自定義的請(qǐng)求頭
??headers:?{ Content-Type :? application/json },
??//?配置默認(rèn)返回?cái)?shù)據(jù)處理
??action:?(data)?=>?{
????//?統(tǒng)一處理未登錄的彈框
????if?(data.success?===?false?&&?data.code?===?ERROR_CODE.UN_LOGIN?&&?!reloginFlag)?{
??????reloginFlag?=?true
??????//?TODO?這里可能統(tǒng)一跳轉(zhuǎn)到?也可以是彈窗點(diǎn)擊跳轉(zhuǎn)
??????Modal.confirm({
????????title:? 重新登錄 ,
????????content:? ,
????????onOk:?()?=>?{
??????????//?location.reload()
??????????history.push(`${config.pathPrefix}/login?redirect=${window.location.pathname}${window.location.search}`)
??????????reloginFlag?=?false
????????},
??????})
????}
??},
??//?是否錯(cuò)誤顯示message
??showError:?true,
??message,
??//?是否以拋出異常的方式?默認(rèn)false?{success:?boolean判斷}
??throwError:?false,
??//?mock?數(shù)據(jù)請(qǐng)求的等待時(shí)間
??delay:?config.delay,
??//?日志打印
??log,
})
//?標(biāo)識(shí)是否是簡(jiǎn)單傳參數(shù),?值為true標(biāo)識(shí)復(fù)雜封裝
export?const?rejectToData?=?Symbol( flag )
/**
?*?創(chuàng)建請(qǐng)求IO的封裝
?*?@param?ioContent?{any?{?url:?string?method?:?string?mock?:?any?apiPrefix?:?string}}
??}
?*?@param?name?mock數(shù)據(jù)的對(duì)應(yīng)文件去除-mock.json后的
?*/
export?const?createIo?=?(ioContent,?name?=? )?=>?{
??const?content?=?{}
??Object.keys(ioContent).forEach((key)?=>?{
????/**
?????*?@param?{baseURL?:?string,?rejectToData?:?boolean,??params?:?{},?query?:?{},?timeout?:?number,?action?(data:?any):?any,?headers?:?{},??body?:?any,?data?:?any,???mock?:?any}
?????*?@returns?{message,?content,?code,success:?boolean}
?????*/
????content[key]?=?async?(options?=?{})?=>?{
??????//?這里判斷簡(jiǎn)單請(qǐng)求封裝?rejectToData=true?表示復(fù)雜封裝
??????if?(!options[rejectToData])?{
????????options?=?{
??????????data:?options,
????????}
??????}
??????delete?options[rejectToData]
??????if?(
????????config.debug?===?false?&&
????????name?&&
????????config.mock?&&
????????config.mock[`${name}.${key}`]?&&
????????mockData[name]?&&
????????mockData[name][key]
??????)?{?//?判斷是否是生產(chǎn)打包?mock注入到代碼中
????????ioContent[key].mock?=?JSON.parse(JSON.stringify(mockData[name][key][config.mock[`${name}.${key}`]]))
??????}?else?if?(name?&&?config.debug?===?true)?{?//注入?mock請(qǐng)求頭
????????if?(options.headers)?{
??????????options.headers[ mock-key ]?=?name
??????????options.headers[ mock-method ]?=?key
????????}?else?{
??????????options.headers?=?{ mock-key :?name,? mock-method :?key}
????????}
??????}
??????const?option?=?{...ioContent[key],?...options}
??????option.url?=?((option.apiPrefix???option.apiPrefix?:?config.apiPrefix)?||? )?+?option.url
??????return?request(option)
????}
??})
??return?content
}
request封裝axios
import?axios?from? axios
//?配置接口參數(shù)
//?declare?interface?Options?{
//???url:?string
//???baseURL?:?string
//???//?默認(rèn)GET
//???method?:?Method
//???//?標(biāo)識(shí)是否注入到data參數(shù)
//???rejectToData?:?boolean
//???//?是否直接彈出message?默認(rèn)是
//???showError?:?boolean
//???//?指定?回調(diào)操作?默認(rèn)登錄處理
//???action?(data:?any):?any
//???headers?:?{
//?????[index:?string]:?string
//???}
//???timeout?:?number
//???//?指定路由參數(shù)
//???params?:?{
//?????[index:?string]:?string
//???}
//???//?指定url參數(shù)
//???query?:?any
//???//?指定body?參數(shù)
//???body?:?any
//???//?混合處理?Get到url,?delete?post?到body,?也替換路由參數(shù)?在createIo封裝
//???data?:?any
//???mock?:?any
//?}
//?ajax?請(qǐng)求的統(tǒng)一封裝
//?TODO?1.?對(duì)jsonp請(qǐng)求的封裝?2.?重復(fù)請(qǐng)求
/**
?*?返回ajax?請(qǐng)求的統(tǒng)一封裝
?*?@param?Object?option?請(qǐng)求配置
?*?@param?{boolean}?opts.showError?是否錯(cuò)誤調(diào)用message的error方法
?*?@param?{object}?opts.message??包含?.error方法?showError?true的時(shí)候調(diào)用
?*?@param?{boolean}?opts.throwError?是否出錯(cuò)拋出異常
?*?@param?{function}?opts.action??包含?自定義默認(rèn)處理?比如未登錄的處理
?*?@param?{object}?opts.headers??請(qǐng)求頭默認(rèn)content-type:?application/json
?*?@param?{number}?opts.timeout??超時(shí)?默認(rèn)60秒
?*?@param?{number}?opts.delay???mock請(qǐng)求延遲
?*?@returns?{function}?{params,?url,?headers,?query,?data,?mock}?data混合處理?Get到url,?delete?post?到body,?也替換路由參數(shù)?在createIo封裝
?*/
export?default?function?request(option?=?{})?{
??return?async?(optionData)?=>?{
????const?options?=?{
??????url:? ,
??????method:? GET ,
??????showError:?option.showError?!==?false,
??????timeout:?option.timeout?||?60?*?1000,
??????action:?option.action,
??????...optionData,
??????headers:?{ X-Requested-With :? XMLHttpRequest ,?...option.headers,?...optionData.headers},
????}
????//?簡(jiǎn)單請(qǐng)求處理
????if?(options.data)?{
??????if?(typeof?options.data?===? object )?{
????????Object.keys(options.data).forEach((key)?=>?{
??????????if?(key[0]?===? : ?&&?options.data)?{
????????????options.url?=?options.url.replace(key,?encodeURIComponent(options.data[key]))
????????????delete?options.data[key]
??????????}
????????})
??????}
??????if?((options.method?||? ).toLowerCase()?===? get ?||?(options.method?||? ).toLowerCase()?===? head )?{
????????options.query?=?Object.assign(options.data,?options.query)
??????}?else?{
????????options.body?=?Object.assign(options.data,?options.body)
??????}
????}
????//?路由參數(shù)處理
????if?(typeof?options.params?===? object )?{
??????Object.keys(options.params).forEach((key)?=>?{
????????if?(key[0]?===? : ?&&?options.params)?{
??????????options.url?=?options.url.replace(key,?encodeURIComponent(options.params[key]))
????????}
??????})
????}
????//?query?參數(shù)處理
????if?(options.query)?{
??????const?paramsArray?=?[]
??????Object.keys(options.query).forEach((key)?=>?{
????????if?(options.query[key]?!==?undefined)?{
??????????paramsArray.push(`${key}=${encodeURIComponent(options.query[key])}`)
????????}
??????})
??????if?(paramsArray.length?>?0?&&?options.url.search(/?/)?===?-1)?{
????????options.url?+=?`?${paramsArray.join( & )}`
??????}?else?if?(paramsArray.length?>?0)?{
????????options.url?+=?`&${paramsArray.join( & )}`
??????}
????}
????if?(option.log)?{
??????option.log( request??options ,?options.method,?options.url)
??????option.log(options)
????}
????if?(options.headers[ Content-Type ]?===? application/json ?&&?options.body?&&?typeof?options.body?!==? string )?{
??????options.body?=?JSON.stringify(options.body)
????}
????let?retData?=?{success:?false}
????//?mock?處理
????if?(options.mock)?{
??????retData?=?await?new?Promise((resolve)?=>
????????setTimeout(()?=>?{
??????????resolve(options.mock)
????????},?option.delay?||?500),
??????)
????}?else?{
??????try?{
????????const?opts?=?{
??????????url:?options.url,
??????????baseURL:?options.baseURL,
??????????params:?options.params,
??????????method:?options.method,
??????????headers:?options.headers,
??????????data:?options.body,
??????????timeout:?options.timeout,
????????}
????????const?{data}?=?await?axios(opts)
????????retData?=?data
??????}?catch?(err)?{
????????retData.success?=?false
????????retData.message?=?err.message
????????if?(err.response)?{
??????????retData.status?=?err.response.status
??????????retData.content?=?err.response.data
??????????retData.message?=?`瀏覽器請(qǐng)求非正常返回:?狀態(tài)碼?${retData.status}`
????????}
??????}
????}
????//?自動(dòng)處理錯(cuò)誤消息
????if?(options.showError?&&?retData.success?===?false?&&?retData.message?&&?option.message)?{
??????option.message.error(retData.message)
????}
????//?處理Action
????if?(options.action)?{
??????options.action(retData)
????}
????if?(option.log?&&?options.mock)?{
??????option.log( request?response: ,?JSON.stringify(retData))
????}
????if?(option.throwError?&&?!retData.success)?{
??????const?err?=?new?Error(retData.message)
??????err.code?=?retData.code
??????err.content?=?retData.content
??????err.status?=?retData.status
??????throw?err
????}
????return?retData
??}
}
一鍵生成mock
#?"build-mock":?"node?./scripts/build-mock.js"
#?所有:
npm?run?build-mock?mockAll?
#?單個(gè)mock文件:
npm?run?build-mock?login
#?單個(gè)mock接口:
npm?run?build-mock?login.logout
#?復(fù)雜?
npm?run?build-mock?login.logout?user
mock.json文件生成
根據(jù)配置動(dòng)態(tài)生成mock.json的內(nèi)容 監(jiān)聽config文件夾 判斷關(guān)于mock的配置項(xiàng)是否有改變重新生成mock.json
//?scripts/webpack-init.js?在wenpack配置文件中初始化
const?path?=?require( path )
const?fs?=?require( fs )
const?{syncWalkDir}?=?require( ./util )
let?confGlobal?=?{}
let?mockJsonData?=?{}
exports.getConf?=?()?=>?confGlobal
exports.getMockJson?=()?=>?mockJsonData
/**
?*?初始化項(xiàng)目的配置?動(dòng)態(tài)生成mock.json和config/conf.json
?*?@param?{string}?env??dev|build
?*/
?exports.init?=?(env?=?process.env.BUILD_ENV??? build ?:? dev )?=>?{
???
??delete?require.cache[require.resolve( ../config )]
??const?config??=?require( ../config )
??const?confJson?=?env?===? build ???config.conf.build?:?config.conf.dev
??confGlobal?=?confJson
??//?1.根據(jù)環(huán)境變量來生成
??fs.writeFileSync(path.join(__dirname,? ../config/conf.json ),??JSON.stringify(confGlobal,?null,? ))
??buildMock(confJson)
?}
?
?//?生成mock文件數(shù)據(jù)
?const?buildMock?=?(conf)?=>?{
??//?2.動(dòng)態(tài)生成mock數(shù)據(jù)?讀取src文件夾下面所有以?-mock.json結(jié)尾的文件?存儲(chǔ)到io/index.json文件當(dāng)中
??let?mockJson?=?{}
??const?mockFiles?=?syncWalkDir(path.join(__dirname,? ../src ),?(file)?=>?/-mock.json$/.test(file))
??console.log( build?mocks:?->>>>>>>>>>>>>>>>>>>>>>> )
??mockFiles.forEach((filePath)?=>?{
????const?p?=?path.parse(filePath)
????const?mockKey?=?p.name.substr(0,?p.name.length?-?5)
????console.log(mockKey,?filePath)
????if?(mockJson[mockKey])?{
??????console.error(`有相同的mock文件名稱${p.name}?存在`,?filePath)
????}
????delete?require.cache[require.resolve(filePath)]
????mockJson[mockKey]?=?require(filePath)
??})
??//?如果是打包環(huán)境,?最小化mock資源數(shù)據(jù)
??const?mockMap?=?conf.mock?||?{}
??const?buildMockJson?=?{}
??Object.keys(mockMap).forEach((key)?=>?{
????const?[name,?method]?=?key.split( . )
????if?(mockJson[name][method]?&&?mockJson[name][method][mockMap[key]])?{
??????if?(!buildMockJson[name])?buildMockJson[name]?=?{}
??????if?(!buildMockJson[name][method])?buildMockJson[name][method]?=?{}
??????buildMockJson[name][method][mockMap[key]]?=?mockJson[name][method][mockMap[key]]
????}
??})
??mockJsonData?=?buildMockJson
??fs.writeFileSync(path.join(__dirname,? ../mock.json ),?JSON.stringify(buildMockJson,?null,? ))
?}
?
?//?監(jiān)聽配置文件目錄下的config.js和config_default.js
const?confPath?=?path.join(__dirname,? ../config )
if?((env?=?process.env.BUILD_ENV??? build ?:? dev )?===? dev )?{
??fs.watch(confPath,?async?(event,?filename)?=>?{
????if?(filename?===? config.js ?||?filename?===? config_default.js )?{
??????delete?require.cache[path.join(confPath,?filename)]
??????delete?require.cache[require.resolve( ../config )]
??????const?config??=?require( ../config )
??????//?console.log( config ,?JSON.stringify(config))
??????const?env?=?process.env.BUILD_ENV??? build ?:? dev
??????const?confJson?=?env?===? build ???config.conf.build?:?config.conf.dev
??????if?(JSON.stringify(confJson)?!==?JSON.stringify(confGlobal))?{
????????this.init()
??????}
????}
??});
}
接口代理處理
onProxyReq和onProxyRes
//?scripts/api-proxy-cache?
const?fs?=?require( fs )
const?path?=?require( path )
const?moment?=?require( moment )
const?{getConf,?getMockJson}?=?require( ./webpack-init )
const?API_CACHE_DIR?=?path.join(__dirname,? ../api-cache )
const?{jsonParse,?getBody}?=?require( ./util )
fs.mkdirSync(API_CACHE_DIR,{recursive:?true})
module.exports?=?{
??//?代理前處理
??onProxyReq:?async?(_,?req,?res)?=>?{
????req.reqBody?=?await?getBody(req)
????const?{ mock-method :?mockMethod,? mock-key :?mockKey}?=?req.headers
????//?eslint-disable-next-line?no-console
????console.log(`Ajax?請(qǐng)求:?${mockKey}.${mockMethod}`,req.method,?req.url)
????//?eslint-disable-next-line?no-console
????req.reqBody?&&?console.log(JSON.stringify(req.reqBody,?null,? ))
????if?(mockKey?&&?mockMethod)?{
??????req.mockKey?=?mockKey
??????req.mockMethod?=?mockMethod
??????const?conf?=?getConf()
??????const?mockJson?=?getMockJson()
??????if?(conf.mock?&&?conf.mock[`${mockKey}.${mockMethod}`]?&&?mockJson[mockKey]?&&?mockJson[mockKey][mockMethod])?{
????????//?eslint-disable-next-line?no-console
????????console.log(`use?mock?data?${mockKey}.${mockMethod}:`,?conf.mock[`${mockKey}.${mockMethod}`],? color:?green )
????????res.mock?=?true
????????res.append( isMock , yes )
????????res.send(mockJson[mockKey][mockMethod][conf.mock[`${mockKey}.${mockMethod}`]])
??????}
?????
????}
??},
??//?響應(yīng)緩存接口
??onProxyRes:?async?(res,?req)?=>?{
????const?{method,?url,?query,?path:?reqPath,?mockKey,?mockMethod}?=?req
????
????if?(mockKey?&&?mockMethod?&&?res.statusCode?===?200)?{
??????
??????let?resBody?=?await?getBody(res)
??????resBody?=?jsonParse(resBody)
??????const?filePath?=?path.join(API_CACHE_DIR,?`${mockKey}.${mockMethod}.json`)
??????let??data?=?{}
??????if?(fs.existsSync(filePath))?{
????????data?=?jsonParse(fs.readFileSync(filePath).toString())
??????}
??????const?cacheObj?=?{
????????date:?moment().format( YYYY-MM-DD?hh:mm:ss ),
????????method,
????????path:?reqPath,
????????url,
????????resHeader:?res.headers,
????????reqHeader:?req.headers,
????????query,
????????reqBody:?await?jsonParse(req.reqBody),
????????resBody:?resBody
??????}
??????if?(resBody.success?===?false)?{
????????data.failed?=?cacheObj
??????}?else?{
????????data.success?=?cacheObj
??????}
??????//?eslint-disable-next-line?no-console
??????fs.writeFile(filePath,?JSON.stringify(data, ,? ),?(err)?=>?{?err?&&?console.log( writeFile ,?err)})
????}
??},
??//?后端服務(wù)沒啟的異常處理
??onError(err,?req,?res)?{
????setTimeout(()?=>?{
?????if?(!res.mock)?{
???????res.writeHead(500,?{
????????? Content-Type :? text/plain ,
???????});
???????res.end( Something?went?wrong.?And?we?are?reporting?a?custom?error?message. );
?????}
???},?10)
??}
}
webpack配置
const?config?=?require( . )
//?config/webpack.config.js
const?{init}?=?require( ../scripts/webpack-init );
init();
//?接口請(qǐng)求本地緩存
const?apiProxyCache?=?require( ../scripts/api-proxy-cache )
for(let?key?in?config.proxy)?{
??config.proxy[key]?=?Object.assign(config.proxy[key],?apiProxyCache);
}
const?webpackConf?=?{
??devServer:?{
????contentBase:?path.join(__dirname,? .. ),?//?本地服務(wù)器所加載的頁面所在的目錄
????inline:?true,
????port:?config.port,
????publicPath:? / ,
????historyApiFallback:?{
??????disableDotRule:?true,
??????//?指明哪些路徑映射到哪個(gè)html
??????rewrites:?config.rewrites,
????},
????host:? 127.0.0.1 ,
????hot:?true,
????proxy:?config.proxy,
??},
}
總結(jié)
一鍵部署 npm run deploy 支持集群部署配置 是一個(gè)文件服務(wù) 是一個(gè)靜態(tài)資源服務(wù) 在線可視化部署前端項(xiàng)目 配置熱更新 在線Postman及接口文檔 支持前端路由渲染, 支持模板 接口代理及路徑替換 Web安全支持 Ajax請(qǐng)求驗(yàn)證,Referer 校驗(yàn) 支持插件開發(fā)和在線配置 可實(shí)現(xiàn):前端模板參數(shù)注入、請(qǐng)求頭注入、IP白名單、接口mock、會(huì)話、第三方登陸等等
評(píng)論
圖片
表情
