架构

模块化设计

thinkjs基于分组/控制器/操作的设计原则,一个典型的URL如下:

http://hostname:port/分组/控制器/操作/参数名/参数值/参数名2/参数值2?arg1=argv1&arg2=argv2

  • 分组 一个应用下有多个分组,一个分组都是很独立的模块。比如:前台模块、用户模块、管理员模块
  • 控制器 一个分组下有多个控制器,一个控制器是多个操作的集合。如:商品的增删改查
  • 操作 一个控制器有多个操作,每个操作都是最小的执行单元。如:添加一个商品

项目中有哪些分组,需要在如下的配置中指定:

  1. //支持的分组列表
  2. 'app_group_list': ['Home', 'Admin'], //表示有Home和Admin 2个分组

默认是哪个分组,可以在下面的配置中指定:

  1. //默认分组
  2. 'default_group': 'Home', //你可以将默认分组改成合适的,如:Blog

CBD模式

thinkjs使用CBD(核心Core+行为Behavior+驱动Driver)的架构模式,核心保留了最关键的部分,并在重要位置添加了切面,其他功能都是以驱动的方式来完成。

核心(Core)

thinkjs的核心部分包含通用函数库、系统默认配置、核心类库等组成,这些都是thinkjs必不可少的部分。

  1. lib/Common/common.js 通用函数库
  2. lib/Common/extend.js js原生对象的扩展
  3. lib/Common/function.js 框架相关的函数库
  4. lib/Conf/alias.js 系统类库别名,加载时使用
  5. lib/Conf/config.js 系统默认配置
  6. lib/Conf/debug.js debug模式下的配置
  7. lib/Conf/mode.js 不同模式下的配置
  8. lib/Conf/tag.js 每个切面下的行为
  9. lib/Lib/Core/App.js 应用核心库
  10. lib/Lib/Core/Controller.js 控制器基类
  11. lib/Lib/Core/Db.js 数据库基类
  12. lib/Lib/Core/Dispatcher.js 路由分发类
  13. lib/Lib/Core/Http.js 封装的http对象类
  14. lib/Lib/Core/Model.js 模型基类
  15. lib/Lib/Core/Think.js 框架类
  16. lib/Lib/Core/View.js 视图类
  17. lib/Lib/Util/Behavior.js 行为基类
  18. lib/Lib/Util/Cache.js 缓存基类
  19. lib/Lib/Util/Cookie.js cookie
  20. lib/Lib/Util/Filter.js 数据过滤类
  21. lib/Lib/Util/Session.js session基类
  22. 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 应用结束

在每一个标签位置都可以配置多个行为,系统的标签位行为如下:

  1. /**
  2. * 系统标签配置
  3. * 可以在App/Conf/tag.js里进行修改
  4. * @type {Object}
  5. */
  6. module.exports = {
  7. //应用初始化
  8. app_init: [],
  9. //pathinfo解析
  10. path_info: [],
  11. //静态资源请求检测
  12. resource_check: ['CheckResource'],
  13. //路由检测
  14. route_check: ['CheckRoute'],
  15. //应用开始
  16. app_begin: ['ReadHtmlCache'],
  17. //action执行初始化
  18. action_init: [],
  19. //模版解析初始化
  20. view_init: [],
  21. //定位模版文件
  22. view_template: ["LocationTemplate"],
  23. //模版解析
  24. view_parse: ["ParseTemplate"],
  25. //模版内容过滤
  26. view_filter: [],
  27. //模版解析结束
  28. view_end: ['WriteHtmlCache'],
  29. //action结束
  30. action_end: [],
  31. //应用结束
  32. app_end: []
  33. };

除了系统的标签位行为,开发人员也可以根据项目的需要自定义标签位行为。

自定义标签位行为文件在 App/Conf/tag.js

行为定义

行为定义有2种方式,一种是一个简单的function,一种是较为复杂些的行为类。

可以通过下面直接function的方式创建一个简单的行为,文件为App/Conf/tag.js

  1. module.exports = {
  2. app_begin: [function(http){ //会传递一个包装的http对象
  3. if (http.group !== 'Home') {
  4. return;
  5. };
  6. var userAgent = http.getHeader('user-agent').toLowerCase();
  7. var flag = ["iphone", "android"].some(function(item){
  8. return userAgent.indexOf(item) > -1;
  9. })
  10. if (flag) {
  11. http.group = "Mobile";
  12. }
  13. }]
  14. }

该行为的作用是:如果当前的分组是Home并且是手机访问,那么将分组改为Mobile。这样就可以对同一个url,PC和Mobile访问执行不同的逻辑,输出不同的内容。

也可以继承行为基类来实现:

  1. module.exports = Behavior(function(){
  2. return {
  3. run: function(){
  4. var http = this.http; //基类中的init方法会自动把传递进来的http对象放在当前对象上。
  5. if (http.group !== 'Home') {
  6. return;
  7. };
  8. var userAgent = http.getHeader('user-agent').toLowerCase();
  9. var flag = ["iphone", "android"].some(function(item){
  10. return userAgent.indexOf(item) > -1;
  11. })
  12. if (flag) {
  13. http.group = "Mobile";
  14. }
  15. }
  16. }
  17. })

将内容保存在App/Lib/Behavior/AgentBehavior.js文件中,并在App/Conf/tag.js中配置如下的内容:

  1. module.exports = {
  2. app_begin: ["Agent"]
  3. }

使用哪种方式来创建行为可以根据行为里的逻辑复杂度来选择。

行为执行顺序

默认情况下,自定义的行为会和系统的行为一起执行,并且自定义行为是追加到系统行为之后的。

如果想更改行为执行的顺序,可以通过下面的方式:

  • app_begin: [true, 'Agent'] 将数组的第一个值设置为true, 表示自定义行为替换系统默认的行为,那么系统的默认行为则不在执行。
  • app_begin: [false, 'Agent'] 将数组的第一个值设置为false, 表示自定义行为放在系统的默认行为之前执行。
自定义标签位执行和行为执行

上面提到的标签位和行为会在http执行过程中自动被调用,同时标签和行为也可以手工调用:

  1. //执行check_auto标签位,
  2. //http为包装的http对象,在整个http执行过程中都可以获取到
  3. //data为传过去的数据, 如果行为里需要的是多个数据,那么这里应该传递个数组
  4. tag("check_auth", http, data);
  5. //执行Agent这个行为
  6. 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来调用。例如:

  1. //调用这些文件时会自动到对应的一些目录下查找
  2. var db = thinkRequire("mssqlDb");
  3. var model = thinkRequire("userModel");
  4. 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,判断当前请求是否是静态资源类请求
  • 执行标签位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, 应用调用结束