閱讀863 返回首頁    go 阿裏雲 go 技術社區[雲棲]


前端ThinkJS框架解析

Thinkjs 是一個快速、簡單的基於MVC和麵向對象的輕量級Node.js開發框架,遵循MIT協議發布。秉承簡潔易用的設計原則,在保持出色的性能和至簡的代碼同時,注重開發體驗和易用性,為WEB應用開發提供強有力的支持。

Thinkjs裏麵很多特性來源於ThinkPHP,同時根據Node.js的特點,使用了Promise, WebSocket等特性,讓代碼更簡潔、優雅。

Thinkjs最大的特色是對目前比較潮的語法特性支持的特別好,比如es6、es7、typescript等,有了這些,對aysnc/await等特性支持,讓代碼更加fashion。

安裝

安裝命令行工具:

$ npm install -g thinkjs

然後使用thinkjs new demo創建一個新項目。為了確保用戶錯誤操作導致現有文件被覆蓋,thinkjs new 命令僅適用於文件夾不存在的,或者空文件夾。否則會報如下錯誤:

path `/data/www/demo` is already a thinkjs project.

實現這一特性其實是依賴一個項目根目錄下的隱藏文件 .thinkjsrc ,使用 ls -a 可以查看隱藏文件,打開這個文件可以看到如下內容:

{
  "createAt": "2017-02-12 19:08:38",
  "mode": "module",
  "es": true
}

使用命令後,係統就開始構建項目了:

$ thinkjs new demo
  create : demo
  create : demo/package.json
  create : demo/.babelrc
  create : demo/.thinkjsrc
  create : demo/nginx.conf
  create : demo/pm2.json
  create : demo/.gitignore
  create : demo/README.md
  create : demo/www
  create : demo/www/development.js
  create : demo/www/production.js
  create : demo/www/testing.js
  create : demo/www/README.md
  create : demo/www/static
  create : demo/www/static/js
  create : demo/www/static/css
  create : demo/www/static/img
  create : demo/src
  create : demo/src/common/bootstrap
  create : demo/src/common/bootstrap/middleware.js
  create : demo/src/common/bootstrap/global.js
  create : demo/src/common/config
  create : demo/src/common/config/config.js
  create : demo/src/common/config/view.js
  create : demo/src/common/config/db.js
  create : demo/src/common/config/hook.js
  create : demo/src/common/config/session.js
  create : demo/src/common/config/error.js
  create : demo/src/common/config/env
  create : demo/src/common/config/env/development.js
  create : demo/src/common/config/env/testing.js
  create : demo/src/common/config/env/production.js
  create : demo/src/common/config/locale
  create : demo/src/common/config/locale/en.js
  create : demo/src/common/controller
  create : demo/src/common/controller/error.js
  create : demo/view/common
  create : demo/view/common/error_400.html
  create : demo/view/common/error_403.html
  create : demo/view/common/error_404.html
  create : demo/view/common/error_500.html
  create : demo/view/common/error_503.html
  create : demo/src/home/config
  create : demo/src/home/config/config.js
  create : demo/src/home/controller
  create : demo/src/home/controller/base.js
  create : demo/src/home/controller/index.js
  create : demo/src/home/logic
  create : demo/src/home/logic/index.js
  create : demo/src/home/model
  create : demo/src/home/model/index.js
  create : demo/view/home
  create : demo/view/home/index_index.html

  enter path:
  $ cd demo

  install dependencies:
  $ npm install

  run the app:
  $ npm start

需要注意的是,新建項目的時候需要好多babel,所以項目的構建會比較慢,依賴的包主要有:

 "dependencies": {
    "thinkjs": "2.2.x",
    "babel-runtime": "6.x.x",
    "source-map-support": "0.4.0"
  },

目錄

.
├── README.md
├── app
│   ├── common
│   │   ├── bootstrap
│   │   ├── config
│   │   └── controller
│   └── home
│       ├── config
│       ├── controller
│       ├── logic
│       └── model
├── nginx.conf
├── package.json
├── pm2.json
├── src
│   ├── common
│   │   ├── bootstrap
│   │   ├── config
│   │   └── controller
│   └── home
│       ├── config
│       ├── controller
│       ├── logic
│       └── model
├── tree.txt
├── view
│   ├── common
│   │   ├── error_400.html
│   │   ├── error_403.html
│   │   ├── error_404.html
│   │   ├── error_500.html
│   │   └── error_503.html
│   └── home
│       └── index_index.html
└── www
    ├── README.md
    ├── development.js
    ├── production.js
    ├── static
    │   ├── css
    │   ├── img
    │   └── js
    └── testing.js

388 directories, 1381 files

啟動流程分析

1)啟動命令

npm start

