從0開發屬於自己的nestjs框架的mini 版 —— koa-decorator路由篇

来源:https://www.cnblogs.com/beyonds/archive/2023/07/30/17591489.html
-Advertisement-
Play Games

在SpringBoot的Controller中,可以使用註解@RequestBody來獲取POST請求中的JSON數據。我們可以將這個註解應用到一個Controller方法的參數上,Spring將會負責讀取請求正文中的數據,將其反序列化為一個Java對象,並將其作為Controller方法的參數傳遞 ...


這篇主要是實現路由註解,用過nestjs的都知道,其路由都是通過註解來實現的,如有控制器@Controller(),@Get()...等等,nestjs 底層框架可選 是expres或者是Fastify,在這裡我選擇 koa2。

話不多說,直接上代碼

src/koa-decorator.ts

引入相關庫


import "reflect-metadata";
import path from "path";

類型聲明

/******** 類型聲明********* */
export type IMethondType = "get" | "post" | "delete" | "put" | "all";
export type IRouterType = {
  path: string | RegExp;
  methond: string | IMethondType;
};
//一個方法對應一個路由信息
export type IKeyMapRouters = {
  [methondName: string]: IRouterType;
};

// 類的元數據參數
export type IControllerMetate = {
  prefix: string | undefined;
  routers: IKeyMapRouters;
};


常量聲明


/********常量聲明********* */

export const CONTROLLER_META_KEY = Symbol("controller_meta_key"); // 控制器類裝飾器key
export const MOTHOD_META_KEY = Symbol("method_meta_key"); // 類方法裝飾器key
export const PARAMS_META_KEY = Symbol("params_meta_key"); // // 類方法參數裝飾器key
export const DesignParamtypes = "design:paramtypes"; //內置的獲取構造函數的參數

類控制裝飾器


/*********類控制裝飾器************** */

export function Controller(prefix?: string) {
  return function (target: Object) {
    let meta: IControllerMetate = getControllerMeta(target);
    meta.prefix = prefix;
    Reflect.defineMetadata(CONTROLLER_META_KEY, meta, target);
  };
}

// 獲取類元數據參數
export function getControllerMeta(target: Object) {
  let classMeta: IControllerMetate = Reflect.getMetadata(
    CONTROLLER_META_KEY,
    target
  );
  if (!classMeta) {
    classMeta = { prefix: "", routers: {} };
    Reflect.defineMetadata(CONTROLLER_META_KEY, classMeta, target);
  }
  return classMeta;
}

類方法裝飾器



/***************類方法裝飾器************************ */

// 方法
export function RequestFactory(methond: string) {
  return function (path?: string) {
    return function (target: any, methodName: string, dec: PropertyDescriptor) {
      let classMeta: IControllerMetate = getControllerMeta(target);
      let methondMeta: IRouterType = { path: path || "", methond };

      classMeta.routers[methodName] = methondMeta;

      Reflect.defineMetadata(
        CONTROLLER_META_KEY,
        classMeta,
        target.constructor
      );
    };
  };
}

export const GET = RequestFactory("get");
export const POST = RequestFactory("post");
export const PUT = RequestFactory("put");
export const DELETE = RequestFactory("delete");
export const ALL = RequestFactory("all");

類方法參數裝飾器


/**********類方法參數裝飾器*********************** */

// 方法參數
export function factroyParameter(fn: Function) {
  return function (target: any, methodName: string, paramsIndex: number) {
    let meta: Array<Function> =
      Reflect.getMetadata(PARAMS_META_KEY, target, methodName) || [];
    meta.unshift(fn);
    Reflect.defineMetadata(PARAMS_META_KEY, meta, target, methodName);
  };
}

// 上下文裝飾器
export function Ctx<T = any>() {
  return factroyParameter((ctx: T) => ctx);
}

//請求
export function Req<T extends { request: any }>() {
  return factroyParameter((ctx: T) => ctx.request);
}

// 響應
export function Res<T extends { response: any }>() {
  return factroyParameter((ctx: T) => ctx.response);
}
// url 的參數
export function Query<T extends { request: Record<any, any> }>(field?: any) {
  return factroyParameter((ctx: T) =>
    field ? ctx.request.query[field] : { ...ctx.request.query }
  );
}
// url 動態參數
export function Param<T extends { request: any }>() {
  return factroyParameter((ctx: T) => ctx.request.param);
}

//請求體參數
export function Body<T extends { request: any }>() {
  return factroyParameter((ctx: T) => ctx.request.body);
}
// 請求頭
export function Header<T extends { request: Record<any, any> }>(field?: any) {
  return factroyParameter((ctx: T) =>
    field ? ctx.request.headers[field] : ctx.request.headers
  );
}

// next 方法
export function Next<T extends Function>() {
  return factroyParameter((_: any, next: T) => next);
}

註冊類控制器的路由(核心方法)

/**
 * 註冊類控制器的路由
 */
