koa2使用es7 的裝飾器decorator

来源:https://www.cnblogs.com/beyonds/archive/2019/07/16/11190359.html
-Advertisement-
Play Games

本文主要講述我在做項目中使用裝飾器(decorator)來動態載入koa-router的路由的一個基礎架構。 目前JavaScript 對decorator 是不支持,但是可以用babel 來編譯 既然是koa2結合decorator 使用,首先是要起一個koa2 項目。 環境要求: node >7 ...


 

本文主要講述我在做項目中使用裝飾器(decorator)來動態載入koa-router的路由的一個基礎架構。

目前JavaScript 對decorator 是不支持,但是可以用babel 來編譯

 

既然是koa2結合decorator 使用,首先是要起一個koa2 項目。

環境要求: node >7.6

 

1.建立文件夾名為koa-decorator ,在該目錄下運行 npm init 初始化一個項目(直接預設回車)

npm init

2.安裝koa的基本依賴包,koa,koa-router

 npm install koa,koa-router;  

 3.構建基本項目目錄


├── dist----------------------------------- 編譯後的 ├── src ----------------------------------- 項目的所有代碼 │ ├──config ----------------------------- 配置文件 │ ├──controller ------------------------- 控制器 │ ├──lib -------------------------------- 一些項目的核心文件(如路由的裝飾器文件就在這裡) │ ├──logic ------------------------------ 一些數據校驗 │ ├──middleware ------------------------- 中間件 │ ├──models------------------------------ 操作數據表相關邏輯代碼(根據項目複雜度可以再分Service層) │ ├──util-------------------------------- 相關的工具文件 │ ├──index.js---------------------------- 項目的入口文件 ├── theme --------------------------------- 一些靜態文件(上傳的圖片) ├── .babelrc ------------------------------ babelrc 的相關配置 ├── .gitignore ---------------------------- git 的忽略配置文件 ├── dev.js -------------------------------- 開發環境的啟動文件 ├── production.js ------------------------- 生產環境的啟動文件

  

 4.安裝babel ,與裝飾器的編譯依賴(只需要要開發環境安裝)  babel-cli,babel-core,babel-register,babel-plugin-transform-decorators-legacy

 npm install babel-cli,babel-core,babel-register,babel-plugin-transform-decorators-legacy --save-dev;

  

5.配置 .babelrc 文件讓 其能使用裝飾器

{
    "presets": [],
    "plugins": [
        "transform-decorators-legacy"
    ]
}

  

6. 編寫開發環境dev.js和 生產環境的production.js 的啟動文件

1. dev.js
require("babel-register");
process.env.NODE_ENV = "development";
require("./src");


2. production.js
process.env.NODE_ENV = "production";
require("./dist");

 你會發現這兩個文件很簡單,主要是區別用來開發運行和生產打包編譯的,生產環境運行的打包後的dist 目錄的代碼

 

7.配置package.json 使項目能修改後自動重啟熱載入,這裡開發環境我使用 supervisor,有人使用nodenom ,生產環境用pm2

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "babel src  --out-dir dist",
    "dev": "set NODE_ENV=development &&  supervisor  --watch src dev.js",
    "start": "npm run build && set NODE_ENV=production  && supervisor --watch dist production.js",
    "pm2": "pm2 start production.js --name 'wx-node' --env  NODE_ENV='production' --output ./logs/logs-out.log  --error ./logs/logs-error.log  --watch dist"
  },

     7.1  運行 npm run build : 是用babel 直接將src 目錄編譯在dist 目錄

  7.2 運行 npm run dev : 是設置環境變數為development 並且監聽src目錄,啟動dev.js 運行,為開發環境

     7.3 運行 npm run start : 是 運行第一個命令npm run build 並且設置環境變數為production 監聽dist 目錄,啟動production.js運行,為生產或者測試環境

     7.4 運行npm run pm2: 這是使用pm2來守護項目進程,並且設置環境變數和日誌記錄

 

8.編寫入口文件index.js 讓服務跑起來

src/index.js

const koa = require("koa");
const http = require("http");
const App = new koa();

// 定義埠常量
const port = 3000;

App.use(async (ctx,next)=>{
    ctx.body = await "this is koa"
    await next();
})

// 啟動服務
var httpApp = http.createServer(App.callback()).listen(port,'0.0.0.0');//獲取ip 為ip4 格式(192.168.5.109),預設是ip6 格式(::ffff:192.168.5.109);
httpApp.on("listening",()=>{
    console.log(`http server start runing in port ${port}...`)
})
App.on("error",(err,ctx)=>{
    console.log("server error: "+err.stack);
    ctx.throw(500, 'server error') 
})

 

9.重點:編寫裝飾器的路由文件,本文核心內容就是在這裡

       9.1  引入相關依賴包 和定義所有的請求方法

src/lib/decoratorRouter/index.js

const koaRouter = require("koa-router");
const router = new koaRouter(); 
const routerPrefix ="/api"   //定義介面首碼

