一文讀懂NodeJS全棧開發利器:CabloyJS(萬字長文)

0 修訂

0.1 修訂說明

文章發表以後,也不免會有一些新的想法。若是新開一篇文章,會顯得散亂;而直接修改正文,那些已經閱讀過本篇文章的讀者又沒法快速定位修改的內容。所以,新增修訂章節,記錄修訂歷史javascript

0.2 修訂歷史

0.2.1 2019-06-18 亮點與痛點

1)亮點

CabloyJS最大的亮點是:經過pc=mobile+pad的模式,把mobile場景的操控體驗開發模式帶⼊pc場景。既顯著減小了代碼開發量,提高了開發效率,⼜保持了用戶操控體驗的⼀致性html

pc-mobile-layout

2)痛點

CabloyJS最大的痛點是:經過模塊化的架構設計,能夠快速開發全場景業務前端

場景 前端 後端
PC:Web CabloyJS前端 CabloyJS後端
PC:Exe CabloyJS前端 + Electron CabloyJS後端
Mobile:IOS CabloyJS前端 + Cordova CabloyJS後端
Mobile:Android CabloyJS前端 + Cordova CabloyJS後端
微信公共號 CabloyJS前端 + 微信API CabloyJS後端
企業微信 CabloyJS前端 + 微信API CabloyJS後端
釘釘 CabloyJS前端 + 釘釘API CabloyJS後端
Slack CabloyJS前端 + Slack API CabloyJS後端
小程序:微信、支付寶、百度等 小程序框架 CabloyJS後端
  • 後端:因爲完整的先後端分離設計,只需開發一套CabloyJS後端代碼便可
  • 前端:全部可基於H5的場景,只需開發一套CabloyJS前端代碼便可

1 基本概念

1.1 CabloyJS是什麼

1.1.1 定義

CabloyJS是一款頂級NodeJS全棧業務開發框架vue

1.1.2 特色

  • CabloyJS是採用NodeJS進行全棧開發的最佳實踐
  • CabloyJS不重複造輪子,而是採用業界最新的開源技術,進行全棧開發的最佳組合
  • CabloyJS前端採用VueJS + Framework7 + WebPack,後端採用KoaJS + EggJS,數據庫採用MySQL
  • CabloyJS時刻跟蹤開源技術的最新成果,並持續優化,使整個框架時刻保持最佳狀態

1.1.3 理念

既可快速開發,又可靈活定製java

爲了實現此理念,CabloyJS內置開發了大量核心模塊,使您能夠在最短的時間內架構一個完整的Web項目。好比,當您新建一個Web項目時,就已經具有完整的用戶登陸與認證系統,也具備驗證碼功能,同時也具有用戶管理角色管理權限管理等功能node

此外,這些內置模塊提供了靈活的定製特性,您也能夠開發全新的模塊來替換內置模塊,從而實現系統的定製化mysql

1.2 CabloyJS核心解決什麼問題

  1. 場景碎片化
  2. 業務模塊化

1.2.1 場景碎片化

1) 先說說Mobile場景

咱們知道,隨着智能機的日益普及,我們開發人員所面對的需求場景與開發場景日益碎片化,如瀏覽器、IOS、Android,還有大量第三方平臺:微信、企業微信、釘釘、Facebook、Slack等等git

隨着智能設備性能愈來愈好,網速愈來愈快,針對如此衆多的開發場景,採用H5開發必將是大勢所趨。只需開發一套代碼,就能夠在以上全部智能設備中運行,不只能夠顯著減小開發量,同時也能夠顯著提高開發效率,對開發團隊和終端用戶均是莫大的福利github

2) 再來談談PC場景

以上我們說H5開發,只需開發一套代碼,就能夠在全部智能設備中運行。可是還有一個開發場景沒有獲得統一:那就是PC場景sql

因爲屏幕顯示尺寸的不一樣,PC場景Mobile場景有着不一樣的操做風格。有些前端UI框架,採用「自適應」策略,爲PC場景開發的頁面,在Mobile場景下雖然也能查看和使用,但使用體驗每每差強人意

這也就是爲何有些前端框架老是成對出現的緣由:如Element-UI和Mint-UI,如AntDesign和AntDesign-Mobile

這也就意味着,當咱們同時面對PC場景Mobile場景時,仍然須要開發兩套代碼。在面對許多開發需求時,這些重複的工做量每每是難以接受的:

  1. 好比,咱們在企業微信或釘釘上開發一些H5業務應用,同時也但願這些應用也能夠在PC端瀏覽器中運行
  2. 好比,咱們爲微信公共號開發了一些H5業務應用,同時也但願這些應用也能夠在PC端瀏覽器中運行。同時,還能夠在同一架構下開發後臺管理類功能,經過區別不一樣的登陸用戶、不一樣的使用場景,從而顯示不一樣的前端頁面
3) PC = MOBILE + PAD

CabloyJS前端採用Framework7框架,目前已同步升級到最新版Framework7 V4。CabloyJS在Framework7的基礎上進行了巧妙的擴展,將PC端的頁面切分爲多個區域,實現了多個Mobile和PAD同時呈如今一個PC端的效果。換句話說,你買了一臺Mac,就相對於買了多臺IPhone和IPad,用多個虛擬的移動設備同時工做,即顯著提高了工做效率,也提供了很是有趣的使用體驗

