titbit-loader

loader for titbit framework

Usage no npm install needed!

<script type="module">
  import titbitLoader from 'https://cdn.skypack.dev/titbit-loader';
</script>

README

titbit-loader

针对titbit框架的自动加载工具,用于自动创建并加载controller以及middleware的场景。也可以自动加载model。

基于此可实现MVC或类MVC的结构,并可以快速开发接口,生成RESTFul风格的API,路由映射文件等操作。

默认情况,会在当前目录创建controller、middleware、model目录。之后就可以在controller中编写class。

titbit-loader只是做了应该手动设定路由和安排中间件的部分,把这部分自动化了,在服务运行后,titbit-loader的作用就结束了。

此扩展从一开始,不是为了开发单体复杂的软件准备的,只是为了解决在中小规模的应用上,可以方便组织代码结构。但是,如果要加载成百上千个Model、路由、中间件组装成一个复杂的应用也没有问题。

对应关系

  • controller中的文件路径映射为路由。

  • model目录中的模块初始化后挂载到app.service上,文件名字即为属性名。

  • middleware目录中的中间件扩展在controller目录中通过__mid.js或js文件中的__mid函数通过配置的方式进行编排。

初始化选项中的mname用于指定在app.service哪个子对象上挂载model,在v22.0.0以后,默认直接挂载到app.service上。

使用titbit-loader需要先安装titbit框架:

const titbit = require('titbit');
const tbloader = require('titbit-loader');

const app = new titbit({
  debug: true        
});

var tbl = new tbloader();

tbl.init(app);

app.run(2022);

controller目录中class示例:

//假设存在文件test.js,那么路径就是/test开头。

'use strict';

class test {
  
  constructor () {
    //默认参数是this.param = '/:id'。
    //可以通过设置this.param来指定参数。
    //this.param = '/:name/:key';

    //this.param = '';表示不带参数。
  }

  /*
    对应HTTP请求类型,有同名小写的方法名称处理请求,可以不写,需要哪些请求就写哪些。
    这里只使用了GET、POST、DELETE请求。
  */

  async get(c) {
    c.res.body = 'test ok:' + c.param.id;
  }

  //注意POST请求表示创建资源,默认加载时是不带参数的,也就是发起POST请求对应的路由是/test。
  async post(c) {
    c.res.body = c.body;
  }

  async delete(c) {
    c.res.body = 'delete ok';
  }

}

module.exports = test;

加载model

默认加载的model的名字就是文件名,没有.js。并且都在app.service.model对象中。但是你可以传递mname选项更改model的名字,或者设置选项directModel为true让model文件直接挂载到app.service上。

controller中不要写太复杂的业务逻辑,这部分你应该放在model中,对于model,如何封装,是否再分层都可以自定义。titbit-loader只是加载并放在app.service中,仅此而已。

const titbit = require('titbit');
const tbloader = require('titbit-loader');
const dbconfig = require('./dbconfig');

//postgresql数据库的扩展
const pg = require('pg');

var app = new titbit({
  debug: true        
});


var tbl = new tbloader({
  //默认就是true,默认通过app.service.model可以获取。
  loadModel: true, 
  //设置了mdb,在你的model文件中初始化时会传递此参数。
  mdb: new pg.Pool(dbconfig),
  //设置了mname,则要通过app.service.m获取。
  mname: 'm'
});

tbl.init(app);

app.run(2022);

model挂载到app.service

默认情况下,mname选项为null,这表示把初始化的model实例挂载到app.service。若要挂载到app.service的属性上,比如挂载到app.service.model上,则可以通过选项mname指定属性为model。在请求上下文中,可以通过c.service访问,c.service指向app.service。这种依赖注入方式在titbit框架的文档中有说明。


const titbit = require('titbit');
const tbloader = require('titbit-loader');
const dbconfig = require('./dbconfig');

//postgresql数据库的扩展
const pg = require('pg');

var app = new titbit({
  debug: true        
});

var tbl = new tbloader({
  //默认就是true,默认通过app.service.model可以获取。
  loadModel: true, 
  mdb: new pg.Pool(dbconfig),
});

tbl.init(app);

app.run(2022);

指定主页文件

