90
汽車大全
ThinkJS 簡介
簡介
最近幾年,前端技術呈現出突飛勐進的發展,湧現出了一大批優秀的前端框架,今天給大家帶來的就是基於node的一款優秀的優秀的前端框架。之前一直用Express來搭建比較簡單的Node應用,但是對於相對複雜的應用來說,Express還是太輕量了。而作為一款優秀的國產前端框架,ThinkJS整合了大量的項目最佳實踐,讓企業級開發變得更加簡單、高效。從 3.0 開始,框架底層基於 Koa 2.x 實現,兼容 Koa 的所有功能。在最新的版本中,ThinkJS全麵支持ES6和ES7的語法規則。
特性
- 基於 Koa 2.x,兼容 middleware
- 內核小巧,支持 Extend、Adapter 等插件方式
- 性能優異,單元測試覆蓋程度高
- 內置自動編譯、自動更新機製,方便快速開發
- 使用更優雅的 async/await 處理異步問題,不再支持 */yield
- 從 3.2 開始支持 TypeScript
架構模型
環境搭建
借助 ThinkJS 提供的腳手架,可以快速的創建一個項目。需要注意的是使用ThinkJS框架需要Node 6.x以上環境的支持。大家ThinkJS環境需要用到如下的步驟:
安裝 ThinkJS 命令
npm install -g think-cli
安裝完成後,係統中會有 thinkjs 命令(可以通過 thinkjs -v 查看 think-cli 的版本號,此版本號非 thinkjs 的版本號)。
注:如果是從 2.x 升級,需要將之前的命令刪除,然後重新安裝。
卸載舊版本命令
npm uninstall -g thinkjs
創建項目
執行 命令“thinkjs new [project_name]” 來創建項目,如:
$ thinkjs new demo;
$ cd demo;
$ npm install;
$ npm start;
運行後可以看到相關的命令。
項目結構
默認創建的項目結構如下:
|--- development.js //開發環境下的入口文件
|--- nginx.conf //nginx 配置文件
|--- package.json
|--- pm2.json //pm2 配置文件
|--- production.js //生產環境下的入口文件
|--- README.md
|--- src
| |--- bootstrap //啟動自動執行目錄
| | |--- master.js //Master 進程下自動執行
| | |--- worker.js //Worker 進程下自動執行
| |--- config //配置文件目錄
| | |--- adapter.js // adapter 配置文件
| | |--- config.js // 默認配置文件
| | |--- config.production.js //生產環境下的默認配置文件,和 config.js 合並
| | |--- extend.js //extend 配置文件
| | |--- middleware.js //middleware 配置文件
| | |--- router.js //自定義路由配置文件
| |--- controller //控製器目錄
| | |--- base.js
| | |--- index.js
| |--- logic //logic 目錄
| | |--- index.js
| |--- model //模型目錄
| | |--- index.js
|--- view //模板目錄
| |--- index_index.html
|--- www
| |--- static //靜態資源目錄
| | |--- css
| | |--- img
| | |--- js
2.x 到3.x的變化
由於2.x到3.x對接口改動較大,所以建議直接刪除掉之前的版本,然後重新安裝信息的版本。2.x到3.x變化的內容有:
核心變化
3.0 拋棄了 2.x 的核心架構,基於 Koa 2.x 版本構建,兼容 Koa 裏的所有功能。主要變化為:
- 之前的 http 對象改為 ctx 對象
- 執行完全改為調用 middleware 來完成
- 框架內置的很多功能不再默認內置,可以通過擴展來支持
啟動方式
2.x 中項目啟動時,會自動加載 src/bootstrap/ 目錄下的所有文件。3.0 中不再自動加載所有的文件,而是改為(分為兩種情況):
- 在 Master 進程中加載 src/boostrap/master.js 文件;
- 在 Worker 進程中加載 src/boostrap/worker.js 文件;
配置
2.x 中會自動加載 src/config/ 目錄下的所有文件,3.0 中改為根據功能加載對應的文件。
hook 和 middleware
移除 2.x 裏的 hook 和 middleware,改為 Koa 裏的 middleware,middleware 的管理放在 src/config/middleware.js 配置文件中。2.x 下的 middleware 類無法在 3.0 下使用,3.0 下可以直接使用 Koa 的 middleware。
Controller
將基類 think.controller.base 改為 think.Controller,並移除 think.controller.rest 類。
Model
將基類 think.model.base 改為 think.Model。
View
模板的配置由原來的 src/common/config/view.js 遷移至 src/config/config.js 中,配置方法和之前基本一致。
其中老版本的 preRender() 方法已經廢棄,新方法名為 beforeRender()。nunjucks 模板引擎的參數順序由原來的 preRender(nunjucks, env, config) 修改為 beforeRender(env, nunjucks, config)。
阻止代碼執行
在新的語法規則中,為了實現阻止某些代碼的執行,對原來的語法進行了調整。移除了 think.prevent 等阻止後續執行的方法,替換為在 before、xxxAction、after 中返回 false 來阻止後續代碼繼續執行。
注:由於 3.0 改動了很多東西,所以不太容易基於原有項目代碼簡單修改來升級。建議使用新的腳手架工具創建項目,然後一一將之前的代碼拷貝到新項目中進行修改。
基礎概念
在介紹ThinkJS框架運行流程之前,我們先來看幾個比較重要的概念:
Context
Context,在大多數的編程語言中,被稱為上下文,也就是對象關聯關係的。Context 是 Koa 中處理用戶請求中的一個對象,貫穿整個請求生命周期。一般在 middleware、controller、logic 中使用,簡稱為 ctx。
例如,在 middleware 中使用 ctx 對象。
module.exports = options => {
// 調用時 ctx 會作為第一個參數傳遞進來
return (ctx, next) => {
...
}
}
在 controller 中使用 ctx 對象。
module.exports = class extends think.Controller {
indexAction() {
// controller 中 ctx 作為類的屬性存在,屬性名為 ctx
// controller 實例化時會自動把 ctx 傳遞進來
const ip = this.ctx.ip;
}
}
ThinkJS框架繼承了Koa的一些屬性和方法,並通過 Extend 機製擴展了很多非常有用的屬性和方法。
Koa 內置 API
ctx.req:Node 的 request 對象。
ctx.res:Node 的 response 對象,不支持 繞開 Koa 對 response 的處理。
ctx.request:Koa 的 Request 對象。
ctx.response:Koa 的 Response 對象。
ctx.state:在中間件之間傳遞信息以及將信息發送給模板時,推薦的命名空間。避免直接在 ctx 上加屬性,這樣可能會覆蓋掉已有的屬性,導致出現奇怪的問題。例如:
ctx.state.user = await User.find(id);
這樣後續在 controller 裏可以通過 this.ctx.state.user 來獲取對應的值。
module.exports = class extends think.Controller {
indexAction() {
const user = this.ctx.state.user;
}
}
關於更多的介紹,讀者可以訪問ThinkJS 上下文介紹
Middleware
Middleware,又稱為中間件,是 Koa 中一個非常重要的概念,利用中間件,可以很方便的處理用戶的請求。由於 ThinkJS 3.0 是基於 Koa@2 版本之上構建的,所以完全兼容 Koa 裏的中間件。
使用中間件的格式如下:
module.exports = options => {
return (ctx, next) => {
// do something
}
}
中間件格式為一個高階函數,外部的函數接收一個 options 參數,這樣方便中間件提供一些配置信息,用來開啟/關閉一些功能。執行後返回另一個函數,這個函數接收 ctx, next 參數,其中 ctx 為 context 的簡寫,是當前請求生命周期的一個對象,存儲了當前請求的一些相關信息,next 為調用後續的中間件,返回值是 Promise,這樣可以很方便的處理後置邏輯。相信做過異步程序開發的同學對這個不會陌生。這種中間件格式是常見的洋蔥頭模型。
例如,官方提供的一個例子:
const defaultOptions = {
consoleExecTime: true // 是否打印執行時間的配置
}
module.exports = (options = {}) => {
// 合並傳遞進來的配置
options = Object.assign({}, defaultOptions, options);
return (ctx, next) => {
if(!options.consoleExecTime) {
return next(); // 如果不需要打印執行時間,直接調用後續執行邏輯
}
const startTime = Date.now();
let err = null;
// 調用 next 統計後續執行邏輯的所有時間
return next().catch(e => {
err = e; // 這裏先將錯誤保存在一個錯誤對象上,方便統計出錯情況下的執行時間
}).then(() => {
const endTime = Date.now();
console.log(`request exec time: ${endTime - startTime}ms`);
if(err) return Promise.reject(err); // 如果後續執行邏輯有錯誤,則將錯誤返回
})
}
}
在 Koa 中,可以通過調用 app.use 的方式來使用中間件。
const app = new Koa();
const execTime = require('koa-execTime'); // 引入統計執行時間的模塊
app.use(execTime({})); // 需要將這個中間件第一個注冊,如果還有其他中間件放在後麵注冊
擴展 app 參數
默認的中間件外層一般隻是傳遞了 options 參數,有的中間件需要讀取 app 相關的信息,框架在這塊做了擴展,自動將 app 對象傳遞到中間件中。
module.exports = (options, app) => {
// 這裏的 app 為 think.app 對象
return (ctx, next) => {
}
}
如果在中間件中需要用到 think 對象上的一些屬性或者方法,那麼可以通過 app.think.xxx 來獲取。
配置格式
為了方便管理和使用中間件,框架使用統一的配置文件來管理中間件,配置文件為 src/config/middleware.js(多模塊項目配置文件為 sr/common/config/middleware.js)。
const path = require('path')
const isDev = think.env === 'development'
module.exports = [
{
handle: 'meta', // 中間件處理函數
options: { // 當前中間件需要的配置
logRequest: isDev,
sendResponseTime: isDev,
},
},
{
handle: 'resource',
enable: isDev, // 是否開啟當前中間件
options: {
root: path.join(think.ROOT_PATH, 'www'),
publicPath: /^\/(static|favicon\.ico)/,
},
}
]
配置項為項目中要使用的中間件列表,每一項支持 handle,enable,options,match 等屬性。
handle
中間件的處理函數,可以用係統內置的,也可以是引入外部的,也可以是項目裏的中間件。handle 的函數格式為:
module.exports = (options, app) => {
return (ctx, next) => {
}
}
這裏中間件接收的參數除了 options 外,還多了個 app 對象,該對象為 Koa Application 的實例。
enable
是否開啟當前的中間件,比如:某個中間件隻在開發環境下才生效。
{
handle: 'resouce',
enable: think.env === 'development' //這個中間件隻在開發環境下生效
}
options
傳遞給中間件的配置項,格式為一個對象,中間件裏獲取到這個配置。
module.exports = [
{
options: {
key: value
}
}
]
有時候需要的配置項需要從遠程獲取,如:配置值保存在數據庫中,這時候就要異步從數據庫中獲取,這時候可以將 options 定義為一個函數來完成:
module.exports = [
{
// 將 options 定義為一個異步函數,將獲取到的配置返回
options: async () => {
const config = await getConfigFromDb();
return {
key: config.key,
value: config.value
}
}
}
]
match
匹配特定的規則後才執行該中間件,支持二種方式,一種是路徑匹配,一種是自定義函數匹配。如:
module.exports = [
{
handle: 'xxx-middleware',
match: '/resource' //請求的 URL 是 /resource 打頭時才生效這個 middleware
}
]
或者:
module.exports = [
{
handle: 'xxx-middleware',
match: ctx => { // match 為一個函數,將 ctx 傳遞給這個函數,如果返回結果為 true,則啟用該 middleware
return true;
}
}
]
框架內置的中間件
框架內置了幾個中間件,可以通過字符串的方式直接引用。常見的有:
- meta 顯示一些 meta 信息,如:發送 ThinkJS 的版本號,接口的處理時間等等
- resource 處理靜態資源,生產環境建議關閉,直接用 webserver 處理即可。
- trace 處理報錯,開發環境將詳細的報錯信息顯示處理,也可以自定義顯示錯誤頁麵。
- payload 處理表單提交和文件上傳,類似於 koa-bodyparser 等 middleware
- router 路由解析,包含自定義路由解析
- logic logic 調用,數據校驗
- controller controller 和 action 調用
自定義的中間件
在項目開發中,有時候需要根據一些特定需要添加中間件,那麼我們可以自定義一些中間件,放在src/middleware目錄下。例如:
module.exports = [
{
handle: 'csrf',
options: {}
}
]
引入外部的中間件非常簡單,隻需要 require 進來即可。這和JSX的語法是一樣的。
const csrf = require('csrf');
module.exports = [
...,
{
handle: csrf,
options: {}
},
...
]
Router
Router,及路由,用來進行頁麵跳轉的,用戶訪問一個地址時,需要有一個對應的邏輯進行處理。傳統的處理方式下,一個請求對應的一個文件,如訪問時 /user/about.php,那麼就會在項目對應的目錄下有 /user/about.php 這個實體文件。這種方式雖然能解決問題,但會導致文件很多,同時可能很多文件裏邏輯功能其實比較簡單。
在 MVC 開發模型裏,一般都是通過路由來解決此類問題。由於 Node.js 是自己啟動 HTTP(S) 服務的,所以已經天然將用戶的請求匯總到一個入口了,這樣處理路由映射就更簡單了。
在 ThinkJS 中,當用戶訪問一個 URL 時,最後是通過 controller 裏具體的 action 來響應的。所以就需要解析出 URL 對應的 controller 和 action,這個解析工作是通過 think-router 模塊實現的。
路由配置
think-router 是一個 middleware,項目創建時默認已經加到配置文件 src/config/middleware.js 裏了,其中 options 支持如下的參數:
- defaultModule {String} 多模塊項目下,默認的模塊名。默認值為 home
- defaultController {String} 默認的控製器名,默認值為 index
- defaultAction {String} 默認的操作名,默認值為 index
- prefix {Array} 默認去除的 pathname 前綴,默認值為 []
- suffix {Array} 默認去除的 pathname 後綴,默認值為 ['.html']
- enableDefaultRouter {Boolean} 在不匹配情況下是否使用默認路由解析,默認值為 true
- subdomainOffset {Number} 子域名映射下的偏移量,默認值為 2
- subdomain {Object|Array} 子域名映射列表,默認為 {}
- denyModules {Array} 多模塊項目下,禁止訪問的模塊列表,默認為 []
例如,下麵是項目中的配置:
module.exports = [
{
handle: 'router',
options: {
defaultModule: 'home',
defaultController: 'index',
defaultAction: 'index',
prefix: [],
suffix: ['.html'],
enableDefaultRouter: true,
subdomainOffset: 2,
subdomain: {},
denyModules: []
}
}
];
路徑預處理
當用戶訪問服務時,通過 ctx.url 屬性,可以得到初始的 pathname,如:訪問本頁麵 https://www.thinkjs.org/zh-cn/doc/3.0/router.html,初始 pathname 為 /zh-cn/doc/3.0/router.html。為了方便後續通過 pathname 解析出對應的 controller 和 action,需要對 pathname 進行預處理。
prefix & suffix
有時候為了搜索引擎優化或者一些其他的原因,URL 上會多加一些東西。比如:當前頁麵是一個動態頁麵,為了 SEO,會在 URL 後麵加上 .html 後綴假裝頁麵是一個靜態頁麵,但 .html 對於路由解析來說是無用的,是要去除的。
prefix 與 subffix 為數組,數組的每一項可以為字符串或者正則表達式, 在匹配到第一個之後停止後續匹配。在ThinkJS中prefix的格式如下:
{
prefix: [],
suffix: ['.html'],
}
路由解析
通過 prefix & suffix 和 subdomain 預處理後,得到真正後續要解析的 pathname。默認的路由解析規則為 /controller/action,如果是多模塊項目,那麼規則為 /module/controller/action,根據這個規則解析出對應的 module、controller、action 值。
如果 controller 有子級,那麼會優先匹配子級 controller,然後再匹配 action。常見的匹配有:
pathname | 項目類型 | 子級控製器 | module | controller | action | 備注 |
---|---|---|---|---|---|---|
/ | 單模塊 | 無 | index | index | controller、action 為配置的默認值 | |
/user | 單模塊 | 無 | user | index | action 為配置的默認值 | |
/user/login | 單模塊 | 無 | user | login | ||
/console/user/login | 單模塊 | 有 | console/user | login | 有子級控製器 console/user | |
/console/user/login/aaa/bbb | 單模塊 | 有 | console/user | login | 剩餘的 aaa/bbb 不再解析 | |
/admin/user | 多模塊 | 無 | admin | user | index | 多模塊項目,有名為 admin 的模塊 |
/admin/console/user/login | 多模塊 | 無 | admin | console/user | login |
解析後的 module、controller、action 分別放在 ctx.module、ctx.controller、ctx.action 上,方便後續調用處理。如果不想要默認的路由解析,那麼可以通過配置 enableDefaultRouter: false 關閉。
自定義路由規則
雖然默認的路由解析方式能夠滿足需求,但有時候會導致 URL 看起來不夠優雅,我們更希望 URL 比較簡短,這樣會更利於記憶和傳播。框架提供了自定義路由來處理這種需求。
自定義路由規則配置文件為 src/config/router.js(多模塊項目放在 src/common/config/router.js),路由規則為二維數組:
module.exports = [
[/libs\/(.*)/i, '/libs/:1', 'get'],
[/fonts\/(.*)/i, '/fonts/:1', 'get,post'],
];
每一條路由規則也為一個數組,數組裏麵的項分別對應為:match、pathname、method、options:
- match {String | RegExp} pathname 匹配規則,可以是字符串或者正則。如果是字符串,那麼會通過path-to-regexp 模塊轉為正則
- pathname {String} 匹配後映射後的 pathname,後續會根據這個映射的 pathname 解析出對應的controller、action
- method {String} 該條路由規則支持的請求類型,默認為所有。多個請求類型中間用逗號隔開,如:get,post
- options {Object} 額外的選項,如:跳轉時指定 statusCode
Adapter
Adapter,適配器,用來解決一類功能的多種實現,這些實現提供一套相同的接口,類似設計模式裏的工廠模式。如:支持多種數據庫,支持多種模版引擎等。Adapter 一般配合 Extend 一起使用。
框架默認提供了很多種 Adapter,如: View、Model、Cache、Session、Websocket,項目中也可以根據需要進行擴展,也可以引入第三方的 Adapter。
Adapter 都是一類功能的不同實現,一般是不能獨立使用的,而是配合對應的擴展一起使用。如:view Adapter(think-view-nunjucks、think-view-ejs)配合 think-view 擴展進行使用。
項目安裝 think-view 擴展後,提供了對應的方法來渲染模板,但渲染不同的模板需要的模板引擎有對應的 Adapter 來實現,也就是配置中的 handle 字段。
Adapter 配置
Adapter 的配置文件為 src/config/adapter.js(多模塊項目文件為 src/common/config/adapter.js),格式如下:
const nunjucks = require('think-view-nunjucks');
const ejs = require('think-view-ejs');
const path = require('path');
exports.view = {
type: 'nunjucks', // 默認的模板引擎為 nunjucks
common: { //通用配置
viewPath: path.join(think.ROOT_PATH, 'view'),
sep: '_',
extname: '.html'
},
nunjucks: { // nunjucks 的具體配置
handle: nunjucks
},
ejs: { // ejs 的具體配置
handle: ejs,
viewPath: path.join(think.ROOT_PATH, 'view/ejs/'),
}
}
exports.cache = {
...
}
- type 默認使用 Adapter 的類型,具體調用時可以傳遞參數改寫
- common 配置通用的一些參數,項目啟動時會跟具體的 adapter 參數作合並
- nunjucks ejs 配置特定類型的 Adapter 參數,最終獲取到的參數是 common 參數與該參數進行合並
- handle 對應類型的處理函數,一般為一個類
Adapter 配置支持運行環境,可以根據不同的運行環境設置不同的配置,如:在開發環境和生產環境的數據庫一般都是不一樣的,這時候可以通過 adapter.development.js 和 adapter.production.js 存放有差異的配置,係統啟動後會讀取對應的運行環境配置和默認配置進行合並。
Adapter 配置解析
Adapter 配置存儲了所有類型下的詳細配置,具體使用時需要對其解析,選擇對應的一種進行使用。比如上麵的配置文件中,配置了 nunjucks 和 ejs 二種模板引擎的詳細配置,但具體使用時一種場景下肯定隻會用其一種模板引擎。Adapter 的配置解析是通過 think-helper 模塊中的 parseAdapterConfig 方法來完成的,如:
const helper = require('think-helper');
const viewConfig = think.config('view'); // 獲取 view adapter 的詳細配置
const nunjucks = helper.parseAdatperConfig(viewConfig); // 獲取 nunjucks 的配置,默認 type 為 nunjucks
/**
{
type: 'nunjucks',
handle: nunjucks,
viewPath: path.join(think.ROOT_PATH, 'view'),
sep: '_',
extname: '.html'
}
*/
const ejs = helper.parseAdatperConfig(viewConfig, 'ejs') // 獲取 ejs 的配置
/**
{
handle: ejs,
type: 'ejs',
viewPath: path.join(think.ROOT_PATH, 'view/ejs/'),
viewPath: path.join(think.ROOT_PATH, 'view'),
sep: '_',
extname: '.html'
}
*/
通過 parseAdapterConfig 方法就可以拿到對應類型的配置,然後就可以調用對應的 handle,傳入配置然後執行了。當然,配置解析並不需要使用者在項目中具體調用,一般都是在插件對應的方法裏已經處理。
Adapter使用
除了引入外部的 Adapter 外,項目內也可以創建 Adapter 來使用。Adapter 文件放在 src/adapter/ 目錄下(多模塊項目放在 src/common/adapter/),如:src/adapter/cache/xcache.js,表示加了一個名為 xcache 的 cache Adapter 類型,然後該文件實現 cache 類型一樣的接口即可。
exports.cache = {
type: 'file',
xcache: {
handle: 'xcache', //這裏配置字符串,項目啟動時會自動查找 src/adapter/cache/xcache.js 文件
...
}
}
Extend
雖然框架內置了很多功能,但在實際項目開發中,提供的功能還是遠遠不夠的。3.0 裏引入了擴展機製,方便對框架進行擴展。支持的擴展類型為:think、application、context、request、response、controller、logic 和 service。框架內置的很多功能也是擴展來實現的,如:Session、Cache。
擴展配置
擴展配置文件路徑為 src/config/extend.js(多模塊項目文件路徑為 src/common/config/extend.js),格式為數組:
const view = require('think-view');
module.exports = [
view //make application support view
];
除了引入外部的 Extend 來豐富框架的功能,也可以在項目中對對象進行擴展,擴展文件放在 src/extend/ 目錄下(多模塊項目放在 src/common/extend/ 下)。
- src/extend/think.js 擴展 think 對象,think.xxx
- src/extend/application.js 擴展 Koa 裏的 app 對象(think.app)
- src/extend/request.js 擴展 Koa 裏的 request 對象(think.app.request)
- src/extend/response.js 擴展 Koa 裏的 response 對象(think.app.response)
- src/extend/context.js 擴展 ctx 對象(think.app.context)
- src/extend/controller.js 擴展 controller 類(think.Controller)
- src/extend/logic.js 擴展 logic 類(think.Logic)- logic 繼承
- controller 類,所以 logic 包含 controller 類所有方法
- src/extend/service.js 擴展 service 類(think.Service)
比如:我們想給 ctx 添加個 isMobile 方法來判斷當前請求是不是手機訪問:
module.exports = {
isMobile(){
const userAgent = this.userAgent.toLowerCase();
const mList = ['iphone', 'android'];
return mList.some(item => userAgent.indexOf(item) > -1);
}
}
然後使用ctx.isMobile() 來判斷是否是手機訪問。當然這個方法沒有任何的參數,我們也可以變成一個 getter。
module.exports = {
get isMobile(){
const userAgent = this.userAgent.toLowerCase();
const mList = ['iphone', 'android'];
return mList.some(item => userAgent.indexOf(item) > -1);
}
}
如果在 controller 中也想通過 this.isMobile 使用,怎麼辦呢? 可以給 controller 也擴展一個 isMobile 屬性來完成。
module.exports = {
get isMobile(){
return this.ctx.isMobile;
}
}
不過這種方式隻能在本模塊使用,如果要在其他項目中使用,可以將這些擴展發布為一個 npm 模塊。這和React的寫法是一樣的。
const controllerExtend = require('./controller.js');
const contextExtend = require('./context.js');
// 模塊入口文件
module.exports = {
controller: controllerExtend,
context: contextExtend
}
擴展裏使用 app 對象
有些 Extend 需要使用一些 app 對象上的數據,那麼可以導出為一個函數,配置時把 app 對象傳遞進去即可。
const model = require('think-model');
module.exports = [
model(think.app) //將 think.app 傳遞給 model 擴展
];
運行流程
前麵說了這麼多基本的概念,現在再來看一下ThinkJS的運行流程。
Node.js 提供了 http 模塊直接創建 HTTP 服務,用來響應用戶的請求,比如 Node.js 官網提供的創建 HTTP 服務的例子:
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at https://${hostname}:${port}/`);
});
ThinkJS 也是調用 http.createServer 的方式來創建服務的,所以整個運行流程包含了啟動服務和響應用戶請求兩個部分。
係統服務
- 執行 npm start 或者 node development.js;
- 實例化 ThinkJS 裏的 Application 類,執行 run 方法。
- 根據不同的環境(Master 進程、Worker 進程、命令行調用)處理不同的邏輯。
- 如果是 Master 進程。加載配置文件,生成 think.config 和 think.logger 對象。 1)加載文件 src/bootstrap/master.js 文件 2)如果配置文件監聽服務,那麼開始監聽文件的變化,目錄為 src/。 3)文件修改後,如果配置文件編譯服務,那麼會對文件進行編譯,編譯到 app/ 目錄下。 4)根據配置 workers 來 fork 對應數目的 Worker。Worker 進程啟動完成後,觸發 appReady 事件。(可以通過 think.app.on("appReady") 來捕獲) 5)如果文件發生了新的修改,那麼會觸發編譯,然後殺掉所有的 Worker 進程並重新 fork。
- 如果是 Worker 進程 1)加載配置文件,生成 think.config 和 think.logger 對象。 2)加載 Extend,為框架提供更多的功能,配置文件為 src/config/extend.js。 3)獲取當前項目的模塊列表,放在 think.app.modules 上,如果為單模塊,那麼值為空數組。 4)加載項目裏的 controller 文件(src/controller/*.js),放在 think.app.controllers 對象上。 5)加載項目裏的 logic 文件(src/logic/*.js),放在 think.app.logics 對象上。 6)加載項目裏的 model 文件(src/model/*.js),放在 think.app.models 對象上。 7)加載項目裏的 service 文件(src/service/*.js),放在 think.app.services 對象上。 8)加載路由配置文件 src/config/router.js,放在 think.app.routers 對象上。 9)加載校驗配置文件 src/config/validator.js,放在 think.app.validators 對象上。 10)加載 middleware 配置文件 src/config/middleware.js,並通過 think.app.use 方法注冊。 11)加載定時任務配置文件 src/config/crontab.js,並注冊定時任務服務。 12)加載 src/bootstrap/worker.js 啟動文件。 13)監聽 process 裏的 onUncaughtException 和 onUnhandledRejection 錯誤事件,並進行處理。可以在配置 src/config.js 自定義這二個錯誤的處理函數。 14)等待 think.beforeStartServer 注冊的啟動前處理函數執行,這裏可以注冊一些服務啟動前的事務處理。 15)如果自定義了創建服務配置 createServer,那麼執行這個函數 createServer(port, host, callback) 來創建服務;如果沒有自定義,則通過 think.app.listen 來啟動服務。 16)服務啟動完成時,觸發 appReady 事件,其他地方可以通過 think.app.on("appReady") 監聽;創建的服務賦值給 think.app.server 對象。
服務啟動後,會打印下麵的日誌:
最後更新:2017-10-15 21:34:50