export function ResigerRouter(
  routerIntance: any,
  controllerInstance: Object | Function
) {
  if (!routerIntance) {
    throw `路由實例不能為空`;
  }
  if (typeof controllerInstance == "function") {
    controllerInstance = Reflect.construct(controllerInstance, []);
  }
  if (
    typeof controllerInstance == "object" &&
    typeof controllerInstance.constructor != "function"
  ) {
    throw `控制器不是一個類函數,註冊失敗`;
  }
  let { prefix, routers }: IControllerMetate = getControllerMeta(
    controllerInstance.constructor
  );
  for (let key in routers) {
    if (key == "constructor") return;

    let routerMeat = routers[key];
    let pathname;
    if (routerMeat.path instanceof RegExp) {
      pathname = routerMeat.path;
    } else if (typeof routerMeat.path !== "object") {
      pathname =
        path
          .join("/", prefix || "", routerMeat.path || "")
          .replace(/\\+/g, "/") || "/";
    }

    //方法參數
    let parameterMeta =
      Reflect.getMetadata(PARAMS_META_KEY, controllerInstance, key) || [];
    let actionFn = async (ctx: any, next: Function) => {
      let args = parameterMeta.map((item: Function) =>
        item(ctx, next, controllerInstance, key)
      );
      const resBody = await (controllerInstance as any)[key].call(
        controllerInstance,
        ...args
      );
      if (resBody !== undefined) ctx.body = await resBody;
      await next();
    };

    let methond = routerMeat.methond;
    routerIntance[methond](pathname, actionFn);
  }
}

測試用例

import koaRouter from "koa-router";
import Koa from "koa";
import { Controller, Ctx, GET, Query, ResigerRouter } from "./koa-decorator";

const app = new Koa();
let rotuer = new koaRouter({
  prefix: "/api",
});

@Controller("user")
class User {
  @GET("list")
  getUserId(@Query("id") id: string, @Ctx() ctx: Koa.DefaultContext): string {
    console.log("ctx", ctx);
    return this.computed(id);
  }

  computed(value: string): string {
    return value + "---computed";
  }
}
// 載入路由
ResigerRouter(rotuer, User);
app.use(rotuer.routes());
app.listen(8080, () => {
  console.log("server is run in prot 8080");
});


// 訪問:http://127.0.0.1:8080/api/user/list?id=1

總結

1、這是使用koa ,koa-router 為底層,無破壞,無侵入實現的路由裝飾器的框架
2、支持可以擴展更多的其他功能的裝飾器
3、此路由框架與上以一篇的實現的ioc框架之前是沒有關聯的,目前兩個都是單獨無耦合的


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

-Advertisement-
Play Games
更多相關文章
  • 一、問題描述: 在待機或正常使用過程中,時不時遇到桌面無響應的情況,但滑鼠正常移動。網路上大致給出以下幾種處理思路: 1.移除拓展塢,集線器2.打開設備管理器,通用串列匯流排控制器,對裡面每個設備的:“允許電腦關閉此設備以節約電源”,把勾去除3.通過命令徹底卸載小組件4.更換無線網卡驅動 本問題實際 ...
  • 博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ...
  • # 痞子衡嵌入式半月刊: 第 79 期 ![](https://raw.githubusercontent.com/JayHeng/pzh-mcu-bi-weekly/master/pics/pzh_mcu_bi_weekly.PNG) 這裡分享嵌入式領域有用有趣的項目/工具以及一些熱點新聞,農曆年 ...
  • 大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是**恩智浦i.MX RT1170 FlexSPI NAND啟動時間**。 本篇是 i.MXRT1170 啟動時間評測第四彈,前三篇分別給大家評測了 [Raw NAND 啟動時間](https://www.cnblogs.com/henj ...
  • 一、插入數據優化 1.1 批量插入 如果有多條數據需要同時插入,不要每次插入一條,然後分多次插入,因為每執行一次插入的操作,都要進行資料庫的連接,多個操作就會連接多次,而一次批量操作只需要連接1次 1.2 手動提交事務 因為Mysql預設每執行一次操作,就會提交一次事務,這樣就會涉及到頻繁的事務的開 ...
  • “莆仙小館”——莆田文化展示APP 文化展示程式目的在於應用科學技術助推家鄉優秀傳統文化的展示與交流。通過圖片、視頻、音頻等展示方式向用戶立體地展示一個文化城邦。傳統文化與科學技術的有效融合,順應了社會發展的需要。傳統文化與科學技術的有效融合是發展中國特色社會主義文化的客觀需要,是傳承中國優秀傳統文 ...
  • # 解決方案 使用`ngClass`和`ngStyle`可以進行樣式的綁定。 ## ngStyle的使用 ngStyle 根據組件中的變數, isTextColorRed和fontSize的值來動態設置元素的顏色和字體大小 ```HTML This text has dynamic styles b ...
  • 在本篇文章中,我們詳細介紹了 Flutter 進階的主題,包括導航和路由、狀態管理、非同步處理、HTTP請求和Rest API,以及數據持久化。這些主題在實際應用中都非常重要,幫助你構建更複雜、功能更強大的 Flutter 應用。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...