一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

【實(shí)踐篇】node實(shí)現(xiàn)mock小工具

 Coder編程 2020-02-15

寫在前面

最近在使用Mockjs作為項(xiàng)目里面mock數(shù)據(jù)的工具,發(fā)現(xiàn)mockjs做的攔截部分是自己實(shí)現(xiàn)摸擬了一個(gè)XMLHttpRequest的方法做的攔截,使用Mockjs攔截請(qǐng)求后,在chromenetwork上無法看到請(qǐng)求(具體mockjs使用方法可以查看他的api,mockjs-api這里我不多做闡述),但為了更加真實(shí)的像后臺(tái)返回?cái)?shù)據(jù),我自己使用Node作為中間代理去實(shí)現(xiàn)了一個(gè)mock-plugin.

express中間件介紹

因?yàn)椴寮喈?dāng)于是實(shí)現(xiàn)了一個(gè)express的中間件的方式,所以這里簡單對(duì)express中間件的使用做一個(gè)說明:

express中間件通過app.use(也有app.get,app.post等方法)的方式注冊(cè)到express的實(shí)例某個(gè)屬性上,將執(zhí)行函數(shù)存放在棧內(nèi)部,然后在回調(diào)執(zhí)行的時(shí)候調(diào)用next()方法將執(zhí)行下一個(gè)存在棧內(nèi)的方法。

這里列舉一個(gè)示例:

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(`起動(dòng)服務(wù)成功!`)
});

通過node執(zhí)行以上代碼后,在瀏覽器上通過訪問http://locahost:4000可以看到控制臺(tái)打?。?br>1.png-12.3kB

可以發(fā)現(xiàn)在執(zhí)行的時(shí)候先執(zhí)行了use注冊(cè)的中間件,然后再執(zhí)行到get路由的時(shí)候,又執(zhí)行了app.use注冊(cè)的中間件。

詳細(xì)的express中間件可以在express官網(wǎng)查看

實(shí)現(xiàn)dev-server

devServer可以使用webpack-dev-server然后通過before的回調(diào)去做一層攔截,這樣也能夠?qū)崿F(xiàn)在響應(yīng)之前對(duì)后臺(tái)的數(shù)據(jù)做一些處理。

我這兒選擇自己實(shí)現(xiàn)一個(gè)devServer,在之前使用webpack-dev-server的服務(wù)大概需要配置,port, proxy,以及跨域https等。當(dāng)然自己實(shí)現(xiàn)devServer就沒必要實(shí)現(xiàn)那么多功能了,正常在開發(fā)場景下很多也不一定用得上,這里我主要使用了webpack-dev-middleware和webpack-hot-middleware達(dá)到自動(dòng)編譯和熱更新的目的,以及可以自己在中間添加express中間件.

貼上代碼:

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開發(fā)環(huán)境配置
const mockPlugin = require('./mock-plugin');

const config = {
  prd: 8800
};

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

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

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

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

具體的一些演示操作,這里也不多講了(這不是實(shí)現(xiàn)mock-plugin的重點(diǎn)),網(wǎng)上也有很多如果通過webpack-dev-middlewarewebpack-hot-middleware的教程,唯一的區(qū)別是代理部分,網(wǎng)上可能用的是http-proxy之類已現(xiàn)有的工具,因?yàn)槲覀冞@兒需要在請(qǐng)求代理中間還需要處理一層,所以這兒我們自己實(shí)現(xiàn)mockPlugin注冊(cè)進(jìn)去。

摸擬mock文件

因?yàn)?code>mock工具包含了一個(gè)請(qǐng)求后臺(tái)的結(jié)果自動(dòng)寫入到Mock目錄下。所以這里將目錄層級(jí)設(shè)置為與請(qǐng)求路徑保持一致:

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

對(duì)應(yīng) baseInfo.js 模板:

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

當(dāng)checktrue時(shí)對(duì)就請(qǐng)求將會(huì)取mockData的數(shù)據(jù)。

主邏輯實(shí)現(xiàn)

mock-plugin主要暴露一個(gè)高階函數(shù),第一層為請(qǐng)求代理配置,返回的函數(shù)的參數(shù)與app.get('/')的回調(diào)參數(shù)一致,不描述細(xì)節(jié),大概輸出主要的邏輯。

// 獲取mock文件的mock數(shù)據(jù)
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);

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

    // 如果存在Mock對(duì)應(yīng)的文件
    let filePath = await valid.isMockFileName(root, req.path);

    if (filePath) {
      // 檢驗(yàn)本地mock文件開關(guān)是否開啟
      let check = await valid.inspectMockCheck(filePath);
      if (check) {
        // 發(fā)送本地mock數(shù)據(jù)
        return res.send(setMockData(filePath))
      } else {
        // 請(qǐng)求結(jié)果
        let body = await request(host, req, res).catch(proxyRes => {
          res.status(proxyRes.statusCode);
        });
        // 發(fā)送請(qǐng)求的結(jié)果信息
        return res.send(body);
      }
    } else {
      // 請(qǐng)求返回主體
      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);
        // 響應(yīng)返回主體
        return res.send(body);
      }
    }
  };
};