4) 實際效果

有圖有真相

pc-mobile-layout

也可PC端體驗

admin.cabloy.com

也可手機掃描體驗

cabloy-demo-qrcode

5) 如何實現的

CabloyJS是模塊化的全棧框架,爲了實現PC = MOBILE + PAD的風格,內置了兩個模塊:egg-born-module-a-layoutmobileegg-born-module-a-layoutpc。當前端框架加載完畢,會自動判斷當前頁面的寬度(稱爲breakpoint),若是小於800,使用Mobile佈局,若是大於800,使用PC佈局,並且breakpoint數值能夠自定義

此外,這兩個佈局模塊自己也有許多參數能夠自定義,甚至,您也能夠開發本身的佈局模塊,替換掉內置的實現方式

下面分別貼出兩個佈局模塊的默認參數,相信您一看便知他們的用處

egg-born-module-a-layoutmobile

export default {
  layout: {
    login: '/a/login/login',
    loginOnStart: true,
    toolbar: {
      tabbar: true, labels: true, bottom: true,
    },
    tabs: [
      { name: 'Home', tabLinkActive: true, iconMaterial: 'home', url: '/a/base/menu/list' },
      { name: 'Atom', tabLinkActive: false, iconMaterial: 'group_work', url: '/a/base/atom/list' },
      { name: 'Mine', tabLinkActive: false, iconMaterial: 'person', url: '/a/user/user/mine' },
    ],
  },
};
複製代碼

egg-born-module-a-layoutpc

export default {
  layout: {
    login: '/a/login/login',
    loginOnStart: true,
    header: {
      buttons: [
        { name: 'Home', iconMaterial: 'dashboard', url: '/a/base/menu/list', target: '_dashboard' },
        { name: 'Atom', iconMaterial: 'group_work', url: '/a/base/atom/list' },
      ],
      mine:
        { name: 'Mine', iconMaterial: 'person', url: '/a/user/user/mine' },
    },
    size: {
      small: 320,
      top: 60,
      spacing: 10,
    },
  },
};
複製代碼

1.2.2 業務模塊化

NodeJS的蓬勃發展,爲先後端開發帶來了更順暢的體驗,顯著提高了開發效率。但仍有網友質疑NodeJS可否勝任大型Web應用的開發。大型Web應用的特色是隨着業務的增加,須要開發大量的頁面組件。面對這種場景,通常有兩種解決方案:

  1. 採用單頁面的構建方式,缺點是產生的部署包很大
  2. 採用頁面異步加載方式,缺點是頁面過於零散,須要頻繁從後端獲取JS資源

CabloyJS實現了第三種解決方案:

  1. 頁面組件按業務需求歸類,進行模塊化,而且實現了模塊的異步加載機制,從而彌合了前兩種解決方案的缺點,完美知足大型Web應用業務持續增加的需求

在CabloyJS中,一切業務開發皆以業務模塊爲單位。好比,咱們要開發一個CMS建站工具,就新建一個業務模塊,如已經實現的模塊egg-born-module-a-cms。該CMS模塊包含十多個Vue頁面組件,在正式發佈時,就會構建成一個JS包。在運行時,只需異步加載這一個JS包,就能夠訪問CMS模塊中任何一個Vue頁面組件了。

所以,在一個大型的Web系統中,哪怕有數十甚至上百個業務模塊,按CabloyJS的模塊化策略進行代碼組織和開發,既不會出現單一巨大的部署包,也不會出現大量碎片化的JS構建文件。

CabloyJS的模塊化系統還有以下顯著的特色:

1) 零配置、零代碼

也就是說,前面說到的模塊化異步打包策略是已經精心調校好的系統核心特性,咱們只需像平時同樣開發Vue頁面組件,在構建時系統會自動進行模塊級別的打包,同時在運行時進行異步加載

咱們仍然以CMS模塊爲例,經過縮減的代碼直觀的看一下代碼風格,若是想了解進一步的細節,能夠直接查看對應的源碼(下同,再也不贅述)

如何查看源碼:進入項目的node_modules目錄,查看egg-born-爲前綴的模塊源碼便可

egg-born-module-a-cms/src/module/a-cms/front/src/routes.js

function load(name) {
  return require(`./pages/${name}.vue`).default;
}

export default [
  { path: 'config/list', component: load('config/list') },
  { path: 'config/site', component: load('config/site') },
  { path: 'config/siteBase', component: load('config/siteBase') },
  { path: 'config/language', component: load('config/language') },
  { path: 'config/languagePreview', component: load('config/languagePreview') },
  { path: 'category/list', component: load('category/list') },
  { path: 'category/edit', component: load('category/edit') },
  { path: 'category/select', component: load('category/select') },
  { path: 'article/contentEdit', component: load('article/contentEdit') },
  { path: 'article/category', component: load('article/category') },
  { path: 'article/list', component: load('article/list') },
  { path: 'article/post', component: load('article/post') },
  { path: 'tag/select', component: load('tag/select') },
  { path: 'block/list', component: load('block/list') },
  { path: 'block/item', component: load('block/item') },
];
複製代碼

