【實踐篇】node實現mock小工具

寫在前面

最近在使用Mockjs做爲項目裏面mock數據的工具,發現mockjs作的攔截部分是本身實現摸擬了一個XMLHttpRequest的方法作的攔截,使用Mockjs攔截請求後,在chromenetwork上沒法看到請求(具體mockjs使用方法能夠查看他的api,mockjs-api這裏我很少作闡述),但爲了更加真實的像後臺返回數據,我本身使用Node做爲中間代理去實現了一個mock-plugin.javascript

express中間件介紹

由於插件至關因而實現了一個express的中間件的方式,因此這裏簡單對express中間件的使用作一個說明:html

express中間件經過app.use(也有app.get,app.post等方法)的方式註冊到express的實例某個屬性上,將執行函數存放在棧內部,而後在回調執行的時候調用next()方法將執行下一個存在棧內的方法。前端

這裏列舉一個示例:java

const express = require('express');
const app = express();

app.use(function (req, res, next) {
  console.log('first all use');
  next()
});

app.use(function (req, res, next){
  setTimeout(() => {
    console.log(`two all use`)
    next()
  }, 1000)
});

app.use(function (req, res, next) {
  console.log('end all use')
  next()
});

app.use('/', function (req, res, next) {
  res.end('hello use')
});

app.listen(4000, function () {
  console.log(`起動服務成功!`)
});

經過node執行以上代碼後,在瀏覽器上經過訪問http://locahost:4000能夠看到控制檯打印:
1.png-12.3kBnode

能夠發如今執行的時候先執行了use註冊的中間件,而後再執行到get路由的時候,又執行了app.use註冊的中間件。webpack

詳細的express中間件能夠在express官網查看ios

實現dev-server

devServer可使用webpack-dev-server而後經過before的回調去作一層攔截,這樣也可以實如今響應以前對後臺的數據作一些處理。git

我這兒選擇本身實現一個devServer,在以前使用webpack-dev-server的服務大概須要配置,port, proxy,以及跨域https等。固然本身實現devServer就不必實現那麼多功能了,正常在開發場景下不少也不必定用得上,這裏我主要使用了webpack-dev-middlewarewebpack-hot-middleware達到自動編譯和熱更新的目的,以及能夠本身在中間添加express中間件.github

貼上代碼:web

const path = require('path');
const express = require('express');
const webpack = require('webpack');
const webpackConfig = require('./webpack.dev');
const devMiddleware = require('webpack-dev-middleware');
const hotMiddleware = require('webpack-hot-middleware');
const app = express();
const compiler = webpack(webpackConfig); // webpack開發環境配置
const mockPlugin = require('./mock-plugin');

const config = {
  prd: 8800
};

 // 註冊webpack-dev-middleware中間件
app.use(
  devMiddleware(compiler, { 
    publicPath: webpackConfig.output.publicPath
  })
);

// 註冊webpack-hot-middleware中間件
app.use(
  hotMiddleware(compiler)  
);

// 註冊mockPlugin插件
app.use(
  mockPlugin({ 
    routes: {
      '/app': 'http://locahost:3002', // 測試代理到服務器的地址
      '/api': 'http://localhost:3003' // 測試代理到服務器的地址
    },
    root: path.resolve(__dirname) // 項目根目錄
  })
);

app.listen(config.prd, function () {
  console.log('訪問地址:', `http://localhost:${config.prd}`);
});

具體的一些演示操做,這裏也很少講了(這不是實現mock-plugin的重點),網上也有不少若是經過webpack-dev-middlewarewebpack-hot-middleware的教程,惟一的區別是代理部分,網上可能用的是http-proxy之類已現有的工具,由於咱們這兒須要在請求代理中間還須要處理一層,因此這兒咱們本身實現mockPlugin註冊進去。

摸擬mock文件

由於mock工具包含了一個請求後臺的結果自動寫入到Mock目錄下。因此這裏將目錄層級設置爲與請求路徑保持一致:

api接口:/app/home/baseInfo => 目錄:mock\app\home\baseInfo.js

對應 baseInfo.js 模板:

// mock 開關
exports.check = function () {
  return true;
}
// mock 數據
exports.mockData = function () {
  return {
    "success": true,
    "errorMsg": "",
    "data": {
      name: 'test'
    }
  }
}

checktrue時對就請求將會取mockData的數據。

主邏輯實現

mock-plugin主要暴露一個高階函數,第一層爲請求代理配置,返回的函數的參數與app.get('/')的回調參數一致,不描述細節,大概輸出主要的邏輯。

// 獲取mock文件的mock數據
const setMockData = (moduleName) => {
  const {mockData} = require(moduleName);
 
  return mockData();
};

// 中間件暴露方法
module.exports = function (options) {
  const {routes, root} = options;

  return async (req, res, next) => {
    let {isReq, host} = await valid.isRequestPath(routes, req);

    // 不是請求地址直接return掉
    if (!isReq) {
      next();
      return;
    }

    // 若是存在Mock對應的文件
    let filePath = await valid.isMockFileName(root, req.path);

    if (filePath) {
      // 檢驗本地mock文件開關是否開啓
      let check = await valid.inspectMockCheck(filePath);
      if (check) {
        // 發送本地mock數據
        return res.send(setMockData(filePath))
      } else {
        // 請求結果
        let body = await request(host, req, res).catch(proxyRes => {
          res.status(proxyRes.statusCode);
        });
        // 發送請求的結果信息
        return res.send(body);
      }
    } else {
      // 請求返回主體
      let body = await request(host, req, res).catch(proxyRes => {
        res.status(proxyRes.statusCode);
        next();
      });

      if (body) {
        // 定義須要寫入文件路徑
        const filePath = path.resolve(root, `mock${req.path}.js`);
        // 寫入mock文件
        writeMockFile(filePath, body);
        // 響應返回主體
        return res.send(body);
      }
    }
  };
};

