說明javascript
封裝 lenneth 旨在快速方便的搭建出一個 node web 應用,不過分封裝也不隨波逐流,koa 的 node 是簡單的,lenneth 也是。java
基於 ES6+typescript 的一些特性,作了一些相似 spring 的註解,對開發自己不增長複雜度,而且不失 koa 的靈活性。 lenneth 內部已經集成了 koa, koa-bodyparser, koa-router 這 3 個基礎庫,已經知足了大部分的開發,若是須要引入其餘的中間件,能夠在入口文件中引入。node
lenneth 抽象了 3 個模塊,分別是 controller,middleware,service,內部不接入任何 db 和 cache。git
安裝github
yarn add lenneth
# or
npm install lenneth
複製代碼
瞄一眼web
import { LennethApplication, ServerSettings, ILenneth } from "lenneth";
@ServerSettings({
port: 8081
})
class App extends LennethApplication implements ILenneth {
$onMountingMiddlewares() {
this.use(async (ctx, next) => {
ctx.body = "hello world";
});
}
}
new App().start();
複製代碼
open in browserspring
http://localhost:8081
複製代碼
lenneth 核心typescript
koa 最精髓的就是它的洋蔥模型,而洋蔥模型的元素就是一個一個的中間件,lenneth 的封裝就是將普通的類方法轉化成 koa 的中間件express
/** * koa中間件,有兩個參數 * @params ctx 上下文 * @params next 洋蔥模型執行下一個中間件 */
async (ctx, next) => {
}
/** * 這個類方法並非koa的中間件 * 按照設計思路,類方法的兩個參數一個是獲取path參數,一個是獲取返回對象,和koa的中間件參數不一樣 */
@Get("/detail/:id")
@UseBefore(UserRuleAuth)
async getUserDetail(
@PathVariable("id") id: string,
@Response() response: TResponse
) {
response.body = this.userService.getUserById(id);
}
複製代碼
轉換函數npm
這個方法就是將上述的類方法轉成 koa 的中間件。在類方法的上層封裝了一個 koa 的中間件方法,在這個方法內部自動執行類方法,並將這個方法的 this 指向原來的類。
// lenneth封裝koa2的核心
const toAsyncMiddleware = (
target: Object | any,
middleware: TApiMiddleware,
key?: string,
cb?: (key: string, ctx: IContext, next: TNext) => any[]
) => {
return async (ctx: IContext, next: TNext) => {
if (key) {
// 此處必定要用call來從新設置this指向
return middleware.call(target, ...cb(key, ctx, next), ctx, next);
}
return middleware.call(target, ctx, next);
};
};
複製代碼
各個模塊
入口文件處,使用 ServerSettings 修飾,裏面的參數都是一些全局方法,如 interceptor,response 等,這些都是一個 middleware,lenneth 只是依照 koa 的洋蔥模型調整了他們的執行順利
@ServerSettings({
port: 8081,
// controller
imports: {
"/apis": UserController
},
// 攔截器
interceptor: Interceptor,
// 返回值封裝
response: AppResponse,
// error事件捕獲
globalError: GlobalError
})
class App extends LennethApplication implements ILenneth {
$onMountingMiddlewares() {
this.use(logger());
}
}
複製代碼
其實也是一箇中間件,只不過在最前執行
import { IMiddleware, Middleware, HeaderParams, Next, TNext } from "lenneth";
@Middleware()
export class Interceptor implements IMiddleware {
async use(@HeaderParams() header: any, @Next() next: TNext) {
console.log("Interceptor", header);
await next();
}
}
複製代碼
中間件,在最後執行,默認開啓,能夠覆蓋
import { IMiddleware, IContext, TResponse, TNext } from "@interfaces";
import { Middleware, Response, Next } from "@decorators";
import { HttpStatus, ResponseStatus } from "@common";
import { LennethError } from "./Lenneth-error";
@Middleware()
export class LennethResponse implements IMiddleware {
async use(
@Response() response: TResponse,
@Next() next: TNext,
ctx: IContext
) {
try {
// 執行前面全部的中間件
await next();
// 統一處理返回
if (response.body) {
return (response.body = {
code: 0,
message: ResponseStatus.SUCCESS,
data: response.body
});
}
return (response.body = { code: 0, message: ResponseStatus.SUCCESS });
} catch (err) {
ctx.status = err.code;
response.status = HttpStatus.OK;
if (err instanceof LennethError) {
response.body = {
code: err.code,
message: err.message || ResponseStatus.ERROR
};
} else {
response.body = {
code: err.code || HttpStatus.INTERNAL_SERVER_ERROR,
message: err.message || ResponseStatus.ERROR
};
// 未識別錯誤 拋至最外層error全局處理
throw err;
}
}
}
}
複製代碼
controller 主要是設置 router 和注入 services
router 的修飾器有 Post,Get 等,params 參數的獲取同 spring,注入 service 使用修飾器 Autowired,這個也和 spring 一致
import {
Controller,
Autowired,
Post,
Get,
RequestBody,
PathVariable,
Response,
TResponse,
UseBefore,
Description
} from "lenneth";
import { UserService } from "../services";
import { IUserInfo } from "../interface";
import { UserAuth, RuleAuth } from "../middleware";
@Controller("/user")
export class UserController {
@Autowired() userService: UserService;
@Post("/add")
@Description("添加會員")
@UseBefore(UserAuth, RuleAuth)
async addUser(
@RequestBody() user: IUserInfo,
@Response() response: TResponse
) {
response.body = this.userService.addUser(user);
}
@Get("/detail/:userId")
@UseBefore(UserAuth)
@Description("查詢會員")
async getUser(
@PathVariable("userId") userId: string,
@Response() response: TResponse
) {
response.body = this.userService.getUserInfo(userId);
}
}
複製代碼
middleware 本質上其實就是 koa 的中間件,只不過我在此基礎上又抽象出一層方法來引入獲取 params 的方法,用來方便開發
在 controller 每一個 api 上,使用 UseBefore 修飾器便可使用這些 middleware,在運行期,middleware 先於 controller 定義的接口,若是 middleware 沒有調用 next 函數,則不會調用下一個中間件(kao 洋蔥模型)
import { IMiddleware, Middleware, Next, TNext, HeaderParams } from "lenneth";
@Middleware()
export class UserAuth implements IMiddleware {
async use(@HeaderParams() headers: any, @Next() next: TNext) {
await next();
}
}
@Middleware()
export class RuleAuth implements IMiddleware {
async use(@HeaderParams() headers: any, @Next() next: TNext) {
await next();
}
}
複製代碼
這個模塊只是作一個類輸出方法
export class UserService {
addUser(userInfo: IUserInfo) {
return userInfo;
}
getUserInfo(id: string) {
return {
name: "zhangsan",
age: 30
};
}
}
複製代碼
單元測試
yarn test
複製代碼
案例
項目地址
說在最後
當初作項目的時候,在 github 上搜過一個項目,是基於 express 的--ts-express-decorators,裏面有不少不錯的設計,lenneth 裏的服務啓動生命週期就是照搬其中的。不過我不喜歡把 node 弄得那麼大,那麼全,並且,koa 自己就是一個極簡的應用,因此,lenneth 僅僅只是作了一層封裝,繁簡天然。