能夠看到,在前端頁面路由的定義中,仍然是採用平時的同步加載寫法

關於模塊的異步加載機制是由核心模塊egg-born-front來完成的,參見源碼egg-born-front/src/base/module.js

2) 模塊自洽、即插即用

每一個業務模塊都是自洽的總體,包含與本模塊業務相關的前端代碼和後端代碼,並且採用先後端分離模式

模塊自洽既有利於自身的高度內聚,也有利於整個系統的充分解耦。業務模塊只須要考慮自身的邏輯實現,容易實現業務的充分沉澱與分享,達到即插即用的效果

舉一個例子:若是咱們要開發文件上傳功能,當咱們在網上找到合適的上傳組件以後,在本身的項目中使用時,仍然須要開發大量對接代碼。也就是說,在網上找到的上傳組件沒有實現充分的沉澱,不是自洽的,也就不能實現便利的分享,達到即插即用的效果

而CabloyJS內置的的文件上傳模塊egg-born-module-a-file就實現了功能的充分沉澱。爲何呢?由於業務模塊自己就包含前端代碼和後端代碼,可以施展的空間很大,能夠充分細化上傳邏輯

所以,在CabloyJS中要調用文件上傳功能,就會變得極其便捷。以CMS模塊爲例,上傳圖片並取得圖片URL,只需短短20行代碼

egg-born-module-a-cms/src/module/a-cms/front/src/pages/article/contentEdit.vue

...
    onUpload(mode, atomId) {
      return new Promise((resolve, reject) => {
        this.$view.navigate('/a/file/file/upload', {
          context: {
            params: {
              mode,
              atomId,
            },
            callback: (code, data) => {
              if (code === 200) {
                resolve({ text: data.realName, addr: data.downloadUrl });
              }
              if (code === false) {
                reject();
              }
            },
          },
        });
      });
    },
...
複製代碼
3) 模塊隔離

在大型Web項目中,不可避免的要考慮各種資源、各類變量、各個實體之間命名的衝突問題。針對這個問題,不一樣的開發團隊大都會規範各種實體的命名規範。隨着項目的擴充,這種命名規範仍然會變得很龐雜。若是咱們面對的是一個開放的系統,使用的是來自不一樣團隊開發的模塊,所面臨的命名衝突的風險就會愈加嚴重

CabloyJS使用了一個巧妙的設計,一勞永逸解決了命名衝突的隱患。在CabloyJS中,業務模塊採用以下命名規範:

egg-born-module-{providerId}-{moduleName}
複製代碼
  • providerId: 開發者Id,強烈建議採用Github的Username,從而確保貢獻到社區的模塊不會衝突
  • moduleName: 模塊名稱

因爲模塊自洽的設計機制,咱們只須要解決模塊命名的惟一性問題,在進行模塊開發時就不會再被命名衝突的困擾所糾纏了

好比,CMS模塊提供了一個前端頁面路由config/list。很顯然,如此簡短的路徑,在其餘業務模塊中出現的機率很是高。但在CabloyJS中,如此命名就不會產出衝突。在CMS模塊內部進行頁面跳轉時,能夠直接使用config/list,這稱之爲相對路徑引用。可是,若是其餘業務模塊也想跳轉至此頁面就使用/a/cms/config/list,這稱之爲絕對路徑引用

再好比,前面的例子咱們要調用上傳文件頁面,就是採用絕對路徑/a/file/file/upload

模塊隔離是業務模塊的核心特性。這是由於,模塊前端和後端有大量實體都須要進行這種隔離。CabloyJS從系統層面完成了這種隔離的機制,從而使得咱們在實際的模塊業務開發時能夠變得輕鬆、便捷。

模塊前端隔離機制

模塊前端的隔離機制由模塊egg-born-front來完成,實現了以下實體的隔離:

  1. 前端頁面組件路由:參見
  2. 前端參數配置:參見
  3. 前端狀態管理:參見
  4. 前端國際化:參見

模塊後端隔離機制

模塊後端的隔離機制由模塊egg-born-backend來完成,實現了以下實體的隔離:

  1. 後端API接口路由:參見
  2. 後端Service:參見

後端Service隔離,不只是解決命名衝突的須要,更是性能提高方面重要的考量。

好比有50個業務模塊,每一個模塊有20個Service,這樣全局就有1000個Service。 在EggJS中,這1000個Service須要一次性預加載以便供Controller代碼調用。CabloyJS就在EggJS的基礎上作了隔離處理,若是是模塊A的Controller,只須要預加載模塊A的20個Service,供模塊A的Controller調用。這樣,就實現了一箭雙鵰:不只命名隔離,並且性能提高,從而知足大型Web系統開發的需求

  1. 後端Model:參見

後端Model是CabloyJS實現的訪問數據實體的便捷工具,在Model的定義和使用上,都比Sequelize簡潔、高效

與後端Service同樣,後端Model也實現了命名隔離,同時也只能被模塊自身的Controller和Service調用

  1. 後端參數配置:參見
  2. 後端Error處理:參見
  3. 後端國際化:參見
4) 快速的前端構建