//聲明所有介面的方式的映射,下麵會用到
const RequestMethod = {
    GET: 'get',
    POST: 'post',
    PUT: 'put',
    DELETE: 'delete',
    ALL: "all"
}

  

  9.2  編寫裝飾類class 的函數,主要作用是對類的攔截,然後實例化該類,並獲取和調用該類下所有實例方法,由於es6 的class的方法是不迭代的,所以使用了Object.getOwnPropertyDescriptors(object.prototype)

src/lib/decoratorRouter/index.js
//定義controller 的函數,這是裝飾類class 的函數,接受一個參數(和路由首碼並接一起)
function Controller(prefix) {
    router.prefixed =routerPrefix+(prefix ? prefix.replace(/\/+$/g, "") : '');
    //對 類 class 進行攔截操作,返回一個函數,該函數實際接受三個參數(攔截目標targer,目標的key,key 的描述) 
    return (target) => {
//把路由router 掛載在攔截目標,作為靜態屬性 target.router = router;
//實例化該類 class let obj = new target;
// 獲取該實例下的所有實例方法,進行 迭代調用,除了構造函數 和一個前置函數(後面會說得如何實現和作用) let actionList = Object.getOwnPropertyDescriptors(target.prototype); for (let key in actionList) { if (key !== "constructor") { var fn = actionList[key].value; if (typeof fn == "function" && fn.name != "__before") { fn.call(obj, router, obj);//保證在類中能正確訪問this,調用該方法是用call,還有兩個參數是 router 和 obj 實例 } } } } }

  

 9.3 編寫裝飾 實例方法的函數,當我們對類class 進行裝飾的時候,其實例方法會全部自動被調用,這時候繼續對實例方法進行攔截,攔截的目的就是給該實例方法與路由結合一起

/src/lib/decoratorRouter/index.js

//該裝飾函數接受兩個參數,請求url 和請求方式
function Request(option = {url, method}) {
//攔截該實例方法,參數三個 return function (target, value, dec) {
//聲明fn 緩存原來的 函數體 dev.value let fn = dec.value;
//然後重寫該函數,參數兩個,在 controller 裝飾類的時候自動調用轉入的兩個參數 dec.value = (routers, targets) => { //這裡,才是真正調用koa-router 路由的時候 routers[option.method](routers.prefixed + option.url, async (ctx, next) => {     //這裡寫了一個前置函數,判斷前置函數存在 if (target.__before && typeof target.__before == "function") {   // 如果class 有__before 前置函數,//再預設裝飾一次 var beforeRes = await target.__before.call(target,ctx, next, target);   //前置函數如果沒有返回內容,繼續執行實例方法,否則直接響應 body,不執行實例方法 if (!beforeRes) { return await fn.call(target, ctx, next, target) }else{ return ctx.body = await beforeRes } } else { // 沒有前置函數,直接調回原來的實例函數執行,使用call ,傳入的參數就有ctx,next,實例targe await fn.call(target, ctx, next, target) } }) } } }

 

  9.4  整合所有的請求方法並導出介面 

/src/lib/decoratorRouter/index.js
// post 請求
 function POST(url) {
    return Request({url, method: RequestMethod.POST})
}
 //get 請求
 function GET(url) {
    return Request({url, method: RequestMethod.GET})
}
//PUT 請求
 function PUT(url) {
    return Request({url, method: RequestMethod.PUT})
}
//DEL請求
 function DEL(url) {
    return Request({url, method: RequestMethod.DELETE})
}
 //ALL 請求
 function ALL(url) {
    return Request({url, method: RequestMethod.ALL})
}

module.exports = {
    Controller,POST,GET,PUT,DEL,ALL
}

 

10 .裝飾koa-router 的核心內容寫完了,那麼如何做到自動載入呢,按照項目目錄架構,controller 目錄是處理介面目錄,使用內置的文件系統模塊fs 處理文件自動載入

/src/lib/loadRouter/index.js

const fs = require("fs");
const {resolve} = require("path")
//這裡很重要,區別環境變數,確定調用是 dist/controller (編譯後),還是調用 src/controller (開發)
let entryPath = process.env.NODE_ENV==="development"?"src":"dist"; 
console.log(process.env.NODE_ENV+"環境:執行目錄"+entryPath)//這是controller 的入口根目錄
let controllerPath = resolve(entryPath,'controller');
//對外導出一個函數,並接收app 實例作為參數,
module.exports = (App)=>{
    let  loadCtroller = (rootPaths)=>{
        try {
            var allfile = fs.readdirSync(rootPaths);  //載入目錄下的所有文件進行遍歷
            allfile.forEach((file)=>{
                var filePath = resolve(rootPaths,file)// 獲取遍歷文件的路徑
          
                if(fs.lstatSync(filePath).isDirectory()){ //判斷該文件是否是文件夾,如果是遞歸繼續遍歷讀取文件
                    loadCtroller(filePath)
                }else{
                 //如果是文件就使用require 導入,(controller下文件都是對外導出的class),在使用 @controller 裝飾函數的時候,將koa-router 的實例作為裝飾對象class 的靜態屬性  
              let r = require(filePath);
                  if(r&&r.router&&r.router.routes){ //如果有koa-routr 的實例說明裝飾成功,直接調用app.use() 
                      try {
                        App
                        .use(r.router.routes())
                      } catch (error) {
                          console.log(filePath)
                      }
                  }else{
                      // console.log("miss routes:--filename:"+filePath)
                  }
                }
            }) 
        } catch (error) {
            console.log(error)
            console.log("no such file or dir :---- "+rootPaths)
        } 
    }
    //調用自動載入路由
    loadCtroller(controllerPath);

}

 

  

11. 在index.js 入口文件載入 /src/lib/loadRouter/index.js 文件

const koa = require("koa");
const http = require("http");
const App = new koa();

// 定義埠常量
const port = 3000;


require("./lib/loadRouter/index")(App) // 載入自動載入路由文件

// 啟動服務
var httpApp = http.createServer(App.callback()).listen(port,'0.0.0.0');//獲取ip 為ip4 格式(192.168.5.109),預設是ip6 格式(::ffff:192.168.5.109);
httpApp.on("listening",()=>{
    console.log(`http server start runing in port ${port}...`)
})
App.on("error",(err,ctx)=>{
    console.log("server error: "+err.stack);
    ctx.throw(500, 'server error') 
})

 

12.然後編寫controller 下的文件,新建index.js

/src/controller/index.js

const {Controller,GET,POST} = require("../lib/decoratorRouter")

 //訪問路徑 :路由首碼 + controller 參數 + 請求方式的參數 => 功能變數名稱:埠/api/index/add
@Controller("/index")
class index{
@GET("/") async index(ctx,next){ ctx.body = await "this is index" } @POST("/add") async add(ctx,next){ ctx.body = await "this is add" } } module.exports = index;

 運行: http://127.0.01:3000/api/index/ 成功訪問顯示 this is index  ,到此基本完畢 了

  源碼git 地址: https://github.com/1119879311/npm_module/tree/master/node-decorator

  對於要多層繼續裝飾,做攔截,class繼承,還有前置函數的使用

可以參考該項目的用法:https://github.com/1119879311/koa2-decorator 

 

在此,完畢,篇幅內容有點多,看不懂可以留言,謝謝大家

  

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天在用junit測試mybits程式是遇到一個問題,報錯為: org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: java.sql.SQLException: The serv ...
  • iOS13中presentViewController的問題 更新了Xcode11.0 beta之後,在iOS13中運行代碼發現 和之前彈出的樣式不一樣。 會出現這種情況是主要是因為我們之前對 裡面的一個屬性,即 (該屬性是控制器在模態視圖時將要使用的樣式)沒有設置需要的類型。在iOS13中 的預設 ...
  • JavaScript簡介 JavaScript歷史 在上世紀1995年,著名的互聯網公司網景公司希望能在靜態HTML頁面上添加一些動態效果,於是叫Brendan Eich這哥們在兩周之內設計出了JavaScript語言。 ECMAScript 為了讓JavaScript成為全球標準,幾個公司聯合EC ...
  • JQGrid是一個在jquery基礎上做的一個表格控制項,以ajax的方式和伺服器端通信。 JQGrid Demo 是一個線上的演示項目。在這裡,可以知道jqgrid可以做什麼事情。 下麵是轉自其他人blog的一個學習資料,與其說是學習資料,說成查詢幫助文檔更加合適。 jqGrid學習之 安裝 jqG ...
  • 什麼是Node? Node.js 是一個基於Chrome V8 引擎的JavaScript運行環境 Node.js使用了一個事件驅動、非阻塞式I/O的模型,使其輕量又高效 事件驅動: 任務執行,發佈者,訂閱者,事件驅動 ( on emit ) 非阻塞: 執行某一個任務的同時也可以執行其他任務 I/O ...
  • 【本文為原創,轉載請註明出處】 技術【HTML+CSS】 佈局【Div】 步驟1 劃分div佈局 步驟2 填充內容 超鏈接+圖片+文本 步驟3 知識點整理 1.清除瀏覽器樣式 https://www.cnblogs.com/Caixingmin/p/11196614.html 2.多個div併排不換 ...
  • 清除瀏覽器預設樣式的原因 一、 某些標簽的預設樣式不符合我們的設計要求。比如說a標簽,預設它是有一條下劃線,並且字體顏色也讓人覺得很難看,所以我們需要清除它預設的樣式,同時根據要求給它重新添加自定義樣式。如圖是a標簽的預設樣式。 二、各瀏覽器預設的樣式各不同,所以會影響到我們的開發,因為在每次開發之 ...
  • 先做個自我介紹,我13年考上一所很爛專科民辦的學校,學的是生物專業,具體的學校名稱我就不說出來獻醜了。13年我就輟學了,我在那樣的學校,一年學費要1萬多,但是根本沒有人學習,我實在看不到希望,我就退學了。退學後我也迷茫,大專都沒有畢業,我真的不知道我能幹什麼,我在糾結著我能做什麼。所以輟學後我一段時 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...