在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框架之前是沒有關聯的,目前兩個都是單獨無耦合的