前端 mock 完美解決方案實戰

2021-02-24 JAVA有道
閱讀本文前需要一定Nodejs的相關知識,因為會擴展webpack的相關功能,並且實現需要遵守一定約定和Ajax封裝。
沉澱的腳手架也放到Github上供給同學參考React-Starter, 使用手冊還沒寫完善, 整體思路和react還是vue無關。
Mock功能介紹市面上講前端mock怎麼做的文章很多,整體上閱讀下來的沒有一個真正站在前端角度上讓我覺得強大和易用的。下面就說下我期望的前端mock要有哪些功能:生產打包可以把mock數據注入到打包的js中走前端mock對於後端已有的接口也能快速把Response數據轉化為mock數據對於第7點的作用是後續項目開發完成,在完全沒有開發後端服務的情況下,也可以進行演示。這對於一些ToB定製的項目來沉澱項目地圖(案例)很有作用。
對於第8點在開發環境後端服務經常不穩定下,不依賴後端也能做頁面開發,核心是能實現一鍵生成mock數據。配置解耦耦合情況什麼是前端配置解耦,首先讓我們看下平時配置耦合情況有哪些:webpack-dev後端測試環境變了需要改git跟蹤的代碼開發的時候想這個接口mock 需要改git跟蹤的代碼 mockUrl ,mock?如何解決前端依賴的配置解耦的思路是配置文件conf.json是在dev或build的時候動態生成的,然後該文件在前端項目引用:

├── 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;

默認使用config_default.js 的內容,如果有config.js 則覆蓋,開發的時候覆制config_default.js 為config.js 後續相關配置可以修改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, // 支持跨域請求      secure: true, // 支持 https    },  },  ...  conf: {    dev: {      title: "前端模板",      pathPrefix: "/react-starter", // 統一前端路徑前綴      apiPrefix: "/api/react-starter", //      debug: true,      delay: 500,    // mock數據模擬延遲      mock: {        // "global.login": "success",        // "global.loginInfo": "success",      }    },    build: {      title: "前端模板",      pathPrefix: "/react-starter",      apiPrefix: "/api/react-starter",      debug: false,      mock: {}    }  }};

在開發或打包的時候根據環境變量使用conf.dev或conf.build 生成conf.json文件內容。

// package.json{  "name": "react-starter",  "version": "1.0.0",  "description": "react前端開發腳手架",  "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文件中暴露出配置項,配置也可以通過window.conf來覆蓋

// src/common/utils.jsximport 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 (      <Router history={history}>        <Switch>          <Route path={`${config.pathPrefix}`} component={Home} />          <Redirect from="/" to={`${config.pathPrefix}`} />        </Switch>      </Router>    )  }}ReactDOM.render(     <App />,  document.getElementById('root'),)

Mock實現效果為了實現我們想要的mock的相關功能,首先是否開啟mock的配置解耦可以通過上面說的方式來實現,我們一般在頁面異步請求的時候都會目錄定義一個io.js的文件, 裡面定義了當前頁面需要調用的相關後端接口:

// 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-mock.json

上面定義了登錄和登出接口,我們希望對應開啟的mock請求能使用當前目錄下的login-mock.json文件的內容。

// src/pages/login/login-mock.json{    "login": {        "failed": {            "success": false,            "code": "ERROR_PASS_ERROR",            "content": null,            "message": "帳號或密碼錯誤!"        },        "success": {            "success": true,            "code": 0,            "content": {                "name": "admin",                "nickname": "超級管理員",                "permission": 15            },            "message": ""        }    },    "logout": {        "success": {            "success": true,            "code": 0,            "content": null,            "message": ""        }    }}

在調用logout登出這個Ajax請求的時候且我們的conf.json中配置的是"login.logout": "success" 就返回login-mock.json中的login.success 的內容,配置沒有匹配到就請求轉發到後端服務。

// config/conf.json{    "title": "前端後臺模板",    "pathPrefix": "/react-starter",    "apiPrefix": "/api/react-starter",    "debug": true,    "delay": 500,    "mock": {        "login.logout": "success"    }}

