0.0.2 • Published 2 years ago

kirinriki v0.0.2

Weekly downloads
-
License
private
Repository
-
Last release
2 years ago

weasel

说明

封装 weasel 旨在快速方便的搭建出一个 node web 应用,不过度封装也不随波逐流,koa 的 node 是简单的,weasel 也是。

基于 ES6+typescript 的一些特性,做了一些类似 spring 的注解,对开发本身不增加复杂度,并且不失 koa 的灵活性。 weasel 内部已经集成了 koa, koa-bodyparser, koa-router 这 3 个基础库,已经满足了大部分的开发,如果需要引入其他的中间件,可以在入口文件中引入。

weasel 抽象了 3 个模块,分别是 controller,middleware,service,内部不接入任何 db 和 cache。

todo

修改注解函数名称 类型 T = Type 接口 I = InterFace 键值 VD/F = value Decorator 类型 CD/C = Class Decorator 参数 PD/P = Params Decorator 函数 FD/F = Function Decorator

安装

yarn add weasel
# or
npm install weasel

也可以客户端安装

# lic 全局安装
npm install -g weasel-cli

# 初始化工程目录
weasel init yourApp

瞄一眼

import { weaselApplication, ServerSettings, Iweasel } from "weasel";

@ServerSettings({
    port: 8081,
})
class App extends weaselApplication implements Iweasel {
    $onMountingMiddlewares() {
        this.use(async (ctx, next) => {
            ctx.body = "hello world";
        });
    }
}

new App().start();

open in browser

http://localhost:8081

weasel 核心

koa 最精髓的就是它的洋葱模型,而洋葱模型的元素就是一个一个的中间件,weasel 的封装就是将普通的类方法转化成 koa 的中间件

  /**
   * 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);
  }

转换函数

这个方法就是将上述的类方法转成 koa 的中间件。在类方法的上层封装了一个 koa 的中间件方法,在这个方法内部自动执行类方法,并将这个方法的 this 指向原来的类。

// weasel封装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);
    };
};

各个模块

  • application

入口文件处,使用 ServerSettings 修饰,里面的参数都是一些全局方法,如 interceptor,response 等,这些都是一个 middleware,weasel 只是依照 koa 的洋葱模型调整了他们的执行顺利

@ServerSettings({
    port: 8081,
    // controller
    imports: {
        "/apis": UserController,
    },
    // 拦截器
    interceptor: Interceptor,
    // 返回值封装
    response: AppResponse,
    // error事件捕获
    globalError: GlobalError,
})
class App extends weaselApplication implements Iweasel {
    $onMountingMiddlewares() {
        this.use(logger());
    }
}
  • interceptor

其实也是一个中间件,只不过在最前执行

import { IMiddleware, Middleware, ParamHeader, Next, TNext } from "weasel";

@Middleware()
export class Interceptor implements IMiddleware {
    async use(@ParamHeader() header: any, @Next() next: TNext) {
        console.log("Interceptor", header);
        await next();
    }
}
  • response

中间件,在最后执行,默认开启,可以覆盖

import { IMiddleware, IContext, TResponse, TNext } from "@interfaces";
import { Middleware, Response, Next } from "@decorators";
import { HttpStatus, ResponseStatus } from "@common";
import { weaselError } from "./weasel-error";
@Middleware()
export class weaselResponse 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 weaselError) {
                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

controller 主要是设置 router 和注入 services

router 的修饰器有 Post,Get 等,params 参数的获取同 spring,注入 service 使用修饰器 Autowired,这个也和 spring 一致

import {
    Controller,
    Autowired,
    Post,
    Get,
    RequestBody,
    PathVariable,
    Response,
    TResponse,
    UseBefore,
    Description,
} from "weasel";
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

middleware 本质上其实就是 koa 的中间件,只不过我在此基础上又抽象出一层方法来引入获取 params 的方法,用来方便开发

在 controller 每个 api 上,使用 UseBefore 修饰器即可使用这些 middleware,在运行期,middleware 先于 controller 定义的接口,如果 middleware 没有调用 next 函数,则不会调用下一个中间件(kao 洋葱模型)

参数获取拦截器应用于必须参数 非必须参数请通过Request获取

import { IMiddleware, Middleware, Next, TNext, ParamHeader } from "weasel";

@Middleware()
export class UserAuth implements IMiddleware {
    async use(@ParamHeader() headers: any, @Next() next: TNext) {
        await next();
    }
}

@Middleware()
export class RuleAuth implements IMiddleware {
    async use(@ParamHeader() headers: any, @Next() next: TNext) {
        await next();
    }
}
  • service

这个模块只是做一个类输出方法

export class UserService {
    addUser(userInfo: IUserInfo) {
        return userInfo;
    }

    getUserInfo(id: string) {
        return {
            name: "zhangsan",
            age: 30,
        };
    }
}

单元测试

yarn test

说在最后

ts-express-decorators weasel 里的服务启动生命周期就是照搬其中的 weasel 仅仅只是做了一层封装,繁简自然。

lenneth