手把手做一個node緩存服務(wù)

前言

不知道各位前端們在日常的開發(fā)生產(chǎn)中是否有過像我一樣的困擾

  1. 公司用的內(nèi)網(wǎng)或特定網(wǎng)絡(luò),離開公司就沒辦法使用內(nèi)部接口?
  2. 大屏項目或各種向領(lǐng)導(dǎo)演示時擔(dān)心網(wǎng)絡(luò)不穩(wěn)定,導(dǎo)致頁面的數(shù)據(jù),echart圖表等無法顯示?
  3. 在沒有后端沒有開發(fā)完的情況下怎樣自己實時制造接口數(shù)據(jù)來保證自己前端開發(fā)的進度不落后?
    ..........
    其實總結(jié)就是缺少了一個我們前端可以控制的接口服務(wù)

先看效果~

  • node服務(wù)啟動前 依賴內(nèi)網(wǎng)環(huán)境的前端項目所有的接口都無法使用


    image
  • node服務(wù)啟動后 在內(nèi)網(wǎng)狀態(tài)下進行緩存后即可在脫離內(nèi)網(wǎng)環(huán)境下演示和調(diào)試頁面


    image
  • node服務(wù)在有緩存后可以保證向服務(wù)請求時不用等待??后端數(shù)據(jù)庫的查詢,接口往往在10ms以內(nèi)就可以返回,保證演示的時候界面不會出現(xiàn)因為等待某個接口而造成頁面圖表或文字顯示不全


    image

開始手寫代碼

思路說明

  • 其實并不是什么高大尚的東西,本質(zhì)就是在有網(wǎng)絡(luò)環(huán)境的情況下啟動一個node服務(wù),請求后將所有請求的數(shù)據(jù)保存在本地的json文件中,之后如果在缺乏網(wǎng)絡(luò)環(huán)境或者需要脫離后端自行調(diào)試的時候就可以完全由node服務(wù)獲取接口數(shù)據(jù)。

目錄構(gòu)建

由于是個簡單的項目,在這里我也就不使用koa,egg等框架了,有需要的小伙伴可以自行引入

  • 先執(zhí)行npm init 初始化一下文件夾??
  • 在文件夾新建index.js作為入口文件,新建utils文件夾和其下的store.js,新建json文件夾和其下的test.json至此簡單的目錄結(jié)構(gòu)就已經(jīng)搭建成功啦。
├── index.js             //主入口文件
├── json
│   └── test.json    //存儲返回數(shù)據(jù)
├── package.json
└── utils
    └── store.js     //處理相關(guān)邏輯
代碼編寫
  1. store.js
// const fs = require('fs');
const date = new Date()
const path = require('path')
const defalutpath = path.resolve(__dirname, '../json/test.json')
const { promises: { readFile, writeFile } } = require('fs');
async function getStuff (path = defalutpath) {
  let result = await readFile(path, 'utf8');
  return result
}
async function setStuff (value, path = defalutpath) {
  let result = await writeFile(path, value, 'utf8');
  return result
}
const getJson = ((key, callback) => {
  getStuff().then(res => {
    const json = res ? JSON.parse(res) : {}
    // console.log('讀取數(shù)據(jù)', json[key])
    // console.log(json[key])
    if (typeof (json[key]) !== "undefined") {
      // console.log('有數(shù)據(jù)', json[key])
      callback(json[key])
    } else {
      callback(false)
    }
  })
})
const setJson = ((key, value = {}) => {
  getStuff().then(res => {
    const json = res ? JSON.parse(res) : {}
    json[key] = value
    if (json !== undefined) {
      setStuff(JSON.stringify(json))
      console.log('寫入成功')
    }
  })
})
//如果有需要還可以在存儲的key值后面加上今天的日期,保證數(shù)據(jù)的有效期。過了有效期重新請求
const formatDate = ((date) => {
  let myyear = date.getFullYear()
  let mymonth = date.getMonth() + 1
  let myweekday = date.getDate()
  if (mymonth < 10) {
    mymonth = "0" + mymonth
  }
  if (myweekday < 10) {
    myweekday = "0" + myweekday
  }
  return myyear + "-" + mymonth + "-" + myweekday
})
const store = {
  getJson,
  setJson
}
module.exports = store
  • getStuffsetStuff來異步讀寫文件內(nèi)容,避免造成node堵塞
  • getJsonsetJson則是對文件讀取寫入的邏輯處理
  1. index.js
