titbit-loader
v22.6.8
Published
loader for titbit framework
Downloads
351
Maintainers
Readme
titbit-loader
针对titbit框架的自动加载工具,用于自动创建并加载controller以及middleware的场景。也可以自动加载model。
基于此可实现MVC或类MVC的结构,并可以快速开发接口,生成RESTFul风格的API,路由映射文件等操作。
默认情况,会在当前目录创建controller、middleware、model目录。之后就可以在controller中编写class。
titbit-loader只是做了应该手动设定路由和安排中间件的部分,把这部分自动化了,在服务运行后,titbit-loader的作用就结束了。
此扩展从一开始,不是为了开发单体复杂的软件准备的,只是为了解决在中小规模的应用上,可以方便组织代码结构。但是它现在功能十分强大,在保持健壮性的同时,可以加载的控制器、模型数量没有限制,并且针对开发过程中的问题和需求几乎都提供了解决方案。比如:
指定加载哪些控制器。
命名!开头的控制器、目录和model不加载,方便保留代码但封禁部分功能。
命名 _ 开头的model不加载,这方便你在model目录引入共享模块。
路由前缀。
自动化编排中间件。
根据TEST模式或DEV模式或正式环境决定加载哪些中间件。
加载控制后如果存在init函数自动运行并传递指定的参数。
加载Model可以根据是否为构造函数进行不同的加载过程。
自定义控制器参数。
controller具备独立性,在内部启用的全局中间件不会扩展到整个应用,因此加载多个controller相互不会影响。
对应关系
controller中的目录和文件名称映射为路由。
model目录中的模块初始化后挂载到app.service上,文件名字即为属性名。
middleware目录中的中间件扩展在controller目录中通过__mid.js 或 js文件中的 __mid 函数通过配置的方式进行编排。
路由规则
默认它按照RESTful规范进行路由的加载过程,比如controller目录中存在admin/account.js文件,那么生成的路由和对应的类方法如下:
GET /admin/account/:id 对应于 async get(c) 方法,获取具体的账户信息
GET /admin/account/:id 对应于 async list(c) 方法,获取列表
POST /admin/account 对应于 async post(c) 方法,创建管理员用户
PUT /admin/account/:id 对应于 async put(c) 方法,更新用户信息
DELETE /admin/account/:id 对应于 async delete(c) 方法,删除用户
:id参数是account.js类中通过this.param指定的,这部分开发者可以自定义参数。
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
初始化选项中的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
});
let 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上。
如果模型文件不是一个构造函数,则仅仅把导出的实例返回,否则就会自动进行new操作并传递mdb参数。
目前,titbit-loader不支持ES6模块的导出,请使用exports或module.exports进行导出操作。
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');
let app = new titbit({
debug: true
});
let 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);
如果导出模块不是构造函数,比如是一个object或箭头函数,此时就只是返回这个导出结果,但是如果它存在init属性并且是一个函数,则会执行一次init函数,并传递mdb参数。
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');
let app = new titbit({
debug: true
});
let 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')
let app = new titbit({
debug: true
})
let 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');
let app = new titbit({
debug: true
});
let tbl = new tbloader({
//相对于程序所在目录,相对路径会自动计算转换为绝对路径。
//如果指定目录下没有对应目录,会自动创建controller、model、middleware
appPath : 'app1'
});
tbl.init(app);
app.run(2022);
加载中间件
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
})
let 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则可以这样做:
const app = new titbit({
debug: true
});
let tbl = new tbloader({
subgroup: ['xyz', '']
});
tbl.init(app);
这时候会加载xyz目录中的文件以及a.js。
对于大规模应用来说,你最好是进行服务拆分,这个时候,titbit+titbit-loader组成一个服务处理业务,然后再把多个这样的应用组合完成更大规模的处理。
mdbMap 指定多个模型关系
比如,你要对接读写分离的数据库服务。可以这样使用:
'use strict'
const Titloader = require('titbit-loader')
const Titbit = require('titbit')
const pg = require('pg')
let readorm = new pg.Pool({
host: '127.0.0.1',
database: 'read',
port:5432,
user: 'pt',
password: '222werrr'
})
let writeorm = new pg.Pool({
host: '127.0.0.1',
database: 'write',
port: 5432,
user: 'pt',
password: '222werrr'
})
const app = new Titbit()
let tbl = new Titloader({
loadModel: false,
mdbMap: {
read: {
mdb: readorm
},
write: {
mdb: writeorm
}
}
})
tbl.init(app)
app.run(1234)
mdbMap支持属性path指定不同的model目录,默认和普通model加载配置目录一致。mdb如果不设置则默认为null,不会使用全局mdb的配置。
mdbMap和mdb以及loadModel不冲突,如果不设置loadModel为false,仍然会加载model目录下的模型。
一些常量和service函数
在v22.1.2版本开始,加载完成后,会在app.service上添加几个常量:
__prepath__ 获取路由的前缀路径。
__appdir__ controller、model等目录所在的绝对路径。
__model__ 指向对应的模型对象,当mname为空,没有此属性,否则指向app.service[mname]。
modelMap(key: string)函数 获取mdbMap设定的key值指向的Model对象。
getModel(name, key='')函数 获取具体的Model实例,如果key值为空,则会在默认加载的Model上获取,否则会在mdbMap设定的加载关系上获取。
完整选项
| 选项 | 说明 | 默认值 | |----|----|----| | appPath | 指定要加载的路径 | 默认为调用扩展的文件所在路径。 | | controllerPath | 指定要加载的控制器目录 | 默认为controller | | modelPath | 指定要加载的模型目录 | 默认为model | | midwarePath | 指定中间件所在目录 | 默认为middleware | | optionsRoute | 是否自动设定OPTIONS路由 | 默认为true | | prepath | 路由前缀 | 默认为空字符串 | | initArgs | controller中初始化类要传递的参数 | 默认为null,表示不传递。 | | homeFile | 首页的文件 | 默认为空字符串 | | modelNamePre | 模型挂载时,名字的前缀。 | 默认为空字符串 | | mname | 模型所在的app.service上的属性名字 | 默认为空,表示直接挂载到app.service上。 | | multi | 是否允许多次加载 | 默认为false | | mdbMap | model映射关系 | 默认为null, 如果需要指定不同model的多个服务,需要使用此选项。比如,读写分离的两个数据库服务。 | | fileAsGroup | 以文件作为路由分组 | 从v22.3.0开始默认为true,设置为false回到之前的模式。|