CabloyJS採用WebPack進行項目的前端構建。因爲CabloyJS項目是由一系列業務模塊組成的,所以,能夠把模塊代碼提早預編譯,從而在構建整個項目的前端時就能夠顯著提高構建速度

經實踐,若是一個項目包含40個業務模塊,若是按照普通的構建模式須要70秒構建完成。而採用預編譯的機制,則只須要20秒便可完成。這對於開發大型Web項目具備顯著的工程意義

5) 保護商業代碼

CabloyJS中的業務模塊,不只前端代碼能夠構建,後端代碼也能夠用WebPack進行構建。後端代碼在構建時,也能夠指定是否醜化,這種機制能夠知足保護商業代碼的需求

CabloyJS後端的基礎是EggJS,是如何作到能夠編譯構建的呢?

CabloyJS後端在EggJS的基礎上進行了擴展,每一個業務模塊都有一個入口文件main.js,經過main.js串聯後端全部JS代碼,所以能夠輕鬆實現編譯構建

1.3 CabloyJS的開發歷程

1.3.1 兩階段

CabloyJS從2016年啓動開發,主要歷經兩個開發階段:

1) 第一階段:EggBornJS

EggBornJS關注的核心就是實現一套完整的以業務模塊爲核心的全棧開發框架

好比模塊egg-born-front是框架前端的核心模塊,模塊egg-born-backend是框架後端的核心模塊,模塊egg-born是框架的命令行工具,用於建立項目骨架

這也是爲何全部業務模塊都是以egg-born-module-爲命名前綴的緣由

2) 第二階段:CabloyJS

EggBornJS只是一個基礎的全棧開發框架,若是要進行業務開發,還須要考慮許多與業務相關的支撐特性,如:用戶管理角色管理權限管理菜單管理參數設置管理表單驗證登陸機制,等等。特別是在先後端分離的場景下,對權限管理的要求就提高到一個更高的水平

CabloyJS在EggBornJS的基礎上,提供了一套核心業務模塊,從而實現了一系列業務支撐特性,並將這些特性進行有機的組合,造成完整而靈活的上層生態架構,從而支持具體的業務開發進程

換句話說,從實質上看,CabloyJS是一組核心業務模塊的組合,從形式上看,CabloyJS是一組模塊依賴項。且看CabloyJS的package.json文件:

cabloy/package.json

{
  "name": "cabloy",
  "version": "2.1.2",
  "description": "The Ultimate Javascript Full Stack Framework",
  ...
  "author": "zhennann",
  "license": "ISC",
  ...
  "dependencies": {
    "egg-born-front": "^4.1.0",
    "egg-born-backend": "^2.1.0",
    "egg-born-bin": "^1.2.0",
    "egg-born-scripts": "^1.1.0",
    "egg-born-module-a-version": "^2.2.2",
    "egg-born-module-a-authgithub": "^2.0.3",
    "egg-born-module-a-authsimple": "^2.0.3",
    "egg-born-module-a-base-sync": "^2.0.10",
    "egg-born-module-a-baseadmin": "^2.0.3",
    "egg-born-module-a-cache": "^2.0.3",
    "egg-born-module-a-captcha": "^2.0.4",
    "egg-born-module-a-captchasimple": "^2.0.3",
    "egg-born-module-a-components-sync": "^2.0.5",
    "egg-born-module-a-event": "^2.0.2",
    "egg-born-module-a-file": "^2.0.2",
    "egg-born-module-a-hook": "^2.0.2",
    "egg-born-module-a-index": "^2.0.2",
    "egg-born-module-a-instance": "^2.0.2",
    "egg-born-module-a-layoutmobile": "^2.0.2",
    "egg-born-module-a-layoutpc": "^2.0.2",
    "egg-born-module-a-login": "^2.0.2",
    "egg-born-module-a-mail": "^2.0.2",
    "egg-born-module-a-markdownstyle": "^2.0.3",
    "egg-born-module-a-mavoneditor": "^2.0.2",
    "egg-born-module-a-progress": "^2.0.2",
    "egg-born-module-a-sequence": "^2.0.2",
    "egg-born-module-a-settings": "^2.0.2",
    "egg-born-module-a-status": "^2.0.2",
    "egg-born-module-a-user": "^2.0.3",
    "egg-born-module-a-validation": "^2.0.4",
    "egg-born-module-test-cook": "^2.0.2"
  }
}
複製代碼

相信您經過這些核心模塊的名稱,就已經猜到這些模塊的用處了

1.3.2 總體架構圖

根據前面兩階段的分析,咱們就能夠勾勒出框架的總體架構圖

cabloy

這種架構,讓整個體系變得井井有條,也讓實際的Web項目的源代碼文件組織結構變得很是簡潔直觀。大量的架構細節都封裝在EggBornJS中,而咱們的Web項目只須要引用一個CabloyJS便可,CabloyJS負責引用架構中其餘核心模塊

這種架構,也讓實際的Web項目的升級變得更加容易,具體以下:

1) 刪除現有模塊依賴項
$ rm -rf node_modules
2) 若是有此文件,建議刪除
$ rm -rf package-lock.json 
3) 從新安裝全部模塊依賴項
$ npm i
複製代碼