你应该已经注意到了,因为文件要映射路径,所以,对于主页来说,需要添加的'/'路径是不能在文件名中体现的,所以需要指定一个文件,并添加get方法作为主页。


const titbit = require('titbit')
const tbloader = require('titbit-loader')

var app = new titbit({
  debug: true
})

var tbl = new tbloader({
  //只有GET请求,主页不允许其他请求
  homeFile : 'home.js',

  //如果要指定子目录的文件,则要使用这样的形式
  //homeFile : 'user/home.js'
});

tbl.init(app)

app.run(2022)

如果你不想让homeFile起作用,则只需要给一个空字符串,默认homeFile选项就是一个空字符串。

指定加载目录

const titbit = require('titbit');
const tbloader = require('titbit-loader');

var app = new titbit({
  debug: true
});

var tbl = new tbloader({
  //相对于程序所在目录,相对路径会自动计算转换为绝对路径。
  //如果指定目录下没有对应目录,会自动创建controller、model、middleware
  appPath : 'app1'
});

tbl.init(app);

app.run(2022);

POST请求的路由参数

默认在处理路由映射时,POST请求表示创建资源,不会带有参数,如果需要传递参数,需要通过在controller类中使用this.postParam属性指定。


class api {

  constructor () {
    this.param = '/:name/:id'
    //给post请求添加参数路由。
    this.postParam = '/:name'
  }

  async get(c) {

  }

  async post (c) {

  }

}

module.exports = api

加载中间件

middleware目录存放的是中间件模块,但是不会每个都加载,需要你在controller中进行设置,配置文件为__mid.js。注意controller中的__mid.js表示对全局开启中间件,controller中的子目录中存在__mid.js表示只对当前目录分组启用,所见即所得,简洁直观高效。

之所以能够按照分组加载执行,其本质不在于titbit-loader本身,而是titbit提供的中间件分组执行机制。因为titbit提供了路由分组功能,并且可以指定中间件严格匹配请求方法和路由名称,所以基于此开发扩展就变得很方便。

controller/:
    __mid.js    //对全局开启
    
    test.js

    api/:
      __mid.js  //只对/api分组启用
      ...

    user/:
      __mid.js  //只对/user分组启用
      ...

    ...

__mid.js示例:

//导出的必须是数组,数组中的顺序就是执行顺序,name是middleware目录中文件的名字,不需要带.js
module.exports = [
  {
    name : 'cors',
    //表示要在接收body数据之前执行
    pre: true
  },
  {
    name : 'apilimit'
  }
];

加载中间件类

如果你的中间件模块是需要new操作的,不是一个直接执行的中间件函数,则可以使用@指定,同时要提供一个middleware函数。

module.exports = [
  {
    //@开头表示模块是类,需要初始化,并且要提供middleware方法,
    //这时候加载时会自动初始化并加载middleware函数作为中间件,
    //并且会绑定this,你可以在中间件模块的middleware函数中比较放心的使用this。
    name : '@apilimit'
  }

];

直接指定中间件

在 v21.3.0版本开始,可以通过middleware属性直接指定中间件。


//文件__mid.js

let mt = async (c, next) => {
  console.log(`mt run ${(new Date()).toLocaleString()}`)
  await next()
}

module.exports = [
  {
    middleware: mt
  }
]

只加载model并指定model路径

可以通过modelPath设定model所在目录,并通过loadModel加载。


const titbit = require('titbit')
const tbloader = require('titbit-loader')

const app = new titbit({
  debug: true
})

var tbl = new tbloader({
  modelPath : 'dbmodel',
  //指定挂载到app.service.dm上,这会创建dm对象并进行挂载。
  mname : 'dm',
})

//只是加载model类。
tbl.loadModel(app)

app.run(1234)

指定中间件的加载环境

如果要区分开发模式还是发布模式,并根据不同情况加载中间件,可以使用mode属性,这个功能在v21.4.0开始支持。

mode有2个可选的值:test | dev。都表示在对应的开发环境才会加载。没有mode,则会直接加载,不做任何区分。

mode为 online 则表示只有在生产环境才会加载执行,开发测试模式不会加载。

这个属性只是指定了加载条件,而对于条件的检测,是titbit框架的实例的service.TEST 或者 service.DEV属性是否存在并为true。


