<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端 mock 完美解決方案實(shí)戰(zhàn)

          共 21893字,需瀏覽 44分鐘

           ·

          2020-12-11 18:41

          關(guān)注?程序員成長指北,回復(fù)“1

          加入我們一起學(xué)習(xí),天天進(jì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è)真正站在前端角度上讓我覺得強(qiáng)大和易用的。下面就說下我期望的前端mock要有哪些功能:
          1. mock功能和前端代碼解耦
          2. 一個(gè)接口支持多種mock情況
          3. 無需依賴另外的后端服務(wù)和第三方庫
          4. 能在network看到mock接口的請(qǐng)求且能區(qū)分
          5. mock數(shù)據(jù)、接口配置和頁面在同一個(gè)目錄下
          6. mock配置改變無需重啟前端dev
          7. 生產(chǎn)打包可以把mock數(shù)據(jù)注入到打包的js中走前端mock
          8. 對(duì)于后端已有的接口也能快速把Response數(shù)據(jù)轉(zhuǎn)化為mock數(shù)據(jù)
          上面的這些功能我講其中幾點(diǎn)的作用:
          對(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ù)。

          配置解耦

          耦合情況

          什么是前端配置解耦,首先讓我們看下平時(shí)配置耦合情況有哪些:
          • webpack-dev后端測(cè)試環(huán)境變了需要改git跟蹤的代碼
          • dev和build的時(shí)候 需要改git跟蹤的代碼
          • 開發(fā)的時(shí)候想這個(gè)接口mock 需要改git跟蹤的代碼 mockUrl ,mock?

          如何解決

          前端依賴的配置解耦的思路是配置文件conf.json是在dev或build的時(shí)候動(dòng)態(tài)生成的,然后該文件在前端項(xiàng)目引用:
          ├── config│ ├── conf.json # git 不跟蹤│ ├── config.js # git 不跟蹤│ ├── config_default.js│ ├── index.js│ └── webpack.config.js├── jsconfig.json├── mock.json # git 不跟蹤
          webpack配置文件引入js的配置,生成conf.json
          // config/index.jsconst _ = 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;
          默認(rèn)使用config_default.js 的內(nèi)容,如果有config.js 則覆蓋,開發(fā)的時(shí)候復(fù)制config_default.js 為config.js 后續(xù)相關(guān)配置可以修改config.js即可。
          // config/config_default.jsconst 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: {} } }};
          在開發(fā)或打包的時(shí)候根據(jù)環(huán)境變量使用conf.dev或conf.build 生成conf.json文件內(nèi)容。
          // 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 "  },  ...}
          指定webpack路徑是./config/webpack.config.js
          然后在webpack.config.js中引入配置并生成conf.json文件
          // config/webpack.config.jsconst config = require('.')const env = process.env.BUILD_ENV ? 'build' : 'dev'const confJson = env === 'build' ? config.conf.build : config.conf.devfs.writeFileSync(path.join(__dirname, './conf.json'), JSON.stringify(confGlobal, null, '\t'))

          引用配置

          在src/common/utils.jsx文件中暴露出配置項(xiàng),配置也可以通過window.conf來覆蓋
          // src/common/utils.jsximport conf from '@/config/conf.json'export const config = Object.assign(conf, window.conf)
          然后就可以在各個(gè)頁面中使用
          import {config} from '@src/common/utils'class App extends Component { render() { return ( ) }}ReactDOM.render( , document.getElementById('root'),)

          Mock實(shí)現(xiàn)

          效果

          為了實(shí)現(xiàn)我們想要的mock的相關(guān)功能,首先是否開啟mock的配置解耦可以通過上面說的方式來實(shí)現(xiàn),我們一般在頁面異步請(qǐng)求的時(shí)候都會(huì)目錄定義一個(gè)io.js的文件, 里面定義了當(dāng)前頁面需要調(diào)用的相關(guān)后端接口:
          // src/pages/login/login-io.jsimport {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
          上面定義了登錄和登出接口,我們希望對(duì)應(yīng)開啟的mock請(qǐng)求能使用當(dāng)前目錄下的login-mock.json文件的內(nèi)容。
          // 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": "" } }}
          在調(diào)用logout登出這個(gè)Ajax請(qǐng)求的時(shí)候且我們的conf.json中配置的是"login.logout": "success" 就返回login-mock.json中的login.success 的內(nèi)容,配置沒有匹配到就請(qǐng)求轉(zhuǎn)發(fā)到后端服務(wù)。
          // config/conf.json{ "title": "前端后臺(tái)模板", "pathPrefix": "/react-starter", "apiPrefix": "/api/react-starter", "debug": true, "delay": 500, "mock": { "login.logout": "success" }}
          這是我們最終要實(shí)現(xiàn)的效果,這里有一個(gè)約定:項(xiàng)目目錄下所有以-mock.jsom文件結(jié)尾的文件為mock文件,且文件名不能重復(fù)。

          思路

          在webpack配置項(xiàng)中devServer的proxy配置接口的轉(zhuǎn)發(fā)設(shè)置,接口轉(zhuǎn)發(fā)使用了功能強(qiáng)大的 http-proxy-middleware 軟件包, 我們約定proxy的配置格式是:
          proxy: { "/api/react-starter/*": { target: `http://192.168.90.68:8888`, changeOrigin: true, secure: true, // onError: (), // onProxyRes, // onProxyReq }, },
          它有幾個(gè)事件觸發(fā)的配置:
          • option.onError?出現(xiàn)錯(cuò)誤

          • option.onProxyRes?后端響應(yīng)后

          • option.onProxyReq?請(qǐng)求轉(zhuǎn)發(fā)前

          • option.onProxyReqWs

          • option.onOpen

          • option.onClose

          所以我們需要定制這幾個(gè)事情的處理,主要是請(qǐng)求轉(zhuǎn)發(fā)前和請(qǐng)求處理后
          onProxyReq
          想在這里來實(shí)現(xiàn)mock的處理, 如果匹配到了mock數(shù)據(jù)我們就直接響應(yīng),就不轉(zhuǎn)發(fā)請(qǐng)求到后端。怎么做呢:思路是依賴請(qǐng)求頭,dev情況下前端在調(diào)用的時(shí)候能否注入約定好的請(qǐng)求頭 告訴我需要尋找哪個(gè)mock數(shù)據(jù)項(xiàng), 我們約定Header:
          • mock-key 來匹配mock文件如login-mock.json的內(nèi)容, 如login

          • mock-method 來匹配對(duì)應(yīng)文件內(nèi)容的方法項(xiàng) 如logout

          然后conf.json中mock配置尋找到具體的響應(yīng)項(xiàng)目如:"login.logout": "success/failed"的內(nèi)容
          onProxyRes
          如果調(diào)用了真實(shí)的后端請(qǐng)求,就把請(qǐng)求的響應(yīng)數(shù)據(jù)緩存下來,緩存到api-cache目錄下文件格式mock-key.mock-method.json
          ├── 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": "" } }}
          這樣做的目的是為了后續(xù)實(shí)現(xiàn)一鍵生成mock文件。

          前端接口封裝

          使用
          上面我們看到定義了接口的io配置:
          // src/pages/login/login-io.jsimport {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
          我們?cè)趕tore中使用
          // 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 // 登陸操作 @action.bound async login(params) { const {success, content} = await io.login(params) if (!success) return runInAction(() => { this.userInfo = content }) }}export default LoginStore
          通過 createIo(apis, 'login') 的封裝在調(diào)用的時(shí)候就可以非常簡單的來傳遞請(qǐng)求參數(shù),簡單模式下會(huì)判斷參數(shù)是到body還是到query中。復(fù)雜的也可以支持比如可以header,query, body等這里不演示了。
          createIo 請(qǐng)求封裝
          這個(gè)是前端接口封裝的關(guān)鍵地方,也是mock請(qǐng)求頭注入的地方。
          // src/io/index.jsximport {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è)requestexport 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í)是否是簡單傳參數(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 = {}) => { // 這里判斷簡單請(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}
          這里對(duì)request也做進(jìn)一步的封裝,配置項(xiàng)設(shè)置了一些默認(rèn)的處理設(shè)置。比如通用的請(qǐng)求響應(yīng)失敗的是否有一個(gè)message, 未登錄的情況是否有一個(gè)彈窗提示點(diǎn)擊跳轉(zhuǎn)登陸頁。如果你想定義多個(gè)通用處理可以再創(chuàng)建一個(gè)request2和createIo2。
          request封裝axios
          是基于axios的二次封裝, 并不是非常通用,主要是在約定的請(qǐng)求失敗和成功的處理有定制,如果需要可以自己修改使用。
          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}, } // 簡單請(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
          根據(jù)api-cache下的接口緩存和定義的xxx-mock.json文件來生成。
          # "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
          具體代碼參考build-mock.js
          mock.json文件生成
          為了在build打包的時(shí)候把mock數(shù)據(jù)注入到前端代碼中去,使得mock.json文件內(nèi)容盡可能的小,會(huì)根據(jù)conf.json的配置項(xiàng)來動(dòng)態(tài)生成mock.json的內(nèi)容,如果build里面沒有開啟mock項(xiàng),內(nèi)容就會(huì)是一個(gè)空json數(shù)據(jù)。當(dāng)然后端接口代理處理內(nèi)存中也映射了一份該mock.json的內(nèi)容。這里需要做幾個(gè)事情:
          • 根據(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 = () => confGlobalexports.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, '\t')) 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, '\t')) }
          // 監(jiān)聽配置文件目錄下的config.js和config_default.jsconst 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
          實(shí)現(xiàn)上面思路里面說的onProxyReq和onProxyRes 響應(yīng)處理
          util.js
          // 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, '\t')) 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,'', '\t'), (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配置
          在webpack配置中引入使用
          const config = require('.')// config/webpack.config.jsconst {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é)

          mock做好其實(shí)在我們前端實(shí)際中還是很有必要的,做過的項(xiàng)目如果后端被鏟除了想要回憶就可以使用mock讓項(xiàng)目跑起來,可以尋找一些實(shí)現(xiàn)的效果來進(jìn)行代碼復(fù)用。
          當(dāng)前介紹的mock流程實(shí)現(xiàn)有很多定制的開發(fā),但是真正完成后,團(tuán)隊(duì)中的成員只是使用還是比較簡單配置即可。
          關(guān)于前端項(xiàng)目部署我也分享了一個(gè)BFF 層,當(dāng)前做的還不是很完善,也分享給大家參考
          Render-Server 主要功能包含:
          • 一鍵部署 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ì)話、第三方登陸等等

          ??愛心三連擊

          1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的點(diǎn)贊在看是我創(chuàng)作的動(dòng)力。

          2.關(guān)注公眾號(hào)程序員成長指北,回復(fù)「1」加入Node進(jìn)階交流群!「在這里有好多 Node 開發(fā)者,會(huì)討論 Node 知識(shí),互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長。


          “在看轉(zhuǎn)發(fā)”是最大的支持

          瀏覽 44
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  综合网中文字幕 | 人人搞人人操 | 日韩三级黄 | 在线 色| 欧美肉丝袜videos办公室 |