1.3.3 意義

有了EggBornJS,今後可複用的不只僅是組件,還有業務模塊

有了CabloyJS,您就能夠快速開發各種業務應用

2 數據版本與開發流程

業務模塊必然要處理數據而且存儲數據,固然也不可避免會出現數據架構的變更,好比新增表、新增字段、刪除字段、調整舊數據,等等

CabloyJS經過巧妙的數據版本控制,可讓業務模塊在不斷的迭代過程當中,無縫的完成模塊升級和數據升級

在數據版本的基礎上,再配合一套開發流程,從而不管是在開發環境仍是生產壞境,都能有順暢的開發與使用體驗

2.1 數據版本

2.1.1 數據版本定義

能夠經過package.json指定業務模塊的數據版本,以模塊egg-born-module-test-cook爲例

egg-born-module-test-cook/package.json

{
  "name": "egg-born-module-test-cook",
  "version": "2.0.2",
  "eggBornModule": {
    "fileVersion": 1,
    "dependencies": {
      "a-base": "1.0.0"
    }
  },
  ...
}
複製代碼

模塊當前的數據版本fileVersion1。當這個模塊正式發佈出去以後,爲1的數據版本就處於封閉狀態。當有新的迭代,須要改變模塊的數據架構時,就須要將fileVersion遞增爲2。以此類推,從而完成模塊數據架構的自動無縫升級

2.1.1 數據版本升級

當CabloyJS後端服務在啓動時,會自動檢測每一個業務模塊的數據版本,當存在數據版本變動時,就會自動調用業務模塊的升級代碼,從而完成自動升級。仍以模塊egg-born-module-test-cook爲例,其數據版本升級代碼以下:

egg-born-module-test-cook/backend/src/service/version.js

...
    async update(options) {
      if (options.version === 1) {
        let sql = ` CREATE TABLE testCook ( id int(11) NOT NULL AUTO_INCREMENT, createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, updatedAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted int(11) DEFAULT '0', iid int(11) DEFAULT '0', atomId int(11) DEFAULT '0', cookCount int(11) DEFAULT '0', cookTypeId int(11) DEFAULT '0', PRIMARY KEY (id) ) `;
        await this.ctx.model.query(sql);

        sql = ` CREATE TABLE testCookType ( id int(11) NOT NULL AUTO_INCREMENT, createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, updatedAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted int(11) DEFAULT '0', iid int(11) DEFAULT '0', name varchar(255) DEFAULT NULL, PRIMARY KEY (id) ) `;
        await this.ctx.model.query(sql);

        sql = ` CREATE VIEW testCookView as select a.*,b.name as cookTypeName from testCook a left join testCookType b on a.cookTypeId=b.id `;
        await this.ctx.model.query(sql);

        sql = ` CREATE TABLE testCookPublic ( id int(11) NOT NULL AUTO_INCREMENT, createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, updatedAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted int(11) DEFAULT '0', iid int(11) DEFAULT '0', atomId int(11) DEFAULT '0', PRIMARY KEY (id) ) `;
        await this.ctx.model.query(sql);
      }
    }
...
複製代碼

當數據版本變動時,CabloyJS後端調用方法update,經過判斷屬性options.version的值,進行對應版本的數據架構變動

2.2 開發流程

2.2.1 背景

那麼問題來了?在模塊開發階段,若是須要變動數據架構怎麼辦呢?由於模塊尚未正式發佈,因此,不須要鎖定數據版本。也就是說,若是當前數據版本fileVersion1,那麼在正式發佈以前,不論進行多少次數據架構變動,fileVersion還是1

一方面,咱們確定要修改方法update,加入架構變動的代碼邏輯,好比添加表、添加字段等等

另外一方面,咱們還要修改當前測試數據庫中的數據架構。由於fileVersion是沒有變化的,因此當重啓CabloyJS後端服務時,方法update並不會再次執行

針對這種狀況,首先想到的是手工修改測試數據庫中的數據架構。而CabloyJS提供了更優雅的機制

2.2.2 運行環境

咱們知道EggJS提供了三個運行環境:測試環境開發環境生產環境。CabloyJS在EggJS的基礎上,對這三個運行環境賦予了進一步的意義

1) 測試環境
  • 測試環境的參數配置以下

{項目目錄}/src/backend/config/config.unittest.js

module.exports = appInfo => {
  const config = {};
  ...
  // mysql
  config.mysql = {
    clients: {
      // donnot change the name
      __ebdb: {
        host: '127.0.0.1',
        port: '3306',
        user: 'root',
        password: '',
        database: 'sys', // donnot change the name
      },
    },
  };
  ...
  return config;
};
複製代碼
  • 命令行以下:
$ npm run test:backend
複製代碼

因爲咱們將測試環境的數據庫名稱設爲sys,那麼CabloyJS就會自動刪除舊的測試數據庫,創建新的數據庫。由於是從新建立數據庫,那麼也就意味着fileVersion0升級爲1,從而觸發方法update的執行,進而自動完成數據架構的升級

2) 開發環境
  • 開發環境的參數配置以下

{項目目錄}/src/backend/config/config.local.js

