0.4.3 • Published 7 years ago

leibniz v0.4.3

Weekly downloads
-
License
MIT
Repository
-
Last release
7 years ago

Leibniz

CircleCI codecov NodeVersion Dependencies DevDependencies Release License

面向 ES2018 的 Node.js 框架

如果要使用此框架,需使用与框架相同的 Babel 配置

特性

  1. 使用最新 ES2017/ES2018 的语法特性

  2. 基于 Koa2, 兼容 Koa2 全部中间件

  3. 约定优于配置

  4. 可扩展, 可自定义 loader 或覆盖框架自带 loader

  5. 在 sequelize ORM 基础上进行修改. 并与框架进行整合, 简化了 model 定义和书写, 能自动推导 mock 数据方法

  6. 依赖注入, 简化书写

例子

你可以参考该项目 onlinejudge-backend 来查看使用该框架后的样子

目录结构约定

  • config

    在该文件夹留下你的配置文件, 推荐使用 config 模块. 框架自带的 sequelizeLoader 会读取该文件夹内的数据库配置, 框架使用了 config 模块. 通过 config 模块你可以使用任意格式的配置文件.

  • controller

    基于装饰器将控制器和路由整合, 路由方面使用了 koa-joi-router, 对参数进行约束. controller 在应用启动时会通过 ControllerLoader 加载.

  • model

    ORM 使用了 sequelize 的最新 v4 版本. model 会在应用启动时通过 modelLoader 自动加载, 需要在配置中写入自己数据库信息.

  • service

    这部分是业务代码的书写地方, 控制器可以直接通过 ctx.service.xxx 方式调用.

  • loader

    您可以自定义 loader. loader 会按照 priority 从小到大顺序加载. 如果您的 loader 具有和框架提供 loader 相同的 priority, 那么您的 loader 将会覆盖框架的 loader. 一个 priority 只能有一个 loader 被调用.

  • index.js

    启动文件. 框架重写了 listen 方法, 在调用 listen 时, 框架会进行一系列初始化工作后再调用 koa2 内部的 listen 方法启动项目.

详细使用说明

Controller

在 Controller 中你可以使用以下装饰器:

  • Controller(prefix, ...middleware)
  • Route(path, method, ...middleware)
  • Get(path, ...middleware)
  • Post(path, ...middleware)
  • Head(path, ...middleware)
  • Put(path, ...middleware)
  • Patch(path, ...middleware)
  • Put(path, ...middleware)
  • Delete(path, ...middleware)
  • Validate(validation)

@Controller 只能装饰类, 每个 Controller 都需要使用 @Controller 进行装饰, 其中, prefix 将作为后面所有路由的前缀. middleware 将作为后面所有路由的前置中间件.

@Validatevalidation 接收 Joi 语法对象. 具体用法可以参考例子以及 Joi 的 API 文档.

一个简单的 Controller 如下:

// controller/post.js
import { Post, Controller, Validate, Joi } from 'Leibniz'

@Controller('/post', middleware1, middleware2)
class Post {
  @Post('/:id', middleware3, middleware4)
  @Validate({
    params: {
      id: Joi.number().integer()
    },
    type: 'json',
    body: {
      title: Joi.string().required(),
      author: Joi.string().required()
    }
  })
  async create (ctx) {
    const data = await ctx.service.post.save(id, ctx.request.body)
    ctx.ok(null, '创建成功')  // { success: true, data: null, message: '创建成功' }
  }
}

Service

Service 是一个类, 你可以直接在类中定义属性, 而不只只是方法. 类的文件名会作为类实例的名字, 可以通过 ctx.service.fileName 调用. 同时支持多层文件夹嵌套, 遇到文件夹的情况, 可以通过 ctx.service.dirName.fileName 调用, 多层嵌套同理.

一个简单的 Service 如下:

// service/post.js
export default class PostService {
  constructor (Post) {
    // 所有的 Model 都可以通过依赖注入获取得到, 如果你不想这么做,
    // 也可以通过全局变量 Database 来得到该 Model, 此处为 Database.Post
    // 你可以注入更多依赖然后在 constructor 中获取得到
    this.PostModel = Post
  }

  async show (id) => {
    return await this.PostModel.findById(id) // 与下面同效
    // return await Database.Post.findById(id)
  }
}

Model

Leibniz 集成了 Sequelize ORM, 使用了最新的 v4 版本. 你可以像下面这样定义一个 Model:

// model/post.js
class Post {
  // model 的配置
  static options = {
    paranoid: true,
    tableName: 'Post' // 默认下, Post 即为这个 Model 对应的表名, 你可以在 options 进行更改
  }

  // model 表
  static fields (DataTypes) {
    // 此处 DataTypes 已被魔改
    // 同时, 框架会自动推导出默认 random 方法如下注释
    // 用户自定义的 random 方法会和自带的一同调用后返回合并的结果
    return {
      id: DataTypes.uuid().primary(),             // UUID.v1()
      title: DataTypes.string().allowNull(),      // Random.string()
      author: DataTypes.string().unique(),        // Random.string()
      test1: DataTypes.string(55).default('aaa'), // Random.string(55)
      test2: DataTypes.integer(),                 // Random.integer()
      test3: DataTypes.float().notNull(),         // Random.float()
      test4: DataTypes.double(),                  // Random.float()
      test5: DataTypes.string(254),               // Random.string(254)
      test6: DataTypes.boolean(),                 // Random.boolean()
      test7: DataTypes.integer(4),                // Random.integer(1000, 9999)
      test8: DataTypes.integer(2)                 // Random.integer(1000, 9999)
    }
  }