那使用start後係統做了什麼呢?

 "scripts": {
    "start": "node www/development.js",
    "compile": "babel src/ --out-dir app/",
    "watch-compile": "node -e \"console.log('<npm run watch-compile> no longer need, use <npm start> command direct.');console.log();\"",
    "watch": "npm run watch-compile"
  },

即使用Node執行www/development.js,這是env環境處理,thinkjs采用了3中env,比較常見的有:

  • development 開發模式
  • production 線上模式
  • testing 測試模式

thinkjs是把www當成node項目目錄,而www下的static才是靜態資源文件目錄。
www/development.js目錄如下:

var instance = new thinkjs({
  APP_PATH: rootPath + path.sep + 'app',
  RUNTIME_PATH: rootPath + path.sep + 'runtime',
  ROOT_PATH: rootPath,
  RESOURCE_PATH: __dirname,
  env: 'development'
});

當然,可以使用“tree src -L 3” 命令來查看項目的目錄:

$ tree src -L 3         
src
├── common
│   ├── bootstrap
│   │   ├── global.js
│   │   └── middleware.js
│   ├── config
│   │   ├── config.js
│   │   ├── db.js
│   │   ├── env
│   │   ├── error.js
│   │   ├── hook.js
│   │   ├── locale
│   │   ├── session.js
│   │   └── view.js
│   └── controller
│       └── error.js
├── home
│   ├── config
│   │   └── config.js
│   ├── controller
│   │   ├── base.js
│   │   └── index.js
│   ├── logic
│   │   └── index.js
│   └── model
│       └── index.js


16 directories, 19 files

常見模塊配置(後文會具體涉及):

$ thinkjs module topic(能創建不能刪除,略遺憾)

  create : src/topic/config
  create : src/topic/config/config.js
  create : src/topic/controller
  create : src/topic/controller/base.js
  create : src/topic/controller/index.js
  create : src/topic/logic
  create : src/topic/logic/index.js
  create : src/topic/model
  create : src/topic/model/index.js
  exist : /Users/sang/workspace/github/nodewebframework/demo/view/topic/index_index.html

此時目錄結構如下:

src
├── common
├── home
└── topic

3)業務模塊目錄

├── home
│   ├── config
│   │   └── config.js
│   ├── controller
│   │   ├── base.js
│   │   └── index.js
│   ├── logic
│   │   └── index.js
│   └── model
│       └── index.js

4)路由和view識別
路由識別,默認根據 模塊/控製器/操作/參數1/參數1值/參數2/參數2值 其實就是一個約定。

比如/解析為:

  • 默認模塊是home
  • 控製是index
  • 操作是indexAction

那如果再來一個呢?

'use strict';

import Base from './base.js';

export default class extends Base {
  /**
   * index action
   * @return {Promise} []
   */
  indexAction(){
    //auto render template file index_index.html
    return this.display();
  }

  myAction(){
    //auto render template file index_index.html
    return this.display();
  }
}

增加myAction,報錯[Error] Error: can’t find template file /Users/sang/workspace/github/nodewebframework/demo/view/home/index_my.html

將view/home/index_index.html複製為view/home/index_my.html。原理是my要對應index_my.html模塊。即index是controller,而my是action。

理解了這個,你就會覺得index_index這樣的命名也不是很怪異了。剩下的就是view編寫之類的,此處就不在熬述。

性能

前麵提到了,開發階段采用babel寫的,所以效率不會很高。

$ autocannon -c 100 -d 5 -p 10 localhost:8360
Running 5s test @ https://localhost:8360
100 connections with 10 pipelining factor

Stat         Avg       Stdev    Max       
Latency (ms) 108.9     201.32   866       
Req/Sec      891.8     148.37   1000      
Bytes/Sec    417.79 kB 50.76 kB 458.75 kB 

4k requests in 5s, 2.09 MB read

點慘,是吧?但是這是開發模式啊,我們肯定要拿線上的production模式來測試。

$ npm run compile
$ node www/production.js 
$ autocannon -c 100 -d 5 -p 10 localhost:8360
Running 5s test @ https://localhost:8360
100 connections with 10 pipelining factor

Stat         Avg       Stdev     Max       
Latency (ms) 61.76     124.71    763       
Req/Sec      1567.2    734.94    1993      
Bytes/Sec    679.12 kB 242.25 kB 884.74 kB 

8k requests in 5s, 3.4 MB read

$ autocannon -c 100 -d 5 -p 10 localhost:8360
Running 5s test @ https://localhost:8360
100 connections with 10 pipelining factor

Stat         Avg       Stdev     Max      
Latency (ms) 54.65     105.47    707      
Req/Sec      1813.4    368.21    1999     
Bytes/Sec    807.73 kB 156.09 kB 917.5 kB 

9k requests in 5s, 4.09 MB read

$ autocannon -c 100 -d 5 -p 10 localhost:8360
Running 5s test @ https://localhost:8360
100 connections with 10 pipelining factor