module.exports = appInfo => {
  const config = {};
  ...
  // mysql
  config.mysql = {
    clients: {
      // donnot change the name
      __ebdb: {
        host: '127.0.0.1',
        port: '3306',
        user: 'root',
        password: '',
        database: 'sys', // recommended
      },
    },
  };
  ...
  return config;
};
複製代碼
  • 命令行以下:
$ npm run dev:backend
複製代碼

雖然咱們也將開發環境的數據庫名稱設爲sys,可是CabloyJS會自動尋找最新建立的測試數據庫,而後一直使用它

3) 生產環境
  • 生產環境的參數配置以下

{項目目錄}/src/backend/config/config.prod.js

module.exports = appInfo => {
  const config = {};
  ...
  // mysql
  config.mysql = {
    clients: {
      // donnot change the name
      __ebdb: {
        host: '127.0.0.1',
        port: '3306',
        user: 'root',
        password: '',
        database: '{實際數據庫名}',
      },
    },
  };
  ...
  return config;
};
複製代碼
  • 命令行以下:
$ npm run start:backend
複製代碼

由於生產環境存儲的都是實際業務數據,因此在生產環境就要設置實際的數據庫名稱了

2.2.3 開發流程的最佳實踐

根據前面數據版本運行環境的分析,咱們就能夠規劃出一套關於開發流程的最佳實踐:

  1. 當項目建立後,先執行一次npm run test:backend,用於自動建立一個測試數據庫
  2. 在進行常規開發時,執行npm run dev:backend來啓動項目後端服務,用於調試
  3. 若是模塊數據版本須要變動,在修改完屬性fileVersion和方法update以後,再一次執行npm run test:backend,從而重建一個新的測試數據庫
  4. 當項目須要在生產環境運行時,則運行npm run start:backend來啓動後端服務

3 特性鳥瞰

3.1 多實例與多域名

CabloyJS經過多實例的概念來支持多域名站點的開發。啓動一個服務,能夠支持多個實例運行。實例共享數據表架構,但運行中產生的數據是相互隔離的

這有什麼好處呢?好比您用CabloyJS開發了一款CRM的SAAS服務,那麼只需開發並運行一個服務,就能夠同時服務多個不一樣的客戶。每一個客戶一個實例,用一個單獨的域名進行區分便可。

再好比,要想開發一款基於微信公共號的營銷平臺,提供給不一樣的客戶使用,多實例與多域名是最天然、最有效的架構設計。

具體信息,請參見

3.2 數據庫事務

3.2.1 EggJS事務處理方式

const conn = await app.mysql.beginTransaction(); // 初始化事務

try {
  await conn.insert(table, row1);  // 第一步操做
  await conn.update(table, row2);  // 第二步操做
  await conn.commit(); // 提交事務
} catch (err) {
  // error, rollback
  await conn.rollback(); // 必定記得捕獲異常後回滾事務!!
  throw err;
}
複製代碼

3.2.2 CabloyJS事務處理方式

CabloyJS在EggJS的基礎上進行了擴展,使得數據庫事務處理變得更加天然,甚至能夠說是無痛處理

在CabloyJS中,實際的代碼邏輯不用考慮數據庫事務,若是哪一個後端API路由須要啓用數據庫事務,直接在API路由上聲明一箇中間件transaction便可,以模塊egg-born-module-test-cook爲例

egg-born-module-test-cook/backend/src/routes.js

...
  { method: 'get', path: 'test/echo/:id', controller: test, action: 'echo', middlewares: 'transaction' },
...
複製代碼

3.3 完美的用戶與身份認證分離體系

3.3.1 通用的身份認證

CabloyJS把用戶系統身份認證系統徹底分離,有以下好處:

  1. 支持衆多身份認證機制:用戶名/密碼認證、手機認證、第三方認證(Github、微信)等等
  2. 可徹底定製登陸頁面,自由組合各類身份認證機制
  3. 網站用戶也能夠自由添加不一樣的身份認證機制,也能夠自由的刪除

好比,用戶A先經過用戶名/密碼註冊的身份,之後還能夠添加Github、微信等認證方式

好比,用戶B先經過Github註冊的身份,之後還能夠添加用戶名/密碼等認證方式

3.3.2 通用的驗證碼機制

CabloyJS把驗證碼機制抽象了出來,而且提供了一個缺省的驗證碼模塊egg-born-module-a-captchasimple,您也能夠按統一規範開發本身的驗證碼模塊,而後掛接到系統中

3.3.3 通用的郵件發送機制

CabloyJS也實現了通用的郵件發送功能,基於成熟的nodemailer。因爲nodemailer內置了一個測試服務器,所以,在開發環境中,不須要真實的郵件發送帳號,也能夠進行系統的測試與調試

3.4 模塊編譯與發佈

前面咱們談到CabloyJS中的業務模塊是自洽的,能夠單獨編譯打包,既能夠顯著提高總體項目打包的效率,也能夠知足保護商業代碼的需求。這裏咱們看看模塊編譯與發佈的基本操做

3.4.1 如何編譯模塊

$ cd /path/to/module
  1) 構建前端代碼
$ npm run build:front
  2) 構建後端代碼
$ npm run build:backend
複製代碼