如下是一些校驗方法,詳細代碼就不貼了,具體源碼可查看:https://github.com/moxaIce/lo...

  • isRequestPath校驗是否爲api接口請求, 返回 Promise包含isReq布爾值,host請求域名, route請求路由的對象。
  • isMockFileName是否存在對應的mock文件,返回Promise返回匹配路徑或者空字符串
  • inspectMockCheck校驗模擬文件請求,開關是否開起, 返回布爾值

至於request方法和writeMockFile方法看下面的小結。

如下是我本身畫的一個邏輯圖,有點醜見諒:
2.png-23.6kB

請求代理

代理的做用不用多說,都知道是解決了前端起的服務和直接請求後臺的跨域問題。我這兒主要是在中間件內部經過http.request方法發起一個http請求,對於http.request方法的使用能夠看這裏, 裏面也有比較詳細的示例,我這兒貼上我寫的代碼:

/**
 * @description 請求方法
 */
const url = require('url');
const http = require('http');

module.exports = function (host, req, res) {
  let body = '';

  return new Promise((resolve, reject) => {
    const parse = url.parse(host);
    let proxy = http.request(
      {
        host: host.hostname,
        port: parse.port,
        method: req.method,
        path: req.path,
        headers: req.headers
      },
      (proxyRes) => {
        // 非200字段內直接響應錯誤 , 在主邏輯裏處理
        if (proxyRes.statusCode < 200 || proxyRes.statusCode > 300) {
          reject(proxyRes)
        }

        proxyRes.on('data', (chunk) => {
          body += chunk.toString();
        }).on('end', () => {
          try {
            resolve(JSON.parse(body));
          } catch (e) {
            // 將響應結果返回,在主文件作異常回調
            reject(proxyRes)
          }
        }).on('error', (err) => {
          console.log(`error is`, err);
        })
      });
    proxy.on('error', (e) => {
      console.error(`請求報錯:${e.message}`)
    });
    proxy.end()
  })
};

代理的實現比較簡單,主要經過外層傳入hostrequset, response在內部用url解析獲得ip而後配置requestoptions, 經過監聽dataend事件將獲得的主體報文resolve出去,以及中間對非200段內的響應處理。

文件寫入

經過中間傳options傳入的root, 能夠獲得完整的mock路徑path.resolve(__dirname, mock${req.path}.js)。傳入到寫入mock文件方法裏

module.exports = async function (filePath, body) {
  await dirExists(path.dirname(filePath));

  fs.writeFile(filePath, echoTpl(JSON.stringify(body)), function (err) {
    if (err) {
      console.log(`寫入文件失敗`)
    }
  });
}

定義mockjs模板

const echoTpl = (data) => {
  return `exports.check = function () {
    return false
  }
  exports.mockData = function () {
    return ${data}
  }
  `
};

dirExists經過遞歸的方式寫入文件目錄

// 獲取文件信息,報錯則文件不存在
const getStat = (path) => {
  return new Promise((resolve) => {
    fs.stat(path, (err, stats) => {
      if (err) {
        resolve(false);
      } else {
        resolve(stats);
      }
    })
  })
};
// 建立目錄
const mkdir = (dir) => {
  return new Promise((resolve) => {
    fs.mkdir(dir, err => {
      if (err) {
        resolve(false);
      } else {
        resolve(true);
      }
    })
  })
};
// 寫入文件
const dirExists = async (dir) => {
  let isExists = await getStat(dir);
  //若是該路徑且不是文件,返回true
  if (isExists && isExists.isDirectory()) {
    return true;
  } else if (isExists) {//若是該路徑存在可是文件,返回false
    return false;
  }
  //若是該路徑不存在
  let tempDir = path.parse(dir).dir;
  //遞歸判斷,若是上級目錄也不存在,則會代碼會在此處繼續循環執行,直到目錄存在
  let status = await dirExists(tempDir);
  let mkdirStatus;
  if (status) {
    mkdirStatus = await mkdir(dir);
  }
  return mkdirStatus;
};

效果演示

使用koa起一個node服務而且暴露以下路由和其中的數據,具體代碼能夠看這兒,我這兒只貼上了關鍵代碼

  • 服務端代碼:
router.get('/app/home/baseInfo', user_controller.baseInfo)
router.post('/app/login', user_controller.login)

const login = async (ctx, next) => {
  ctx.body = {
    success: true,
    message: '',
    code: 0,
    data: {
      a: 1,
      b: '2'
    }
  }
};

const baseInfo = async (ctx, next) => {
  ctx.body = {
    success: true,
    errorMsg: '',
    data: {
      avatar: 'http://aqvatarius.com/themes/taurus/html/img/example/user/dmitry_b.jpg',
      total: 333,
      completed: 30,
      money: '500'
    }
  };
};
  • client代碼
mounted() {
    axios.get('/app/home/baseInfo', function (res) {
      console.log(`res 23`, res)
    });

    axios({
      url: '/app/login',
      method: 'post',
      headers: {
        // 'Content-Type': 'application/json;charset=UTF-8',
        'a': 'b'
      }
    })
  }

具體效果能夠看下圖:
34.gif-270kB
前端在訪問的時候會將後臺響應的數據自動寫入到Mock目錄下。

結語

感受在寫文章的過程當中發現寫入mock文件的時候可能使用stream會更好一點,年末了業務需求不太多,避免上班划水,隨便想了寫一寫~

相關文章
相關標籤/搜索