//文件__mid.js 

let mt = async (c, next) => {
  console.log('dev test -- ', c.method, c.path, c.routepath)
  await next()
}

module.exports = [
  {
    name : 'api-log',
    mode : 'test'
  },

  {
    middleware : mt,
    mode : 'dev'
  },

  {
    name : 'api-limit',
    mode : 'online'
  }

]

这个功能是具备开发性质的,就是这需要你在titbit服务中,只要设置了以下配置:

const app = new titbit()

//相当于app.service.TEST = true
app.addService('TEST', true)

这就表示,会开启测试模式(开发模式)。这个时候,不仅titbit-loader会检测并确定是否加载中间件,还可以在请求上下文中知道应用运行在开发模式。

高级功能

这部分功能相对要麻烦点,但是可以应对比较复杂的情况。

分组的名称

如果通过输出测试可以看到中间件分组,只是比较麻烦,在titbit-loader加载时,采用了非常简单的机制,controller所在目录,即为根分组,名字是'/'。其他都是目录名字作为分组名称,但是都以/开头。

比如以下目录结构:

controller/:
  a.js
  ...
  api/:
    user.js
    ...
  admin/:
    user.js
    ...

a.js所在分组是/。user.js所在分组是/api,这样,不通过titbit-loader加载的中间件,也可以指定分组,可以对相关分组生效。

加载中间件时传递参数

对于中间件是class的情况,有时还需要传递参数,这时候,可以通过__mid.js中的args属性来指定:

module.exports = [
  {
    name : '@apilimit',
    args : {
      maxLimit: 100,
      timeout: 56000
    }
  }

]

这在初始化apilimit实例时,会传递args参数。

只对文件中的某些请求启用中间件

比如,有controller/a.js文件,只对其中的post和put请求启用限制body大小的中间件,则可以在class中提供__mid函数:


class a {

  constructor () {

  }

  async get (c) {
    //...
  }

  async post (c) {
    //...
  }

  async put (c) {
    //...
  }

  __mid () {
    return [
      {
        name : 'setMaxBody',
        pre: true,
        //只对post和put函数启用,而且只有请求/a路径时才会生效。
        path : ['post', 'put']
      }
    ]
  }

}

不导出controller和model

在controller和model目录中的文件,如果不想导出,则可以命名文件开头加上!(英文符号)。这时候会忽略此文件。对于model来说,以!和_开头的文件都不会导出,以_开头的文件可以作为model的公共模块。

导出controller中的某些分组

通过subgroup选项可以指定要加载哪些目录下的路由文件,注意这时候若要对controller目录中的文件也加载,要在subgroup数组中包括空字符串或 '/',比如在controller中存在三个目录和文件:

abc/ bcd/ xyz/ a.js

如果只想加载xyz 和 a.js则可以这样做:


var app = new titbit({
  debug: true
});

var tbl = new tbloader({
  subgroup: ['xyz', '']
});

tbl.init(app);

这时候会加载xyz目录中的文件以及a.js。

对于大规模应用来说,你最好是进行服务拆分,这个时候,titbit+titbit-loader组成一个服务处理业务,然后再把多个这样的应用组合完成更大规模的处理。



separate模式

从v22.0.1开始,默认启用separate模式,也就是说多个controller中的全局中间件多次加载不会冲突,比如通过传递prepath选项设置路径前缀和没有前缀的两个实例加载都不会冲突。全局中间件会对当前所有分组启用中间件,但是不会扩散到全局。

完整选项

选项 说明 默认值
appPath 指定要加载的路径 默认为调用扩展的文件所在路径。
controllerPath 指定要加载的控制器目录 默认为controller
modelPath 指定要加载的模型目录 默认为model
midwarePath 指定中间件所在目录 默认为middleware
separate 是否采用分离模式 默认为true
optionsRoute 是否自动设定OPTIONS路由 默认为true
prepath 路由前缀 默认为空字符串
initArgs controller中初始化类要传递的参数 默认为null,表示不传递。
homeFile 首页的文件 默认为空字符串
modelNamePre 模型挂载时,名字的前缀。 默认为空字符串
mname 模型所在的app.service上的属性名字 默认为空,表示直接挂载到app.service上。
multi 是否允许多次加载 默认为false