lenneth

    0.3.1 • Public • Published

    lenneth(蕾娜斯)

    说明

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

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

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

    安装

    yarn add lenneth
    # or 
    npm install lenneth

    也可以客户端安装

    # lic 全局安装 
    npm install -g lenneth-cli
     
    # 初始化工程目录 
    lenneth init yourApp

    瞄一眼

    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 browser

    http://localhost:8081
    

    lenneth 核心

    koa 最精髓的就是它的洋葱模型,而洋葱模型的元素就是一个一个的中间件,lenneth 的封装就是将普通的类方法转化成 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 指向原来的类。

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

    各个模块

    • application

    入口文件处,使用 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());
      }
    }
    • interceptor

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

    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();
      }
    }
    • response

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

    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

    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

    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();
      }
    }
    • service

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

    export class UserService {
      addUser(userInfo: IUserInfo) {
        return userInfo;
      }
     
      getUserInfo(id: string) {
        return {
          name: "zhangsan",
          age: 30
        };
      }
    }

    单元测试

    yarn test

    案例

    lenneth-demo

    项目地址

    lenneth

    脚手架工具

    lenneth-cli

    说在最后

    当初做项目的时候,在 github 上搜过一个项目,是基于 express 的--ts-express-decorators,里面有很多不错的设计,lenneth 里的服务启动生命周期就是照搬其中的。不过我不喜欢把 node 弄得那么大,那么全,而且,koa 本身就是一个极简的应用,所以,lenneth 仅仅只是做了一层封装,繁简自然。

    Install

    npm i lenneth

    DownloadsWeekly Downloads

    1

    Version

    0.3.1

    License

    MIT

    Unpacked Size

    107 kB

    Total Files

    206

    Last publish

    Collaborators

    • avatar