這是我們最終要實現的效果,這裡有一個約定:項目目錄下所有以-mock.jsom文件結尾的文件為mock文件,且文件名不能重複。思路在webpack配置項中devServer的proxy配置接口的轉發設置,接口轉發使用了功能強大的 http-proxy-middleware 軟體包, 我們約定proxy的配置格式是:

proxy: {    "/api/react-starter/*": {      target: `http://192.168.90.68:8888`,      changeOrigin: true,      secure: true,      // onError: (),      // onProxyRes,      // onProxyReq      },  },

option.onError 出現錯誤

option.onProxyRes 後端響應後

option.onProxyReq 請求轉發前

option.onProxyReqWs

option.onOpen

option.onClose

所以我們需要定製這幾個事情的處理,主要是請求轉發前和請求處理後onProxyReq想在這裡來實現mock的處理, 如果匹配到了mock數據我們就直接響應,就不轉發請求到後端。怎麼做呢:思路是依賴請求頭,dev情況下前端在調用的時候能否注入約定好的請求頭 告訴我需要尋找哪個mock數據項, 我們約定Header:然後conf.json中mock配置尋找到具體的響應項目如:"login.logout": "success/failed"的內容onProxyRes如果調用了真實的後端請求,就把請求的響應數據緩存下來,緩存到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": ""        }    }}

端接口封裝使用

// 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註冊到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 // 登陸操作 @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') 的封裝在調用的時候就可以非常簡單的來傳遞請求參數,簡單模式下會判斷參數是到body還是到query中。複雜的也可以支持比如可以header,query, body等這裡不演示了。createIo 請求封裝這個是前端接口封裝的關鍵地方,也是mock請求頭注入的地方。

// 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// 創建一個requestexport const request = creatRequest({ // 自定義的請求頭 headers: {'Content-Type': 'application/json'}, // 配置默認返回數據處理 action: (data) => { // 統一處理未登錄的彈框 if (data.success === false && data.code === ERROR_CODE.UN_LOGIN && !reloginFlag) { reloginFlag = true // TODO 這裡可能統一跳轉到 也可以是彈窗點擊跳轉 Modal.confirm({ title: '重新登錄', content: '', onOk: () => { // location.reload() history.push(`${config.pathPrefix}/login?redirect=${window.location.pathname}${window.location.search}`) reloginFlag = false }, }) } }, // 是否錯誤顯示message showError: true, message, // 是否以拋出異常的方式 默認false {success: boolean判斷} throwError: false, // mock 數據請求的等待時間 delay: config.delay, // 日誌列印 log,})
// 標識是否是簡單傳參數, 值為true標識複雜封裝export const rejectToData = Symbol('flag')
/** * 創建請求IO的封裝 * @param ioContent {any { url: string method?: string mock?: any apiPrefix?: string}} } * @param name mock數據的對應文件去除-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 = {}) => { // 這裡判斷簡單請求封裝 rejectToData=true 表示複雜封裝 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] ) { // 判斷是否是生產打包 mock注入到代碼中 ioContent[key].mock = JSON.parse(JSON.stringify(mockData[name][key][config.mock[`${name}.${key}`]])) } else if (name && config.debug === true) { //注入 mock請求頭 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也做進一步的封裝,配置項設置了一些默認的處理設置。比如通用的請求響應失敗的是否有一個message, 未登錄的情況是否有一個彈窗提示點擊跳轉登陸頁。如果你想定義多個通用處理可以再創建一個request2和createIo2。request封裝axios是基於axios的二次封裝, 並不是非常通用,主要是在約定的請求失敗和成功的處理有定製,如果需要可以自己修改使用。

import axios from 'axios'
// 配置接口參數// declare interface Options {// url: string// baseURL?: string// // 默認GET// method?: Method// // 標識是否注入到data參數// rejectToData?: boolean// // 是否直接彈出message 默認是// showError?: boolean// // 指定 回調操作 默認登錄處理// action?(data: any): any// headers?: {// [index: string]: string// }// timeout?: number// // 指定路由參數// params?: {// [index: string]: string// }// // 指定url參數// query?: any// // 指定body 參數// body?: any// // 混合處理 Get到url, delete post 到body, 也替換路由參數 在createIo封裝// data?: any// mock?: any// }// ajax 請求的統一封裝// TODO 1. 對jsonp請求的封裝 2. 重複請求
/** * 返回ajax 請求的統一封裝 * @param Object option 請求配置 * @param {boolean} opts.showError 是否錯誤調用message的error方法 * @param {object} opts.message 包含 .error方法 showError true的時候調用 * @param {boolean} opts.throwError 是否出錯拋出異常 * @param {function} opts.action 包含 自定義默認處理 比如未登錄的處理 * @param {object} opts.headers 請求頭默認content-type: application/json * @param {number} opts.timeout 超時 默認60秒 * @param {number} opts.delay mock請求延遲 * @returns {function} {params, url, headers, query, data, mock} data混合處理 Get到url, delete post 到body, 也替換路由參數 在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}, } // 簡單請求處理 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) } } // 路由參數處理 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 參數處理 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 = `瀏覽器請求非正常返回: 狀態碼 ${retData.status}` } } }
// 自動處理錯誤消息 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根據api-cache下的接口緩存和定義的xxx-mock.json文件來生成。

# "build-mock": "node ./scripts/build-mock.js"# 所有:npm run build-mock mockAll # 單個mock文件:npm run build-mock login# 單個mock接口:npm run build-mock login.logout# 複雜 npm run build-mock login.logout user

mock.json文件生成為了在build打包的時候把mock數據注入到前端代碼中去,使得mock.json文件內容儘可能的小,會根據conf.json的配置項來動態生成mock.json的內容,如果build裡面沒有開啟mock項,內容就會是一個空json數據。當然後端接口代理處理內存中也映射了一份該mock.json的內容。這裡需要做幾個事情:監聽config文件夾 判斷關於mock的配置項是否有改變重新生成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
/** * 初始化項目的配置 動態生成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.根據環境變量來生成 fs.writeFileSync(path.join(__dirname, '../config/conf.json'), JSON.stringify(confGlobal, null, '\t')) buildMock(confJson) }
// 生成mock文件數據 const buildMock = (conf) => { // 2.動態生成mock數據 讀取src文件夾下面所有以 -mock.json結尾的文件 存儲到io/index.json文件當中 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) }) // 如果是打包環境, 最小化mock資源數據 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')) }
// 監聽配置文件目錄下的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實現上面思路裡面說的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 請求: ${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}`]]) }
} }, // 響應緩存接口 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)}) } }, // 後端服務沒啟的異常處理 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.jsconst {init} = require('../scripts/webpack-init');init();// 接口請求本地緩存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, '..'), // 本地伺服器所加載的頁面所在的目錄 inline: true, port: config.port, publicPath: '/', historyApiFallback: { disableDotRule: true, // 指明哪些路徑映射到哪個html rewrites: config.rewrites, }, host: '127.0.0.1', hot: true, proxy: config.proxy, },}

mock做好其實在我們前端實際中還是很有必要的,做過的項目如果後端被剷除了想要回憶就可以使用mock讓項目跑起來,可以尋找一些實現的效果來進行代碼復用。當前介紹的mock流程實現有很多定製的開發,但是真正完成後,團隊中的成員只是使用還是比較簡單配置即可。關於前端項目部署我也分享了一個BFF 層,當前做的還不是很完善,也分享給大家參考Web安全支持 Ajax請求驗證,Referer 校驗支持插件開發和在線配置 可實現:前端模板參數注入、請求頭注入、IP白名單、接口mock、會話、第三方登陸等等

作者:rebareba
https://segmentfault.com/a/1190000038320901

推薦閱讀

相關焦點

  • 前端mock完美解決方案實戰
    它有這些功能:Mock功能介紹市面上講前端mock怎麼做的文章很多,整體上閱讀下來的沒有一個真正站在前端角度上讓我覺得強大和易用的。下面就說下我期望的前端mock要有哪些功能:生產打包可以把mock數據注入到打包的js中走前端mock對於後端已有的接口也能快速把Response數據轉化為mock數據對於第7點的作用是後續項目開發完成,在完全沒有開發後端服務的情況下,也可以進行演示。這對於一些ToB定製的項目來沉澱項目地圖(案例)很有作用。
  • 這麼好的微前端解決方案你頂得住?
    微前端概念由微服務概念延展而來,摒棄大型單體方式,將前端整體分解為小而簡單的塊,這些塊可以獨立開發、測試和部署,同時仍然聚合為一個產品出現在客戶面前。 微前端不是一門具體的技術,而是整合了技術、策略和方法,可能會以腳手架、輔助插件和規範約束這種生態圈形式展示出來,是一種宏觀上的架構。
  • 你需要了解的幾種微前端解決方案
    ,以及其帶來的利弊之處,因為那些弊端,使得我們團隊自己探究了一套目前認為最好的微前端解決方案。通過本文,可以快速幫您理清楚微前端方案的利弊,從而做出有利於您團隊的更好更明智的選擇。中文釋義:可以由多個團隊獨立開發的現代web應用程式的技術、策略和方案。本文則是在此基礎上對現有的微前端解決方案進行對比總結,廢話少說,讓我們開始今天的課題。
  • PostMan 之 Mock 接口測試
    測試人員可以通過 mock server 自己去造一個接口來訪問。mock server 可用於模擬真實的接口。收到請求時,它會根據配置返回對應的請求。在前後端分離的大背景下,mock server 在前端開發應用非廣泛,也影響到測試人員。
  • SpringBoot使用Powermockito mock靜態方法/私有方法 - 第342篇
    比如Mockito不能用於static Method, final method, 枚舉類, private method,這些我們都可以用powermock來實現,當然對於新版的Mockito配合上mockito-inline能解決部分的問題,這個具體看前面的章節《SpringBoot使用Mockito mock靜態方法/私有方法》。
  • Pokemon Go turn off mock locations解決途徑詳解
    Pokemon Go turn off mock locations解決途徑詳解。今天小編給大家帶來的是Pokemon Go turn off mock locations解決途徑詳解,下面一起來看看。
  • 群面Mock分享會!
    昨晚我們舉辦了小型mock,原計劃是9個人的小型Mock,時長1h。但由於活動太火爆,來了很多旁聽的小夥伴,全場20多個人,mock了一個小時四十分鐘才結束,真的是乾貨滿滿的一場mock呀!來學員的反饋👇👇👇
  • pageHelper分頁失效解決方案
    前言      pageHelper是一款優秀的Mybatis分頁插件,在項目中可以非常便利的使用,使開發效率得到很大的提升,但不支持一對多結果映射的分頁查詢,所以在平時的使用時,對於一對多分頁會出現分頁錯誤,這篇文章主要對pageHelper分頁錯誤進行重現以及提出解決方案
  • 如何在Python中創建mock
    解決這個問題有兩種方案。要麼忽略,像集成測試那樣去進行單元測試,要麼求助於mock。第一種方案的缺點是,集成測試僅僅告訴我們函數調用時哪一行代碼出問題了,這樣很難找到問題根源所在。這並不是說,集成測試沒有用處,因為在某些情況下它確實非常有用。不管怎樣,單元測試和集成測試用於解決不同的問題,它們應該被同時使用。因此,如果想要成為一個好的測試人員,mock是一個不錯的替代選擇。
  • 【第2154期】EMP微前端解決方案
    前言今日前端早讀課由歡聚時代@吉古力授權分享。
  • Spring Boot Swagger2自動生成接口文檔和Mock模擬數據
    一、簡介在當下這個前後端分離的技術趨勢下,前端工程師過度依賴後端工程師的接口和數據,給開發帶來了兩大問題:問題一、後端接口查看難:要怎麼調用?參數怎麼傳遞?有幾個參數?參數都代表什麼含義?這是很多公司前後端分離之後帶來的困擾,那怎麼來解決這些問題?問題一的一般解決方案:後端團隊共同維護一個在線文檔,每次改接口再去改對應的文檔,但難免會遺漏,花的大力氣但卻效果平平。
  • 微前端架構初探以及我的前端技術盤點
    你將收穫什麼是微服務以及微服務能給企業帶來什麼微前端架構概念及方案umi下的微前端架構方案實戰一個程式設計師的技術復盤與展望試想一下,如果面對以上問題, 如果有一種架構模式, 可以讓我們在主應用中共享公共組件和狀態(但是要保證子應用運行時內部的狀態隔離), 並且不同子模塊之間可以單獨開發部署, 模塊間切換不刷新頁面, 並且模塊之間,父子應用之間可以通過某種簡單的方式實現通信,這樣是不是就完美了?不錯, 這也許就是微前端要解決的問題.
  • mock介紹及moco框架搭建使用
    Mock介紹mock測試就是在測試過程中,對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法。 在具體的測試過程中,我們經常會碰到需要模擬數據或者接口的情況,因為環境問題或者系統複雜度的問題,我們需要使用 Mock 方式進行數據的模擬。
  • 推薦一些 GitHub 上值得前端學習的開源實戰項目,進階必看!
    最近好多同學問我了解找一些學習的實戰項目;看一個別人寫的優秀的項目,從中可以學到很多;比如代碼的規範,項目的結構;從項目作者每次提交記錄,去學習一些別人的開發思維以及開發整個項目的流程;下面我主要找了一些比較火的一些框架以及 node 項目。
  • 開課吧開啟雙十二教育節:以實戰驅動的Web前端課程
    隨著網際網路的快速發展,Web前端人員的需求量越來越大。前端開發也由此逐漸成為了一個不可缺少的專業角色。作為數位化人才在線教育平臺,開課吧帶領名師團隊研發了豐富的課程體系。
  • 觸景無限斬獲最佳前端人臉抓拍方案多項AI大獎
    日前,觸景無限科技(北京)有限公司再次獲得媒體與業界肯定,斬獲雷鋒網2018 AI最佳掘金案例年度榜單中「AI+安防領域」最佳前端人臉抓拍方案獎。  觸景無限憑藉在前端感知賽道的深耕榮膺大獎,實至名歸。 一直以來,觸景無限致力於從數據源頭實現人工智慧價值,賦能前端設備,其開放式的前端智能感知框架,完美體現「毫米毫秒毫瓦」特點,凸顯了硬體設計、驅動開發、算法並行化與工程化方面的強大實力。
  • ...第十二期筆記:5G時代需要怎樣的射頻前端解決方案?慧智微提供新解
    5月15日(周五)上午10點,第十二期「集微公開課」邀請到慧智微電子市場總監彭洋洋,帶來以《Agi5G 5G射頻前端整體解決方案》為主題的精彩演講。其中,對於5G終端而言,最為重要的元器件便在於射頻前端。作為接收發網絡信號的「心臟」。5G對射頻前端提出了更高的要求。作為目前全球領先的射頻前端晶片提供商,廣州慧智微電子通過自主創新,研發出有基礎專利的射頻前端可重構技術解決方案Agi5G,並已於2019年Q4量產。
  • 領域驅動設計在前端中的應用
    細數存在的問題這裡我們通過貼出項目中的問題代碼,來分析出一些存在問題,以及討論其會導致的後果與優化的方案。領域驅動設計不是萬能的,它只是解決了軟體開發中的部分問題,也不是可適用於任何場景的,但是其核心思想是可以借鑑到軟體設計與開發過程中的,本文主要講解領域驅動設計在前端中解決的問題以及核心思想。業務領域在龐大的項目中,領域是非常繁多的,在對領域進行劃分時,我們需要與產品、後端進行統一。
  • 當後端一次性丟給你10萬條數據,作為前端工程師的你,要怎麼處理?
    通過以上分析其實已經可以解決朋友的問題了,但是最為一名有追求的前端工程師, 筆者認真梳理了一下,並基於第一種方案抽象出一個實際的問題:如何渲染大數據列表並支持搜索功能?筆者將通過模擬不同段位前端工程師的實現方案, 來探索一下該問題的價值.
  • 菜鳥搭建 Mock 伺服器實踐:Anyproxy+Moco
    一、前言這次分享主要和mock相關的,mock測試就是在測試過程中,對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法