Stat         Avg       Stdev     Max     
Latency (ms) 54.14     89.81     465     
Req/Sec      1816.4    319.14    2000    
Bytes/Sec    914.23 kB 145.96 kB 1.05 MB 

9k requests in 5s, 4.55 MB read

下麵以同樣的功能express + ejs模板的方式。

$ autocannon -c 100 -d 5 -p 10 localhost:3000
Running 5s test @ https://localhost:3000
100 connections with 10 pipelining factor

Stat         Avg       Stdev     Max       
Latency (ms) 53.85     177.72    1309      
Req/Sec      1728      385.85    2075      
Bytes/Sec    702.87 kB 159.56 kB 851.97 kB 

9k requests in 5s, 3.53 MB read

$ autocannon -c 100 -d 5 -p 10 localhost:3000
Running 5s test @ https://localhost:3000
100 connections with 10 pipelining factor

Stat         Avg       Stdev     Max       
Latency (ms) 46.06     141.52    739       
Req/Sec      2061.2    320.53    2275      
Bytes/Sec    842.14 kB 134.95 kB 950.27 kB 

10k requests in 5s, 4.2 MB read

$ autocannon -c 100 -d 5 -p 10 localhost:3000
Running 5s test @ https://localhost:3000
100 connections with 10 pipelining factor

Stat         Avg       Stdev    Max       
Latency (ms) 45.97     139.58   620       
Req/Sec      2059.4    122.93   2167      
Bytes/Sec    829.03 kB 52.43 kB 884.74 kB 

10k requests in 5s, 4.2 MB read

模塊分解

創建項目之後,基本的代碼框架已經建立起來了,其中默認的 home 和 common 肯定是無法滿足要求的。我們需要給自己的項目建立起相關的層次結構。這裏給大家列舉一些常見的模塊分類方式。僅供參考。

簡單網站

官方網站、博客、社區等,這類係統結構較為簡單,通常一個前端一個後端管理即可滿足要求。通常需要包含以下模塊:

src/
src/common/  # 通用模塊,放置主配置參數、boostrap adapter middleware service 等相關組件
src/home/  # 前端默認模塊
src/backend/  # 後端管理模塊
src/util/  # 係統工具類

電商平台

電商平台係統主要考慮到入駐的商戶、注冊的客戶、管理人員、運營人員等使用人群,還需要考慮到較大的功能模塊切分(如果足夠大到類似京東、天貓那種體量的係統,則需要進行數據、功能、服務、位置等角度的分割)。

src/
src/common/
src/home/
src/sso/  # 單點登錄、令牌管理等
src/rest/  # 針對Wap、App等多客戶端的 rest api
src/goods/  # 商品管理及服務
src/storage/  # 庫存管理及服務
src/cart/  # 購物車
src/order/  # 訂單
src/delivery/  # 快遞
src/pay/  # 在線支付、空中支付
src/member/  #
src/coupon/  # 電子券
src/promotion/  # 促銷
src/points/  # 積分
src/merchant/  # 入駐商戶
src/shop/  # 商戶門店
src/finance/  # 財務核算及款項清算
src/stat/
src/log/
src/monitor/
src/util/
src/task/
src/message/  # 消息隊列

即時消息平台

實時推送平台不僅僅要處理 WebSocket 連接和消息囤積發送,還要處理多用戶購買相應服務套餐、統計連接數、統計下行流量、進行連接鑒權等。通常包含的模塊如下:

src/
src/common/
src/home/
src/rest/
src/storage/
src/websocket/  # ws 或者 wss 服務
src/webhook/  # 鉤子服務
src/middleware/  # 搭載中間件運行
src/pay/
src/member/
src/stat/
src/log/
src/monitor/
src/util/
src/message/  # 消息隊列


在線教育、直播平台

在線教育或直播平台通常具備實時音視頻上傳、轉碼、存儲、廣播等硬性要求,因此係統除了管理相關課件、學生、教師、選課等,還要負責處理相關媒體文件。

src/
src/common/
src/home/
src/rest/
src/sso/  # 單點登錄、令牌管理等
src/media/  # 課件、音視頻等媒體文件
src/bulk/  # 流媒體
src/process/  # 編解碼處理
src/storage/
src/live/  # 直播
src/pay/
src/student/
src/teacher/
src/schedule/
src/stat/
src/log/
src/monitor/
src/util/
src/task/
src/message/  # 消息隊列


參數配置

官網是這麼描述配置文件加載順序的:框架默認的配置 -> 項目模式下框架配置 -> 項目公共配置 -> 項目模式下的公共配置 -> 模塊下的配置。

第三個和第四個則是在不同的項目創建模式下的默認 config 配置文件夾,位置在:

# normal mode
thinkjs_normal/src/config/*
# module mode
thinkjs_module/src/common/config/*

最後一個是指的在 module mode 下的項目,每個 module 自己的 config,位置在:

thinkjs_module/src/home/config/*

明白了多個地方多個配置文件的玩法之後,你可以創建多個 module,並給每個 module 配置自身獨特的配置參數。

需要注意的是:thinkjs 加載配置文件是有順序的!!!多個配置文件最終會在 thinkjs 運行時被全部加載,並合並在一起。所以當存在多個配置文件時,需要注意配置參數的 key(即屬性名)盡量不要重複,因為按照加載順序,後加載的 key 的值會覆蓋先加載的 key 的值,導致出現不希望的結果。

舉例來說,有兩個配置文件 src/common/config/assets.js 和 src/home/config/assets.js,

// src/common/config/assets.js
export default {
  "site_title": "my site"
};

// src/home/config/assets.js
export default {
  "site_title": "my test"
};

// src/home/controller/index.js
let assets = this.config('assets');
let siteTitle = assets['site_title'];
console.log('siteTitle is: ', siteTitle); // my test


Babel 編譯時刪除注釋

開發時的工作代碼都在 src 下麵,運行時才會編譯到 app 下麵成為運行腳本(經過 Babel 編譯),如果不想自己寫的各種注釋也出現在 app 下麵的代碼中,可以修改項目目錄下的一個隱藏文件 .babelrc 增加相應 comments 參數。

{
  "presets": [
    ["es2015", {"loose": true}],
    "stage-1"
  ],
  "plugins": ["transform-runtime"],
  "sourceMaps": true,
  "comments": false  # <-- 就是這個參數
}


controller

目前,thinkJs支持兩種控製器:普通的控製器和多級控製器。
支持__before和__after這樣的回調鉤子,對於app和controller控製來說是非常實用的。使用co來實現也是可圈可點,此處如果使用koa可以更加優雅。例如:

class PathController extends BaseController {
  constructor(app, ctx, next) {
    super(app, ctx, next)

    this.path = '/c'
    // this.global_filter.push('custom_filter')
    this.post_filter = [this.log]
  }

  before() {

  }

  log(ctx, next) {
    ctx.someText = 'some'
    // console.log('before')
    return next().then(function(){
      // console.log('after')
    })
  }

  post(req, res) {
    console.log(this.ctx.someText)
    var a = this.reqbody.a

    return res.body = this.ctx.someText
  } 

  after() {
  }
}

修改 pm2 日誌位置

pm2 (官網 https://pm2.keymetrics.io)是一個優秀的 Node.js 進程管理器。thinkjs 推薦使用 pm2 來管理項目運行,並自動生成了 pm2 的配置文件 pm2.json 。

它的強大之處在於不僅可以作為 Node.js 項目的守護進程,還具備可配置化啟動、分布式支持、內存監控、熱重載(優雅重載)、支持數據統計、運行日誌記錄、實時運行監控、API 和腳本支持等強大的特性。

默認生成的 pm2 配置文件不含日誌記錄部分,如果不單獨配置,pm2 的日誌將會保存在安裝目錄中,查找起來很不方便。普遍的做法是:在項目目錄下建立 logs 文件夾,用來放置 pm2 以及其他(諸如 log4js 等等)日誌,打開 pm2.json ,給 apps[0] 增加如下幾行配置參數:

{
  "apps": [{
    "error_file"      : "/data/www/thinkjs_module/logs/pm2-err.log",
    "out_file"        : "/data/www/thinkjs_module/logs/pm2-out.log",
    "log_date_format" : "YYYY-MM-DD HH:mm:ss Z",
    "merge_logs"      : false
  }]
}


  • error_file pm2 捕捉到的致命錯誤記錄在這裏
  • out_file pm2 接收到的 console 輸出記錄在這裏
  • log_date_format 日期和時間格式
  • merge_logs 是否給日誌文件增加進程id的後綴

總結

主要優勢:

  • 完全自己實現,對已有框架很少借鑒
  • 內置各種adapter,db,中間件,hook,插件,非常豐富,all in one 比組裝更適合新手
  • 遵循mvc和coc
  • 使用最潮的es6/es7/ts特性,對aysnc函數,exports等都非常好的支持
  • 支持i18n等實用功能
  • 內置pm2和nginx集成,部署方便
  • 有自己的腳手架,稍弱
  • 性能不錯,雖然比express稍弱,但功能強大許多
  • 測試豐富,代碼質量有保障
  • 文檔健全,是經過設計的,支持多語言
  • 背後有75團和李成銀支持,最近一周內有更新,代碼提交2600+,35人貢獻,整體來說算健康

附:ThinkJS官網文檔

最後更新:2017-09-20 20:03:00

  上一篇:go  拉卡拉如何完成上億用戶的谘詢工作?
  下一篇:go  Spring cloud zuul