3.4.2 編譯參數

  1. 前端編譯:爲了提高總體項目打包的效率,模塊前端編譯默認開啓醜化處理
  2. 後端編譯:默認關閉醜化處理,可經過修改編譯參數開啓醜化選項

後端爲何默認關閉醜化選項呢?

答:CabloyJS全部內置的核心模塊都是關閉醜化選項的,這樣便於您直觀的調試整個系統的源代碼,也能夠很容易走進CabloyJS,發現一些更有趣的架構設計

{模塊目錄}/build/config.js

module.exports = {
  productionSourceMap: true,
  uglify: false,
};
複製代碼

3.4.3 模塊發佈

當項目中的模塊代碼穩定後,能夠將模塊公開發布,貢獻到開源社區。也能夠在公司內部創建npm私有倉庫,而後把模塊發佈到私有倉庫,造成公司資產,便於重複使用

$ cd /path/to/module
$ npm publish
複製代碼

4 業務開發

到目前爲止,實話說,前面談到的概念大多屬於EggBornJS的層面。CabloyJS在EggBornJS的基礎上,開發了大量核心業務模塊,從而支持業務層面的快速開發。下面咱們就介紹一些基本概念

4.1 原子的概念

4.1.1 原子是什麼

原子是CabloyJS最基本的要素,如文章、公告、請假單,等等

爲何叫原子?在化學反應中,原子是最基本的粒子。在CabloyJS中,經過原子的組合,就能夠實現任何想要的功能,如CMS、OA、CRM、ERP,等等

好比,您所看到的這篇文章就是一個原子

4.1.2 原子的意義

正因爲從各類業務模型中抽象出來一個通用的原子概念,於是,CabloyJS爲原子實現了許多通用的特性和功能,從而能夠便利的爲各種實際業務賦能

好比,模塊CMS中的文章能夠發表評論,能夠點贊,支持草稿搜索功能。這些都是CabloyJS核心模塊egg-born-module-a-base-sync提供的通用特性與功能。只要新建一個原子類型,這些原子都會被賦能

這就是抽象的力量

4.1.3 統一存儲

全部原子數據都會有一些相同的字段屬性,也會有與業務相關的字段屬性。相同的字段都統一存儲到數據表aAtom中,與業務相關的字段存儲在具體的業務表中,aAtom業務表是一對一的關係

這種存儲機制體現了共性差別性的有機統一,有以下好處:

  1. 可統一配置數據權限
  2. 可統一支持增刪改查等操做
  3. 可統一支持星標、標籤、草稿、搜索等操做

關於原子的更多信息,請參見

4.2 角色體系

角色是面向業務系統開發最核心的功能之一,CabloyJS提供了既簡潔又靈活的角色體系

4.2.1 角色模型

CabloyJS的角色體系不一樣於網上流行的RBAC模型

RBAC模型沒有解決業務開發中資源範圍受權的問題。好比,Mike是軟件部的員工,只能查看本身的日誌;Jone是軟件部經理,能夠查看本部門的日誌;Jimmy是企業負責人,能夠查看整個企業的日誌

RBAC模型概念複雜,在實際應用中,又每每引入新的概念(用戶組、部門、崗位等),使得角色體系疊牀架屋,理解困難,維護繁瑣

4.2.2 概念辨析

涉及到角色體系,每每會有這些概念:用戶用戶組角色部門崗位受權對象等等

而CabloyJS設計的角色體系只有用戶角色受權對象等概念,概念精簡,層次清晰,靈活高效,既便於理解,又便於維護

1) 部門即角色

部門從本質上來講,其實就是角色,如:軟件部財務部等等

2) 崗位即角色

崗位從本質上來講,其實也就是角色,如:軟件部經理軟件部設計崗軟件部開發崗等等

3) 資源範圍即角色

資源範圍也是角色。如:Jone是軟件部經理,能夠查看軟件部的日誌。其中,軟件部就是資源範圍

4.2.3 角色樹

CabloyJS針對各種業務開發的需求,提煉了一套內置角色,並造成一個規範的角色樹。實際開發中,可經過對角色樹的擴充和調整,知足各種角色相關的需求

  • root
    • anonymous
    • authenticated
      • template
      • registered
      • activated
      • superuser
      • organization
        • internal
        • external
名稱 說明
root 角色根節點,包含全部角色
anonymous 匿名角色,凡是沒有登陸的用戶自動納入匿名角色
authenticated 認證角色
template 模版角色,可爲模版角色配置一些基礎的、通用的權限
registered 已註冊角色
activated 已激活角色
superuser 超級用戶角色,如用戶root屬於超級用戶角色
organization 組織角色
internal 內部組織角色,如可添加軟件部財務部等子角色
external 外部組織角色,可爲合做夥伴提供角色資源

4.3 API接口權限

CabloyJS是先後端分離的模式,對API接口權限的控制需求就提高到一個更高的水平。CabloyJS提供了一個很是天然直觀的權限控制方式

好比模塊egg-born-module-a-baseadmin有一個API接口role/children,是要查詢某角色的子角色清單。這個API接口只容許管理員用戶訪問,咱們能夠這樣作

4.3.1 功能與API接口的關係