  // mock 数据, model 拥有 mock 方法, 该 mock 方法会使用 random 返回的结果创建 mock 数据
  // model 初始化的时候, 框架会为检测 fields 所有键的类型并尝试生成 mock 数据
  // 但自带的 mock 数据可能不能满足你的需求, 你可以在此进行扩展
  static random (Random) {
    return {
      title: Random.cword(30),
      author: Random.cword(30)
    }
  }

  // 建立表关联, Book 是另一个 model
  static associate (Book) {
    this.belongsTo(Book)
  }

  // 非静态方法为实例拥有
  get aaaaa () {
    return this.title + this.author
  }

  set aaaaa (val) {
    this.title = val
    this.author = val
  }
}

export default Post

您可以使用 Model 装饰器并传入一些 model 的配置项来替代 options. 如果 options 提供了同 Model 装饰器相同的配置, Model 装饰器的配置优先级更高.

您可以直接在 random, fields, associate 三个方法的参数中任意使用 DataTypes, RandomModel. 这三个都通过依赖注入获得. 这样做的目的在于减少模块的引入. Random 来自于 mockjs 模块.

你可以不用写 random 方法, 因为 Model 会自动检测 fields 并生成 random 方法. 如果你写了, 那他会对 Model 生成的 mock 数据进行覆盖. 注意, 你不能调用 random 方法, Model 具有静态方法 mock, 使用该方法可以自动的生成 mock 数据, 他会调用 random 方法生成数据模板然后插入数据库中.

associate 中, 你可以直接使用其他 model 的 ModelName 作为函数参数, 这个也是通过依赖注入获取得到.

Loader

框架的核心是由 loader 组成的, 用户可以通过 loader 进一步扩展框架. 例如:

import path from 'path'
import glob from 'glob'
import 'reflect-metadata'

/**
 * 用户控制器 Loader
 */
@Reflect.metadata('priority', 30) // 可选
export default class ControllerLoader {
  static init (app) { // 通过 app 依赖注入获取得到 app 示例, 也可以输入 ctx 来获取 ctx. 两个只能输入其中一个.
    const controllerPath = path.resolve(app.__appname, 'controller')
    const controllerFiles = glob.sync(`${controllerPath}/**/*.js`)
    controllerFiles.forEach(file => app.use(require(file).default.prototype.router.middleware()))
  }
}

您可以在自己项目中的 loader 文件夹上创建多个 loader. 如有需要可以通过 @Reflect.metadata('priority', 30) 来指派优先级. 30 是内部 ControllerLoader 所使用的, 如果你也是用 30, 那么自带的 ControllerLoader 将不再被使用. 你也可以不指定 priority, 那么他们会在默认的 loader 执行完后执行.

每个 loader 都必须具备静态方法 init, 您可以在 init 中传入两种参数, 一个是 app 表示 leibniz 实例, 另一个是 ctx.

框架目前自带的 loader 和对应 priority 如下:

// AppLoader
ConfigLoader(app)       10
SequelizeLoader(app)    20
ModelLoader(app)        30
ControllerLoader(app)   40
// CtxLoader (ctxloader 在 apploader 全部执行完后执行, 他们的优先级是互不干扰的)
ServiceLoader(ctx)      90

index.js

项目入口文件和 Koa2 如出一辙, 没有什么变化. 但注意此处的 listen 方法已经被重写过. 结合上述的例子和该文件, 你可以成功启动这个项目(未测试), 可以看到, 在入口文件, 你不需要做任何的路由, model, 以及 service 的配置, 非常简便.

另外, 在任意地方, 你都可以通过 app.sequelize 或者 Database(全局) 来获取得到数据库.

import Leibniz from 'leibniz'

const app = new Leibniz()

app.listen(8000)

leibniz 导出了以下东西:

- App as default
- Joi
- @Authorization   鉴权装饰器, 检验 JWT
- @Controller      控制器装饰器, 所有控制器必须使用
- @Model           Model 装饰器, 配置 options 用, 更推荐通过 static options 的方式来处理
- @Validate        Joi 校验装饰器
- @Route @Get @Post @Head @Put @Patch @Delete 路由装饰器
- di               依赖注入, 具有 register, unregister, getParamNames, resolve 四个方法
- requireDirectory from ./util/require-directory 导入文件夹, 接收两个参数, 第一个参数为 glob 形式路径, 第二个参数是标志位, 标志位为 1 表示进行实例化, 默认只引入

test

关于测试, 可以参考该项目 onlinejudge-backend

简单的说, 你只要在 before 的地方 listen 就好. Leibniz 实例的 listen 方法是异步的, 你可以 await 它:

before(async () => {
  global.ctx = app.context   // Get ctx. then you can test service by ctx.service
  const server = await app.listen()  // this will do some init work then return koa.listen
  global.app = server  // get httpserver. then you can request it with supertest to test route
})

Standard - JavaScript Style Guide