架构
模块化设计
ThinkJS 基于 分组/控制器/操作
的设计原则,一个典型的 URL 如下:
http://hostname:port/分组/控制器/操作/参数名/参数值/参数名2/参数值 2?arg1=argv1&arg2=argv2
分组
一个应用下有多个分组,一个分组都是很独立的模块。比如:前台模块、用户模块、管理员模块控制器
一个分组下有多个控制器,一个控制器是多个操作的集合。如:商品的增删改查操作
一个控制器有多个操作,每个操作都是最小的执行单元。如:添加一个商品
项目中有哪些分组,需要在如下的配置中指定:
// 支持的分组列表
'app_group_list': ['Home', 'Admin'], // 表示有 Home 和 Admin 2 个分组
默认是哪个分组,可以在下面的配置中指定:
// 默认分组
'default_group': 'Home', // 你可以将默认分组改成合适的,如:Blog
CBD 模式
ThinkJS 使用 CBD(核心 Core+ 行为 Behavior+ 驱动 Driver) 的架构模式,核心保留了最关键的部分,并在重要位置添加了 切面
,其他功能都是以驱动的方式来完成。
核心 (Core)
ThinkJS 的核心部分包含通用函数库、系统默认配置、核心类库等组成,这些都是 ThinkJS 必不可少的部分。
lib/Common/common.js 通用函数库
lib/Common/extend.js js 原生对象的扩展
lib/Common/function.js 框架相关的函数库
lib/Conf/alias.js 系统类库别名,加载时使用
lib/Conf/config.js 系统默认配置
lib/Conf/debug.js debug 模式下的配置
lib/Conf/mode.js 不同模式下的配置
lib/Conf/tag.js 每个切面下的行为
lib/Lib/Core/App.js 应用核心库
lib/Lib/Core/Controller.js 控制器基类
lib/Lib/Core/Db.js 数据库基类
lib/Lib/Core/Dispatcher.js 路由分发类
lib/Lib/Core/Http.js 封装的 http 对象类
lib/Lib/Core/Model.js 模型基类
lib/Lib/Core/Think.js 框架类
lib/Lib/Core/View.js 视图类
lib/Lib/Util/Behavior.js 行为基类
lib/Lib/Util/Cache.js 缓存基类
lib/Lib/Util/Cookie.js cookie 类
lib/Lib/Util/Filter.js 数据过滤类
lib/Lib/Util/Session.js session 基类
lib/Lib/Util/Valid.js 验证类
行为 (Behavior)
行为是 ThinkJS 扩展机制中一项比较关键的扩展,行为可以独立调用,也可以整合到标签 (tag) 里一起调用,行为是执行过程中一个动作或事件。如:路由检测是个行为、静态缓存检测也是个行为。
标签 (tag) 是一组行为的集合,是在系统执行过程中切面处调用的。与 EventEmitter
不同,标签里的行为是按顺序执行的,当前的行为通过 Promise 机制控制后面的行为是否被执行。
系统标签位
当执行一个 http 请求时,会在对应的时机执行如下的标签位:
app_init
应用初始化path_info
解析 path 路径resource_check
静态资源请求检测route_check
路由检测app_begin
应用开始action_init
action 初始化view_init
视图初始化view_template
模版定位view_parse
模版解析view_filter
模版内容过滤view_end
视图结束action_end
action 结束app_end
应用结束
在每一个标签位置都可以配置多个行为,系统的标签位行为如下:
/**
* 系统标签配置
* 可以在 App/Conf/tag.js 里进行修改
* @type {Object}
*/
module.exports = {
// 应用初始化
app_init: [],
//pathinfo 解析
path_info: [],
// 静态资源请求检测
resource_check: ['CheckResource'],
// 路由检测
route_check: ['CheckRoute'],
// 应用开始
app_begin: ['ReadHtmlCache'],
//action 执行初始化
action_init: [],
// 模版解析初始化
view_init: [],
// 定位模版文件
view_template: ["LocationTemplate"],
// 模版解析
view_parse: ["ParseTemplate"],
// 模版内容过滤
view_filter: [],
// 模版解析结束
view_end: ['WriteHtmlCache'],
//action 结束
action_end: [],
// 应用结束
app_end: []};
除了系统的标签位行为,开发人员也可以根据项目的需要自定义标签位行为。
自定义标签位行为文件在 App/Conf/tag.js
。
行为定义
行为定义有 2 种方式,一种是一个简单的 function,一种是较为复杂些的行为类。
可以通过下面直接 function 的方式创建一个简单的行为,文件为 App/Conf/tag.js
:
module.exports = {app_begin: [
function(http){ // 会传递一个包装的 http 对象
if (http.group !== 'Home') {
return;
};
var userAgent = http.getHeader('user-agent').toLowerCase();
var flag = ["iphone", "android"].some(function(item){
return userAgent.indexOf(item) > -1;
})
if (flag) {
http.group = "Mobile";
}
}]
}
该行为的作用是:如果当前的分组是 Home 并且是手机访问,那么将分组改为 Mobile。这样就可以对同一个 url,PC 和 Mobile 访问执行不同的逻辑,输出不同的内容。
也可以继承行为基类来实现:
module.exports = Behavior(function(){
return {
run: function(){
var http = this.http; // 基类中的 init 方法会自动把传递进来的 http 对象放在当前对象上。
if (http.group !== 'Home') {
return;
};
var userAgent = http.getHeader('user-agent').toLowerCase();
var flag = ["iphone", "android"].some(function(item){
return userAgent.indexOf(item) > -1;
})
if (flag) {
http.group = "Mobile";
}
}
}
})
将内容保存在 App/Lib/Behavior/AgentBehavior.js
文件中,并在 App/Conf/tag.js
中配置如下的内容:
module.exports = {
app_begin: ["Agent"]
}
使用哪种方式来创建行为可以根据行为里的逻辑复杂度来选择。
行为执行顺序
默认情况下,自定义的行为会和系统的行为一起执行,并且自定义行为是追加到系统行为之后的。
如果想更改行为执行的顺序,可以通过下面的方式:
app_begin: [true, 'Agent']
将数组的第一个值设置为 true, 表示自定义行为替换系统默认的行为,那么系统的默认行为则不在执行。app_begin: [false, 'Agent']
将数组的第一个值设置为 false, 表示自定义行为放在系统的默认行为之前执行。
自定义标签位执行和行为执行
上面提到的标签位和行为会在 http 执行过程中自动被调用,同时标签和行为也可以手工调用:
// 执行 check_auto 标签位,
//http 为包装的 http 对象,在整个 http 执行过程中都可以获取到
//data 为传过去的数据, 如果行为里需要的是多个数据,那么这里应该传递个数组
tag("check_auth", http, data);
// 执行 Agent 这个行为
B("Agent", http, data);
驱动 (Driver)
除了核心和行为外,ThinkJS 里的很多功能都是通过驱动来实现的,如:Cache, Session, Db 等。
驱动包括:
lib/Lib/Driver/Cache
缓存驱动lib/Lib/Driver/Db
数据库驱动lib/Lib/Driver/Session
Session 驱动lib/Lib/Driver/Socket
Socket 驱动lib/Lib/Driver/Template
模版引擎驱动
如果有些功能框架里还没实现,如:mssql 数据库,那么开发人员可以在项目里 App/Lib/Driver/Db/
里实现。
自动加载
Node.js 里虽然提供了 require 来加载模块,但对于应用内的文件加载并没有给出快捷的加载方式。为此 ThinkJS 实现了一套快速加载机制,这些快速加载的文件包含:
- 系统核心文件
- 行为文件
- 各种驱动文件
通过全局函数 thinkRequire
来调用。例如:
// 调用这些文件时会自动到对应的一些目录下查找
var db = thinkRequire("mssqlDb");
var model = thinkRequire("userModel");
var behavior = thinkRequire("AgentBehavior");
这些文件具体包含:xxxBehavior
, xxxModel
, xxxController
, xxxCache
, xxxDb
, xxxTemplate
, xxxSocket
, xxxSession
如果是这些之外的文件通过 thinkRequire
加载,那么会调用系统 require 函数。
系统流程
系统的执行流程分为启动服务和响应用户请求 2 块:
启动服务
- 通过
node index.js
启动 - 调用系统入口文件
think.js
- 常量定义,获取 ThinkJS 的版本号
- 加载
lib/Lib/Core/Think.js
文件,调用 start 方法 - 加载系统的函数库、系统默认配置
- 捕获异常
- 加载项目函数库、配置文件、自定义路由配置、行为配置、额外的配置文件
- 合并 autoload 的查找路径列表,注册 autoload 机制
- 记录当前 Node.js 的进程 id
- 加载
lib/Lib/Core/App.js
文件,调用 run 方法 - 识别是否使用 cluster,开启 http 服务
响应用户请求
- 用户发生了 url 访问
- 执行标签位
form_parse
- 发送
X-Powered-By
响应头信息,值为 ThinkJS 的版本号 - 执行标签位
app_init
- 执行标签位
resource_check
,判断当前请求是否是静态资源类请求。静态资源类请求执行标签位resource_output
- 执行标签位
path_info
,获取修改后的 pathname - 执行标签位
route_check
,进行路由检测,识别对应的 Group, Controller, Action - 执行标签位
app_begin
,检测当前请求是否有静态化缓存 - 执行标签位
action_init
,实例化 Controller - 调用
__before
方法,如果存在的话 - 调用对应的 action 方法
- 调用
__after
方法,如果存在的话 - 执行标签位
view_init
,初始化模版引擎 - 执行标签位
view_template
, 查到模版文件的具体路径 - 执行标签位
view_parse
,解析模版内容 - 执行标签位
view_filter
,对解析后的内容进行过滤 - 执行标签位
view_end
,模版渲染结束 - 执行标签位
app_end
, 应用调用结束