// 1、加載模塊
const http = require('http');
const server = new http.Server()
const axios = require('axios');
const store = require('./utils/store')
const md5 = require('md5-node')
// const querystring = require('querystring')
const Url = require('url')
//定義請求頭,解決跨域
const headers = {
  "Content-Type": "application/json;charset=UTF-8",
  "Access-Control-Allow-Origin": "*",
  "access-control-allow-credentials": "true"
}
// 2、監(jiān)聽請求事件
server.on('request', function (request, response) {
  // 監(jiān)聽到請求之后所做的操作
  // request 對象包含:用戶請求報文的所有內(nèi)容
  // response 響應(yīng)對象,用來響應(yīng)一些數(shù)據(jù)
  // 當(dāng)服務(wù)器想要向客戶端響應(yīng)數(shù)據(jù)的時候,就必須使用response對象
  // const { method, url, headers } = request;
  // console.log(method, url, headers)
  if (request.url !== '/favicon.ico') {
    const index = request.url.indexOf('/mock/') //與前端約定用mock來識別
    const reurl = 'http://' + request.url.substr(index + 6)
    // 3、通過method來做不同的判斷
    if (request.method === 'OPTIONS') { 
      response.writeHead(200, {
        "Access-Control-Allow-Credentials": "true",
        "Access-Control-Allow-Headers": "authorization,content-type,token",
        "Access-Control-Allow-Methods": "*",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Expose-Headers": "authorization,content-type,token",
        "Access-Control-Max-Age": "3600",
        "Connection": "keep-alive",
        "Content-Length": "0",
        "Server": 'openresty/1.15.8.2'
      })
      // res.write('')
      response.end()
      console.log('options go api... ')
    }
    if (request.method === 'GET') {
      store.getJson(reurl, ((res) => { 
        // console.log('GET-res', res)
        if (res) {//如果命中緩存??
          console.log('get請求命中緩存??')
          response.writeHead(200, headers)
          response.end(JSON.stringify(res))
        } else {
          console.log('get請求命中發(fā)送······')
          axios.get(reurl)
            .then(res => {
              store.setJson(reurl, res.data)
              response.writeHead(200, headers);
              response.end(JSON.stringify(res.data));
            })
            .catch(err => {
              console.log('geterr', err);
            })
        }
      }))
    } else {//post 請求
      const newURL = Url.parse(reurl)
      parseJSON(request, response, ((res) => {
        const key = reurl + md5(res)//post請求用Md5加密保證參數(shù)獨立性
        const postData = res
        store.getJson(key, (res => {
        //這里是如果請求頭中帶有token或各種自定義請求頭時的配置
          const opheaders = {
            // 'Content-Length': length,
            // 'token': request.headers.token !== undefined ? request.headers.token : '',
            'Content-Type': 'application/json;charset=UTF-8'

          }
          // store.getJson(reurl)
          if (res) {//如果命中緩存??
            console.log('post請求命中緩存??')
            response.writeHead(200, headers)
            response.end(JSON.stringify(res))
          } else {
            console.log('post請求命中....')
            axios.post(reurl, postData, { headers: opheaders })
              .then(function (res) {
                store.setJson(key, res.data)
                response.writeHead(200, headers);
                response.end(JSON.stringify(res.data));
              })
              .catch(function (err) {
                console.log('posterr', err);
              });
          }
        }))
      }))
    }
  }
})

function parseJSON (req, res, next) {
  let data = ''
  // const length = req.headers['content-length']
  req.on('data', function (chunk) {
    // chunk 默認是一個二進制數(shù)據(jù),和 data 拼接會自動 toString
    data += chunk;
  });
  //注冊end事件,所有數(shù)據(jù)接收完成會執(zhí)行一次該方法
  //如果需要可以使用querystring對url進行反序列化(解析url將&和=拆分成鍵值對),得到一個對象
  req.on('end', function () {
    // console.log(data)
    if (data) {
      data = JSON.parse(data)
    }
    next(data)
  })
}

// 4、監(jiān)聽端口,開啟服務(wù)
server.listen(8099, function () {
  console.log("服務(wù)器已經(jīng)啟動,可訪問以下地址:");
  console.log('http://localhost:8099');
})
  • 安裝依賴axios,md5-node,入口文件的思路很簡單,在收到請求后先截取/mock/后的url用來作為store的key值。
  • 在本地json中讀取到這個key值則直接返回value值不去請求后端接口,如果沒有key值則請求后端接口并將后端的返回值寫入本地的json文件中,之后再返回給前端。

服務(wù)的啟動與使用

  1. npm install supervisor -g安裝這個依賴可以使我們服務(wù)熱更新便于調(diào)試。

    image

  2. 運行npm run hot 此時項目已經(jīng)啟動

  3. 前端服務(wù)在原先的地址上加入此時node地址,例如原先前端請求地址為172.16.18.147:8080/xxx/xxxx則此時在前端axios請求配置為
    http://node服務(wù)ip:8099/mock/172.16.18.147:8080/xxx/xxxx

    image

  4. 頁面正常的接口請求后,所有的返回數(shù)據(jù)會保存在本地的json文件中


    image
  5. 服務(wù)開啟時,如果請求命中??緩存則不會再走網(wǎng)絡(luò)請求,而是直接獲取本地json中的值


    image

小結(jié)

至此node服務(wù)的功能大致已經(jīng)介紹完畢啦。以后再有演示或者脫離后端的情況下只要你開啟node服務(wù)
就可以用本地緩存來展示數(shù)據(jù)啦~

做完這個后又想著做一個electron版本的服務(wù)了,這樣就可以時時自定義請求頭和接口參數(shù),這部分就下次再更新了

這是我的github項目地址有需要的朋友們可以去看看然后點個star~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。