1 基本概念
1.1 CabloyJS是什麼
1.1.1 定義
CabloyJS是一款頂級NodeJS全棧業務開發框架
1.1.2 特點
- CabloyJS是採用NodeJS進行全棧開發的最佳實踐
- CabloyJS不重複造輪子,而是採用業界最新的開源技術,進行全棧開發的最佳組合
- CabloyJS前端採用VueJS + Framework7 + WebPack,後端採用KoaJS + EggJS,數據庫採用MySQL
- CabloyJS時刻跟蹤開源技術的最新成果,並持續優化,使整個框架時刻保持最佳狀態
1.1.3 理念
既可快速開發,又可靈活定製
爲了實現此理念,CabloyJS內置開發了大量核心模塊,使您可以在最短的時間內架構一個完整的Web項目。比如,當您新建一個Web項目時,就已經具備完整的用戶登錄與認證系統,也具有驗證碼功能,同時也具備用戶管理
、角色管理
、權限管理
等功能
此外,這些內置模塊提供了靈活的定製特性,您也可以開發全新的模塊來替換內置模塊,從而實現系統的定製化
1.2 CabloyJS核心解決什麼問題
- 場景碎片化
- 業務模塊化
1.2.1 場景碎片化
1) 先說說Mobile場景
我們知道,隨着智能機的日益普及,咱們開發人員所面對的需求場景與開發場景日益碎片化,如瀏覽器、IOS、Android,還有大量第三方平臺:微信、企業微信、釘釘、Facebook、Slack等等
隨着智能設備性能越來越好,網速越來越快,針對如此衆多的開發場景,採用H5開發必將是大勢所趨。只需開發一套代碼,就可以在以上所有智能設備中運行,不僅可以顯著減少開發量,同時也可以顯著提升開發效率,對開發團隊和終端用戶均是莫大的福利
2) 再來談談PC場景
以上咱們說H5開發,只需開發一套代碼,就可以在所有智能設備中運行。但是還有一個開發場景沒有得到統一:那就是PC場景
由於屏幕顯示尺寸的不同,PC場景
和Mobile場景
有着不同的操作風格。有些前端UI框架,採用“自適應”策略,爲PC場景開發的頁面,在Mobile場景下雖然也能查看和使用,但使用體驗往往差強人意
這也就是爲什麼有些前端框架總是成對出現的原因:如Element-UI和Mint-UI,如AntDesign和AntDesign-Mobile
這也就意味着,當我們同時面對PC場景
和Mobile場景
時,仍然需要開發兩套代碼。在面對許多開發需求時,這些重複的工作量往往是難以接受的:
- 比如,我們在企業微信或釘釘上開發一些H5業務應用,同時也希望這些應用也可以在PC端瀏覽器中運行
- 比如,我們爲微信公共號開發了一些H5業務應用,同時也希望這些應用也可以在PC端瀏覽器中運行。同時,還可以在同一架構下開發後臺管理類功能,通過區別不同的登錄用戶、不同的使用場景,從而顯示不同的前端頁面
3) PC = MOBILE + PAD
CabloyJS前端採用Framework7框架,目前已同步升級到最新版Framework7 V4。CabloyJS在Framework7的基礎上進行了巧妙的擴展,將PC端的頁面切分爲多個區域,實現了多個Mobile和PAD同時呈現在一個PC端的效果。換句話說,你買了一臺Mac,就相對於買了多臺IPhone和IPad,用多個虛擬的移動設備同時工作,即顯著提升了工作效率,也提供了非常有趣的使用體驗
4) 實際效果
有圖有真相
也可PC端體驗
也可手機掃描體驗
5) 如何實現的
CabloyJS是模塊化的全棧框架,爲了實現PC = MOBILE + PAD
的風格,內置了兩個模塊:egg-born-module-a-layoutmobile
和egg-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應用的特點是隨着業務的增長,需要開發大量的頁面組件。面對這種場景,一般有兩種解決方案:
- 採用單頁面的構建方式,缺點是產生的部署包很大
- 採用頁面異步加載方式,缺點是頁面過於零散,需要頻繁從後端獲取JS資源
CabloyJS實現了第三種解決方案:
- 頁面組件按業務需求歸類,進行模塊化,並且實現了模塊的異步加載機制,從而彌合了前兩種解決方案的缺點,完美滿足大型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
來完成,實現瞭如下實體的隔離:
模塊後端隔離機制
模塊後端的隔離機制由模塊egg-born-backend
來完成,實現瞭如下實體的隔離:
後端Service隔離,不僅是解決命名衝突的需要,更是性能提升方面重要的考量。比如有50個業務模塊,每個模塊有20個Service,這樣全局就有1000個Service。 在EggJS中,這1000個Service需要一次性預加載以便供Controller代碼調用。CabloyJS就在EggJS的基礎上做了隔離處理,如果是模塊A的Controller,只需要預加載模塊A的20個Service,供模塊A的Controller調用。這樣,就實現了一舉兩得:不僅命名隔離,而且性能提升,從而滿足大型Web系統開發的需求
- 後端Model:參見
後端Model是CabloyJS實現的訪問數據實體的便捷工具,在Model的定義和使用上,都比Sequelize簡潔、高效與後端Service一樣,後端Model也實現了命名隔離,同時也只能被模塊自身的Controller和Service調用
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 整體架構圖
根據前面兩階段的分析,我們就可以勾勒出框架的整體架構圖
這種架構,讓整個體系變得層次分明,也讓實際的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"
}
},
...
}
模塊當前的數據版本fileVersion
爲1
。當這個模塊正式發佈出去之後,爲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 背景
那麼問題來了?在模塊開發階段,如果需要變更數據架構怎麼辦呢?因爲模塊還沒有正式發佈,所以,不需要鎖定數據版本。也就是說,如果當前數據版本fileVersion
是1
,那麼在正式發佈之前,不論進行多少次數據架構變更,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就會自動刪除舊的測試數據庫,建立新的數據庫。因爲是重新創建數據庫,那麼也就意味着fileVersion
由0
升級爲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 開發流程的最佳實踐
根據前面數據版本
和運行環境
的分析,我們就可以規劃出一套關於開發流程
的最佳實踐:
- 當項目創建後,先執行一次
npm run test:backend
,用於自動創建一個測試數據庫 - 在進行常規開發時,執行
npm run dev:backend
來啓動項目後端服務,用於調試 - 如果模塊數據版本需要變更,在修改完屬性
fileVersion
和方法update
之後,再一次執行npm run test:backend
,從而重建一個新的測試數據庫 - 當項目需要在生產環境運行時,則運行
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把用戶系統
與身份認證系統
完全分離,有如下好處:
- 支持衆多身份認證機制:用戶名/密碼認證、手機認證、第三方認證(Github、微信)等等
- 可完全定製登錄頁面,自由組合各種身份認證機制
- 網站用戶也可以自由添加不同的身份認證機制,也可以自由的刪除
比如,用戶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 編譯參數
- 前端編譯:爲了提升整體項目打包的效率,模塊前端編譯默認開啓醜化處理
- 後端編譯:默認關閉醜化處理,可通過修改編譯參數開啓醜化選項
後端爲什麼默認關閉醜化選項呢?答: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
與業務表
是一對一的關係
這種存儲機制體現了共性
與差異性
的有機統一,有如下好處:
- 可統一配置
數據權限
- 可統一支持
增刪改查
等操作 - 可統一支持
星標、標籤、草稿、搜索
等操作
關於原子
的更多信息,請參見
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
的權限
功能授權
有兩種途徑:
- 調用API直接授權
- CabloyJS已經實現了功能授權的管理界面:用管理員身份登錄系統,進入
工具
>功能權限管理
,進行授權配置即可
4.4 數據訪問權限
前面談到,針對各類業務數據,CabloyJS抽象出來原子
的概念。對數據訪問
授權,也就是對原子授權
原子授權
主要解決這類問題:誰
能對哪個範圍內
的原子數據
執行什麼操作
,基本格式如下:
角色 | 原子類型 | 原子指令 | 資源範圍 |
---|---|---|---|
superuser | todo | read | 財務部 |
角色superuser
僅能讀取財務部
的todo
數據
更詳細信息,強烈建議參見
4.5 簡單流程
在實際的業務開發中,難免會遇到一些流程需求。比如,CMS中的文章,在作者提交之後,可以轉入審覈員進行審覈,審覈通過之後方能發佈
當原子數據進入流程時,在不同的節點,處於不同的狀態(審覈中、已發佈),只能由指定的角色進行節點的操作
CabloyJS通過原子標記
和原子指令
的配合實現了一個簡單的流程機制。也就是說,對於大多數簡單流程場景,不需要複雜的流程引擎
,就可以在CabloyJS中很輕鬆的實現
更詳細信息,強烈建議參見
5 解決方案
前面說到CabloyJS研發經歷了兩個階段:
- EggBornJS
- 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吧!這可是神奇的魔法喲!