# titbit-loader **Repository Path**: daoio/titbit-loader ## Basic Information - **Project Name**: titbit-loader - **Description**: titbit自动加载扩展,此扩展实现了类似的MVC模式,可以自动加载控制器类、模型、中间件,并实现中间件自动编排。同时还可以指定在开发或生产环境下加载不同的中间件。 - **Primary Language**: JavaScript - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 1 - **Created**: 2020-09-29 - **Last Updated**: 2025-07-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## 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文件,那么生成的路由和对应的类方法如下: ```javascript 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属性指定。 ```javascript 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框架: ```javaScript 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示例: ``` JavaScript //假设存在文件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中,仅此而已。** ```javaScript 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框架的文档中有说明。 ```javaScript 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方法作为主页。 ```javaScript 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选项就是一个空字符串。 ## 指定加载目录 ```javaScript 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提供了路由分组功能,并且可以指定中间件严格匹配请求方法和路由名称,所以基于此开发扩展就变得很方便。 ```javaScript controller/: __mid.js //对全局开启 test.js api/: __mid.js //只对/api分组启用 ... user/: __mid.js //只对/user分组启用 ... ... ``` __mid.js示例: ```javaScript //导出的必须是数组,数组中的顺序就是执行顺序,name是middleware目录中文件的名字,不需要带.js module.exports = [ { name : 'cors', //表示要在接收body数据之前执行 pre: true }, { name : 'apilimit' } ]; ``` #### 加载中间件类 如果你的中间件模块是需要new操作的,不是一个直接执行的中间件函数,则可以使用@指定,同时要提供一个middleware函数。 ```javaScript module.exports = [ { //@开头表示模块是类,需要初始化,并且要提供middleware方法, //这时候加载时会自动初始化并加载middleware函数作为中间件, //并且会绑定this,你可以在中间件模块的middleware函数中比较放心的使用this。 name : '@apilimit' } ]; ``` #### 直接指定中间件 在 v21.3.0版本开始,可以通过middleware属性直接指定中间件。 ``` JavaScript //文件__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加载。 ``` JavaScript 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。 ``` JavaScript //文件__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服务中,只要设置了以下配置: ``` JavaScript 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函数: ``` JavaScript 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则可以这样做: ``` JavaScript const app = new titbit({ debug: true }); let tbl = new tbloader({ subgroup: ['xyz', ''] }); tbl.init(app); ``` 这时候会加载xyz目录中的文件以及a.js。 > 对于大规模应用来说,你最好是进行服务拆分,这个时候,titbit+titbit-loader组成一个服务处理业务,然后再把多个这样的应用组合完成更大规模的处理。 ---- ---- ### mdbMap 指定多个模型关系 比如,你要对接读写分离的数据库服务。可以这样使用: ```javascript '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回到之前的模式。|