以下是一些校驗(yàn)方法,詳細(xì)代碼就不貼了,具體源碼可查看:https://github.com/moxaIce/lo...

  • isRequestPath校驗(yàn)是否為api接口請(qǐng)求, 返回 Promise包含isReq布爾值,host請(qǐng)求域名, route請(qǐng)求路由的對(duì)象。
  • isMockFileName是否存在對(duì)應(yīng)的mock文件,返回Promise返回匹配路徑或者空字符串
  • inspectMockCheck校驗(yàn)?zāi)M文件請(qǐng)求,開關(guān)是否開起, 返回布爾值

至于request方法和writeMockFile方法看下面的小結(jié)。

以下是我自己畫的一個(gè)邏輯圖,有點(diǎn)丑見諒:
2.png-23.6kB

請(qǐng)求代理

代理的作用不用多說,都知道是解決了前端起的服務(wù)和直接請(qǐng)求后臺(tái)的跨域問題。我這兒主要是在中間件內(nèi)部通過http.request方法發(fā)起一個(gè)http請(qǐng)求,對(duì)于http.request方法的使用可以看這里, 里面也有比較詳細(xì)的示例,我這兒貼上我寫的代碼:

/**
 * @description 請(qǐng)求方法
 */
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字段內(nèi)直接響應(yīng)錯(cuò)誤 , 在主邏輯里處理
        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) {
            // 將響應(yīng)結(jié)果返回,在主文件做異?;卣{(diào)
            reject(proxyRes)
          }
        }).on('error', (err) => {
          console.log(`error is`, err);
        })
      });
    proxy.on('error', (e) => {
      console.error(`請(qǐng)求報(bào)錯(cuò):${e.message}`)
    });
    proxy.end()
  })
};

代理的實(shí)現(xiàn)比較簡單,主要通過外層傳入hostrequset, response在內(nèi)部用url解析得到ip然后配置requestoptions, 通過監(jiān)聽dataend事件將得到的主體報(bào)文resolve出去,以及中間對(duì)非200段內(nèi)的響應(yīng)處理。

文件寫入

通過中間傳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通過遞歸的方式寫入文件目錄

// 獲取文件信息,報(bào)錯(cuò)則文件不存在
const getStat = (path) => {
  return new Promise((resolve) => {
    fs.stat(path, (err, stats) => {
      if (err) {
        resolve(false);
      } else {
        resolve(stats);
      }
    })
  })
};
// 創(chuàng)建目錄
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;
  //遞歸判斷,如果上級(jí)目錄也不存在,則會(huì)代碼會(huì)在此處繼續(xù)循環(huán)執(zhí)行,直到目錄存在
  let status = await dirExists(tempDir);
  let mkdirStatus;
  if (status) {
    mkdirStatus = await mkdir(dir);
  }
  return mkdirStatus;
};

效果演示

使用koa起一個(gè)node服務(wù)并且暴露如下路由和其中的數(shù)據(jù),具體代碼可以看這兒,我這兒只貼上了關(guān)鍵代碼

  • 服務(wù)端代碼:
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:///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
前端在訪問的時(shí)候會(huì)將后臺(tái)響應(yīng)的數(shù)據(jù)自動(dòng)寫入到Mock目錄下。

結(jié)語

感覺在寫文章的過程中發(fā)現(xiàn)寫入mock文件的時(shí)候可能使用stream會(huì)更好一點(diǎn),年底了業(yè)務(wù)需求不太多,避免上班劃水,隨便想了寫一寫~

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    欧美大黄片在线免费观看| 亚洲精选91福利在线观看| 亚洲国产成人久久一区二区三区 | 98精品永久免费视频| 久热香蕉精品视频在线播放| 日韩精品毛片视频免费看| 国产精品日韩欧美一区二区| 国产成人亚洲综合色就色| 九九久久精品久久久精品| 99热在线播放免费观看| 婷婷开心五月亚洲综合| 精品亚洲av一区二区三区| 夫妻激情视频一区二区三区| 欧洲日本亚洲一区二区| 欧美国产日本免费不卡| 日本午夜免费观看视频| 日韩国产欧美中文字幕| 老司机精品在线你懂的| 国产午夜精品福利免费不| 99久只有精品免费视频播放| 在线免费观看一二区视频| 日韩精品一区二区一牛| 精品国产丝袜一区二区| 国产成人精品午夜福利| 日本妇女高清一区二区三区| 五月婷婷亚洲综合一区| 少妇人妻精品一区二区三区| 欧洲偷拍视频中文字幕| 亚洲国产另类久久精品| 最近中文字幕高清中文字幕无| 亚洲高清一区二区高清| 国产又粗又黄又爽又硬的| 国产熟女一区二区不卡| 精品少妇一区二区三区四区| 暴力性生活在线免费视频| 欧美人妻一区二区三区| 亚洲最新一区二区三区| 国产又粗又黄又爽又硬的| 国产日韩精品欧美综合区| 国产亚洲欧美一区二区| 国产精品香蕉在线的人|