從零開始搭建一個mock服務

nw2018發表於2018-05-11

mock資料是一件很有意義的事情,前後端可以並行開發正是得益於mock生成的假資料,趁著有空擼了個輪子easy-config-mock,以下記錄實現思路以及最終實現的所有程式碼技術細節


關注點

記錄開發過程中的關注點:

如何實現mock資料的功能(技術選型)

使用mockjs去生成假資料(附:mock規則

使用express搭建服務(附:express官網

如何整合到腳手架中,或者說工作流中

儘可能設計得簡單,使其很容易整合到現有的腳手架或者工作流中,實際上easy-config-mock也做到了很容易整合,只需:

const easyConfigMock = require('easy-config-mock')

new easyConfigMock({
  // 將配置檔案路徑傳遞進去,服務會自動監聽檔案變化並重啟服務
  path: path.resolve(__dirname, 'mock.config.js')
})
複製程式碼
如何跟專案搭配使用

推薦將mock資料的配置檔案放在專案的根目錄下,原因在於mock資料跟業務是緊密聯絡的,丟在一起容易查閱與維護,如下:

+ vue-preject
-   node_modules
-   src
    mock.config.js   // mock資料的配置檔案,名字僅做示例用
複製程式碼
如何支援更復雜的場景

mockjs的功能很強大,可以生成隨機假資料,但在業務場景非常複雜的情況下這還不夠,有時為了驗證顯示邏輯,期望是可以定製mock介面的返回

這是可以做到的,得益於express的中介軟體,看一下easy-config-mock中的配置檔案是怎麼寫的,更多說明

// mock.config.js
module.exports = {
  // common選項不是必須的,可以不用有該選項,內建的配置如下,當然你也可以更改
  common: {
    // mock服務的預設埠,如果埠被佔用,會自動換一個
    port: 8018,
    // 如果你想看一下ajax的loading效果,該配置項可以設定介面的返回延遲
    timeout: 500,
    // 如果你想看一下介面請求失敗的效果,將rate設定成0就可以了,rate取值範圍0~1,代表成功的概率
    rate: 1,
    // 預設是true,自動開啟mock服務,當然你也可以通過將其設定為false,關閉掉mock服務
    mock: true
  },
  // 普通的api...
  '/pkApi/getList': {
    code: 0,
    'data|5': [{
      'uid|1000-99999': 999,
      'name': '@cname'
    }],
    result: true
  },
  // 中介軟體api(標準的express中介軟體),這裡你可以書寫介面返回邏輯
  ['/pkApi/getOther'] (req, res, next) {
    const id = req.query.id
    req.myData = {   // 重要! 將返回資料掛載在req.myData
      0: {
        code: 0,
        'test|1-100': 100
      },
      1: {
        code: 1,
        'number|+1': 202
      },
      2:{
        code: 2,
        'name': '@cname'
      }
    }[id]
    next()  // 最後不要忘記手動呼叫一下next,不然介面就暫停處理了!
  }
}
複製程式碼

easy-config-mock的優點

  • 很容易整合到腳手架或者工作流中,並且可以自動重啟服務
  • 支援自定義中介軟體,以滿足更為複雜的業務場景
  • 基本不會侵入業務程式碼,只需要將介面的請求字首改成http://127.0.0.1:mock埠
  • 配置檔案丟在專案裡面利於開發與維護

實現的具體技術細節

以下是實現該輪子需要的所有技術細節了,程式碼僅簡要表達基本思想,詳情內容請看原始碼

如何監聽mock.config.js檔案變化

使用chokidar模組

const chokidar = require('chokidar')

chokidar.watch(somepath, {
  persistent: true
}).on('change', _ => {
  // file change...
  // do some logic...
})
複製程式碼
如何實現服務的自動重啟

fork子程式去啟動express服務,當配置檔案發生變化的時候,殺掉子程式並重啟服務

const childProcess = require('child_process')

let child
// 使用子程式啟動express服務
child = childProcess.fork('./server.js', [], {
  encoding: 'utf8',
  execArgv: process.execArgv
})

chokidar.watch(somepath, {
  persistent: true
}).on('change', _ => {
  // 檔案發生變化後殺死子程式並重啟服務
  child.kill('SIGKILL')
  child = childProcess.fork('./server.js', [], {
    encoding: 'utf8',
    execArgv: process.execArgv
  })
})
複製程式碼
子程式如何讀取到配置檔案資料

程式給父程式傳遞的資料子程式是不知道的,可以利用父子程式之間的通訊,可以參考child_process中子程式與父程式之間的通訊與斷開連線

// 給子程式傳遞資料
child.send({
  path: path
  ...
})
// 子程式接收到資料
process.on('message', ({ path, ... }) => {
  delete require.cache[path]
  // 這裡,拿到了mock資料的配置項
  const options = require(path)
})
複製程式碼

require是有快取的,需要先刪除require的快取,再去重新獲取配置檔案的資料

如何去模擬jsonp的請求

首先得知道該請求是否是jsonp,檢測請求連結是否帶有callback引數

let dataType
app.use((req, res, next) => {
  dataType = req.query.callback ? 'jsonp' : 'json'
  next()
})
複製程式碼
如何延遲介面的返回

有時,我們編寫了loading的效果並想驗證一下

const delayRes = (time) => (req, res, next) => {
  setTimeout(function() { next() }, time)
}
// 給介面增加1秒延遲
app.use(delayRes(1000))
複製程式碼
如何讓介面返回失敗

有時,我們想看下斷網或者伺服器出錯時的效果

const successRate = (rate) => (req, res, next) => {
  if (rate > Math.random()) return next()
  return next(500)
}
// 100%返回500錯誤
app.use(successRate(0))
app.use((err, req, res, next) => {
  res.status(500).json({ status: 0, code: 500, msg: 'Server Error' })
})
複製程式碼
如何允許跨域

訪問的非jsonp的mock介面是跨域請求(協議,域名,埠三者相同才為同域),跨域請求是禁止的,會報錯,這裡需要設定為允許跨域

const crossDomain = () => (req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
  if (req.method === 'OPTIONS') res.status(200) // 讓OPTIONS快速返回
  next();
}
app.use(crossDomain())
複製程式碼
最終:路由建立
const { mock } = require('mockjs')

// options是配置檔案裡面的api資訊
Object.keys(options).forEach(path => {
  const data = options[path]

  // 如果是自定義中間
  if (typeof data === 'function') app.use(data)

  app.use(path, (req, res, next) => {
    // req中帶有myData的話說明是自定義中介軟體,否則是普通的mock api
    const rsp = req.myData ? mock(req.myData) : mock(data)
    res.status(200)[dataType](rsp)
  })
})
複製程式碼
補充:easy-config-mock的webpack版外掛

easy-mock-webpack-plugin

其實直接用easy-config-mock就可以了


參考連結


都看到這了,賞個贊?唄~??????原始碼地址

本文完。

相關文章