咱們把須要受權的對象抽象爲功能。這樣處理有一個好處:就是一個功能能夠綁定1個或多個API接口。當咱們對一個功能賦予了權限,也就對這一組綁定的API接口進行了訪問控制

4.3.2 功能定義

先定義一個功能role

egg-born-module-a-baseadmin/backend/src/meta.js

...
      functions: {
        role: {
          title: 'Role Management',
        },
      },
...  
複製代碼

4.3.3 功能綁定

再將功能API接口綁定

egg-born-module-a-baseadmin/backend/src/routes.js

...
  { method: 'post', path: 'role/children', controller: role,
  meta: { right: { type: 'function', name: 'role' } }
},
...
複製代碼
名稱 說明
right 全局中間件right,默認處於開啓狀態,只需配置參數便可
type function: 判斷功能受權
name 功能的名稱

4.3.4 功能受權

接下來,咱們就須要把功能role受權給角色superuser,而管理員用戶歸屬於角色superuser,也就擁有了訪問API接口role/children的權限

功能受權有兩種途徑:

  1. 調用API直接受權
  2. CabloyJS已經實現了功能受權的管理界面:用管理員身份登陸系統,進入工具 > 功能權限管理,進行受權配置便可

4.4 數據訪問權限

前面談到,針對各種業務數據,CabloyJS抽象出來原子的概念。對數據訪問受權,也就是對原子受權

原子受權主要解決這類問題:能對哪一個範圍內原子數據執行什麼操做,基本格式以下:

角色 原子類型 原子指令 資源範圍
superuser todo read 財務部

角色superuser僅能讀取財務部todo數據

更詳細信息,強烈建議參見

4.5 簡單流程

在實際的業務開發中,不免會遇到一些流程需求。好比,CMS中的文章,在做者提交以後,能夠轉入審覈員進行審覈,審覈經過以後方能發佈

當原子數據進入流程時,在不一樣的節點,處於不一樣的狀態(審覈中、已發佈),只能由指定的角色進行節點的操做

CabloyJS經過原子標記原子指令的配合實現了一個簡單的流程機制。也就是說,對於大多數簡單流程場景,不須要複雜的流程引擎,就能夠在CabloyJS中很輕鬆的實現

更詳細信息,強烈建議參見

5 解決方案

前面說到CabloyJS研發經歷了兩個階段:

  1. EggBornJS
  2. CabloyJS

若是說還有第三階段的話,那就是解決方案階段。EggBornJS構建了完整的NodeJS全棧開發體系,CabloyJS提供了大量面向業務開發的核心模塊。那麼,在EggBornJS和CabloyJS的基礎上,接下來就能夠針對不一樣的業務場景,研發相應的解決方案,解決實際的業務問題

5.1 Cabloy-CMS

CabloyJS是一個單頁面、先後端分離的框架,而有些場景(如博客社區等)更看重SEO、靜態化

CabloyJS針對這類場景,專門開發了一個模塊egg-born-module-a-cms,提供了一套文章靜態渲染的機制。CabloyJS自己自然的成爲CMS的後臺管理系統,從而造成動靜結合的特色,主要特性以下:

  • 內置多站點、多語言支持
  • 不一樣語言可單獨設置主題
  • 內置SEO優化,自動生成Sitemap文件
  • 文章在線撰寫、發佈
  • 文章發佈時實時渲染靜態頁面,沒必要整站輸出,提高總體性能
  • 內置文章查看計數器
  • 內置評論系統
  • 內置全文檢索
  • 文章可添加附件
  • 自動合併並最小化CSS和JS
  • JS支持ES6語法,並在合併時自動Babel編譯
  • 首頁圖片延遲加載,自動匹配設備像素比
  • 調試便捷

具體信息,請參見

5.2 Cabloy-Community

CabloyJS以CMS模塊爲基礎,開發了一個社區模塊egg-born-module-cms-sitecommunity,配置方式與CMS模塊徹底同樣,只需選用不一樣的社區主題便可輕鬆搭建一個交流社區(論壇)

6 將來規劃與社區建設

Atwood定律: 凡是能夠用JavaScript來寫的應用,最終都會用JavaScript來寫

CabloyJS將來規劃的核心之一,就是持續輸出高質量的解決方案,爲提高廣大研發團隊的開發效率不懈努力

CabloyJS以及全部核心模塊均已開源,歡迎你們加入CabloyJS,發Issue,點Star,提PR,更但願您能開發更多的業務模塊,共建CabloyJS的繁榮生態

7 名稱由來

最後再來聊聊框架名稱的由來

7.1 EggBornJS

這個名稱的由來比較簡單,由於有了Egg,因此就有了EggBorn。有一部動畫片叫《天書奇譚》,裏面的萌主就叫「蛋生」,我很喜歡看(不當心暴露了年齡😅)

7.2 CabloyJS

Cabloy來自藍精靈的魔法咒語,只有拼對了Cabloy這個單詞纔會有神奇的效果。一樣,CabloyJS是有關JS的魔法,基於模塊的組合與生化反應,您將實現您想要的任何東西

8 結語

親,您也能夠拼對Cabloy吧!這但是神奇的魔法喲!

相關文章
相關標籤/搜索