文章发表以后,也不免会有一些新的想法。若是新开一篇文章,会显得散乱;而直接修改正文,那些已经阅读过本篇文章的读者又没法快速定位修改的内容。所以,新增修订
章节,记录修订历史javascript
CabloyJS最大的亮点是:经过pc=mobile+pad
的模式,把mobile场景的操控体验
和开发模式
带⼊pc场景。既显著减小了代码开发量,提高了开发效率,⼜保持了用户操控体验的⼀致性html
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是一款顶级NodeJS全栈业务开发框架vue
既可快速开发,又可灵活定制java
为了实现此理念,CabloyJS内置开发了大量核心模块,使您能够在最短的时间内架构一个完整的Web项目。好比,当您新建一个Web项目时,就已经具有完整的用户登陆与认证系统,也具备验证码功能,同时也具有用户管理
、角色管理
、权限管理
等功能node
此外,这些内置模块提供了灵活的定制特性,您也能够开发全新的模块来替换内置模块,从而实现系统的定制化mysql
Mobile场景
咱们知道,随着智能机的日益普及,我们开发人员所面对的需求场景与开发场景日益碎片化,如浏览器、IOS、Android,还有大量第三方平台:微信、企业微信、钉钉、Facebook、Slack等等git
随着智能设备性能愈来愈好,网速愈来愈快,针对如此众多的开发场景,采用H5开发必将是大势所趋。只需开发一套代码,就能够在以上全部智能设备中运行,不只能够显著减小开发量,同时也能够显著提高开发效率,对开发团队和终端用户均是莫大的福利github
PC场景
以上我们说H5开发,只需开发一套代码,就能够在全部智能设备中运行。可是还有一个开发场景没有获得统一:那就是PC场景
sql
因为屏幕显示尺寸的不一样,PC场景
和Mobile场景
有着不一样的操做风格。有些前端UI框架,采用“自适应”策略,为PC场景开发的页面,在Mobile场景下虽然也能查看和使用,但使用体验每每差强人意
这也就是为何有些前端框架老是成对出现的缘由:如Element-UI和Mint-UI,如AntDesign和AntDesign-Mobile
这也就意味着,当咱们同时面对PC场景
和Mobile场景
时,仍然须要开发两套代码。在面对许多开发需求时,这些重复的工做量每每是难以接受的:
CabloyJS前端采用Framework7框架,目前已同步升级到最新版Framework7 V4。CabloyJS在Framework7的基础上进行了巧妙的扩展,将PC端的页面切分为多个区域,实现了多个Mobile和PAD同时呈如今一个PC端的效果。换句话说,你买了一台Mac,就相对于买了多台IPhone和IPad,用多个虚拟的移动设备同时工做,即显著提高了工做效率,也提供了很是有趣的使用体验
有图有真相
也可PC端体验
也可手机扫描体验
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,
},
},
};
复制代码
NodeJS的蓬勃发展,为先后端开发带来了更顺畅的体验,显著提高了开发效率。但仍有网友质疑NodeJS可否胜任大型Web应用的开发。大型Web应用的特色是随着业务的增加,须要开发大量的页面组件。面对这种场景,通常有两种解决方案:
CabloyJS实现了第三种解决方案:
在CabloyJS中,一切业务开发皆以业务模块为单位。好比,咱们要开发一个CMS建站工具,就新建一个业务模块,如已经实现的模块egg-born-module-a-cms
。该CMS模块包含十多个Vue页面组件,在正式发布时,就会构建成一个JS包。在运行时,只需异步加载这一个JS包,就能够访问CMS模块中任何一个Vue页面组件了。
所以,在一个大型的Web系统中,哪怕有数十甚至上百个业务模块,按CabloyJS的模块化策略进行代码组织和开发,既不会出现单一巨大的部署包,也不会出现大量碎片化的JS构建文件。
CabloyJS的模块化系统还有以下显著的特色:
也就是说,前面说到的模块化异步打包策略是已经精心调校好的系统核心特性,咱们只需像平时同样开发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
每一个业务模块都是自洽的总体,包含与本模块业务相关的前端代码和后端代码,并且采用先后端分离模式
模块自洽
既有利于自身的高度内聚
,也有利于整个系统的充分解耦
。业务模块只须要考虑自身的逻辑实现,容易实现业务的充分沉淀与分享
,达到即插即用
的效果
举一个例子:若是咱们要开发文件上传功能,当咱们在网上找到合适的上传组件以后,在本身的项目中使用时,仍然须要开发大量对接代码。也就是说,在网上找到的上传组件没有实现充分的沉淀,不是自洽的,也就不能实现便利的分享,达到即插即用
的效果
而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();
}
},
},
});
});
},
...
复制代码
在大型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是CabloyJS实现的访问数据实体的便捷工具,在Model的定义和使用上,都比Sequelize简洁、高效
与后端Service同样,后端Model也实现了命名隔离,同时也只能被模块自身的Controller和Service调用
CabloyJS采用WebPack进行项目的前端构建。因为CabloyJS项目是由一系列业务模块组成的,所以,能够把模块代码提早预编译,从而在构建整个项目的前端时就能够显著提高构建速度
经实践,若是一个项目包含40个业务模块,若是按照普通的构建模式须要70秒构建完成。而采用预编译的机制,则只须要20秒便可完成。这对于开发大型Web项目具备显著的工程意义
CabloyJS中的业务模块,不只前端代码能够构建,后端代码也能够用WebPack进行构建。后端代码在构建时,也能够指定是否丑化,这种机制能够知足保护商业代码
的需求
CabloyJS后端的基础是EggJS,是如何作到能够编译构建的呢?
CabloyJS后端在EggJS的基础上进行了扩展,每一个业务模块都有一个入口文件main.js,经过main.js串联后端全部JS代码,所以能够轻松实现编译构建
CabloyJS从2016年启动开发,主要历经两个开发阶段:
EggBornJS关注的核心就是实现一套完整的以业务模块为核心的全栈开发框架
好比模块egg-born-front
是框架前端的核心模块,模块egg-born-backend
是框架后端的核心模块,模块egg-born
是框架的命令行工具,用于建立项目骨架
这也是为何全部业务模块都是以egg-born-module-
为命名前缀的缘由
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"
}
}
复制代码
相信您经过这些核心模块的名称,就已经猜到这些模块的用处了
根据前面两阶段的分析,咱们就能够勾勒出框架的总体架构图
这种架构,让整个体系变得井井有条,也让实际的Web项目的源代码文件组织结构变得很是简洁直观。大量的架构细节都封装在EggBornJS中,而咱们的Web项目只须要引用一个CabloyJS便可,CabloyJS负责引用架构中其余核心模块
这种架构,也让实际的Web项目的升级变得更加容易,具体以下:
1) 删除现有模块依赖项
$ rm -rf node_modules
2) 若是有此文件,建议删除
$ rm -rf package-lock.json
3) 从新安装全部模块依赖项
$ npm i
复制代码
有了EggBornJS,今后可复用的不只仅是组件,还有业务模块
有了CabloyJS,您就能够快速开发各种业务应用
业务模块必然要处理数据而且存储数据,固然也不可避免会出现数据架构的变更,好比新增表、新增字段、删除字段、调整旧数据,等等
CabloyJS经过巧妙的数据版本控制,可让业务模块在不断的迭代过程当中,无缝的完成模块升级和数据升级
在数据版本的基础上,再配合一套开发流程,从而不管是在开发环境仍是生产坏境,都能有顺畅的开发与使用体验
能够经过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
。以此类推,从而完成模块数据架构的自动无缝升级
当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
的值,进行对应版本的数据架构变动
那么问题来了?在模块开发阶段,若是须要变动数据架构怎么办呢?由于模块尚未正式发布,因此,不须要锁定数据版本。也就是说,若是当前数据版本fileVersion
是1
,那么在正式发布以前,不论进行多少次数据架构变动,fileVersion
还是1
一方面,咱们确定要修改方法update
,加入架构变动的代码逻辑,好比添加表、添加字段等等
另外一方面,咱们还要修改当前测试数据库中的数据架构。由于fileVersion
是没有变化的,因此当重启CabloyJS后端服务时,方法update
并不会再次执行
针对这种状况,首先想到的是手工修改测试数据库中的数据架构。而CabloyJS提供了更优雅的机制
咱们知道EggJS提供了三个运行环境:测试环境
、开发环境
、生产环境
。CabloyJS在EggJS的基础上,对这三个运行环境赋予了进一步的意义
{项目目录}/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
的执行,进而自动完成数据架构的升级
{项目目录}/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会自动寻找最新建立的测试数据库,而后一直使用它
{项目目录}/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
复制代码
由于生产环境
存储的都是实际业务数据,因此在生产环境
就要设置实际的数据库名称了
根据前面数据版本
和运行环境
的分析,咱们就能够规划出一套关于开发流程
的最佳实践:
npm run test:backend
,用于自动建立一个测试数据库npm run dev:backend
来启动项目后端服务,用于调试fileVersion
和方法update
以后,再一次执行npm run test:backend
,从而重建一个新的测试数据库npm run start:backend
来启动后端服务CabloyJS经过多实例
的概念来支持多域名站点
的开发。启动一个服务,能够支持多个实例运行。实例共享数据表架构,但运行中产生的数据是相互隔离的
这有什么好处呢?好比您用CabloyJS开发了一款CRM的SAAS服务,那么只需开发并运行一个服务,就能够同时服务多个不一样的客户。每一个客户一个实例,用一个单独的域名进行区分便可。
再好比,要想开发一款基于微信公共号的营销平台,提供给不一样的客户使用,多实例与多域名
是最天然、最有效的架构设计。
具体信息,请参见
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;
}
复制代码
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' },
...
复制代码
CabloyJS把用户系统
与身份认证系统
彻底分离,有以下好处:
好比,
用户A
先经过用户名/密码
注册的身份,之后还能够添加Github、微信
等认证方式
好比,
用户B
先经过Github
注册的身份,之后还能够添加用户名/密码
等认证方式
CabloyJS把验证码机制抽象了出来,而且提供了一个缺省的验证码模块egg-born-module-a-captchasimple
,您也能够按统一规范开发本身的验证码模块,而后挂接到系统中
CabloyJS也实现了通用的邮件发送功能,基于成熟的nodemailer
。因为nodemailer
内置了一个测试服务器,所以,在开发环境中,不须要真实的邮件发送帐号,也能够进行系统的测试与调试
前面咱们谈到CabloyJS中的业务模块是自洽的,能够单独编译打包,既能够显著提高总体项目打包的效率,也能够知足保护商业代码
的需求。这里咱们看看模块编译与发布的基本操做
$ cd /path/to/module
1) 构建前端代码
$ npm run build:front
2) 构建后端代码
$ npm run build:backend
复制代码
后端为何默认关闭丑化选项呢?
答:CabloyJS全部内置的核心模块都是关闭丑化选项的,这样便于您直观的调试整个系统的源代码,也能够很容易走进CabloyJS,发现一些更有趣的架构设计
{模块目录}/build/config.js
module.exports = {
productionSourceMap: true,
uglify: false,
};
复制代码
当项目中的模块代码稳定后,能够将模块公开发布,贡献到开源社区。也能够在公司内部创建npm私有仓库,而后把模块发布到私有仓库,造成公司资产,便于重复使用
$ cd /path/to/module
$ npm publish
复制代码
到目前为止,实话说,前面谈到的概念大多属于EggBornJS的层面。CabloyJS在EggBornJS的基础上,开发了大量核心业务模块,从而支持业务层面的快速开发。下面咱们就介绍一些基本概念
原子是CabloyJS最基本的要素,如文章、公告、请假单,等等
为何叫原子?在化学反应中,原子是最基本的粒子。在CabloyJS中,经过原子的组合,就能够实现任何想要的功能,如CMS、OA、CRM、ERP,等等
好比,您所看到的这篇文章就是一个原子
正因为从各类业务模型
中抽象出来一个通用的原子
概念,于是,CabloyJS为原子实现了许多通用的特性和功能,从而能够便利的为各种实际业务赋能
好比,模块CMS中的文章能够发表评论
,能够点赞
,支持草稿
、搜索
功能。这些都是CabloyJS核心模块egg-born-module-a-base-sync
提供的通用特性与功能。只要新建一个原子类型,这些原子都会被赋能
这就是
抽象
的力量
全部原子数据都会有一些相同的字段属性,也会有与业务相关的字段属性。相同的字段都统一存储到数据表aAtom
中,与业务相关的字段存储在具体的业务表
中,aAtom
与业务表
是一对一的关系
这种存储机制体现了共性
与差别性
的有机统一,有以下好处:
数据权限
增删改查
等操做星标、标签、草稿、搜索
等操做关于原子
的更多信息,请参见
角色
是面向业务系统开发最核心的功能之一,CabloyJS提供了既简洁又灵活的角色体系
CabloyJS的角色体系不一样于网上流行的RBAC模型
RBAC模型
没有解决业务开发中资源范围受权
的问题。好比,Mike
是软件部的员工,只能查看本身的日志;Jone
是软件部经理,能够查看本部门的日志;Jimmy
是企业负责人,能够查看整个企业的日志
RBAC模型
概念复杂,在实际应用中,又每每引入新的概念(用户组、部门、岗位等),使得角色体系叠床架屋
,理解困难,维护繁琐
涉及到角色体系,每每会有这些概念:用户
、用户组
、角色
、部门
、岗位
、受权对象
等等
而CabloyJS设计的角色体系只有用户
、角色
、受权对象
等概念,概念精简,层次清晰,灵活高效,既便于理解,又便于维护
部门
从本质上来讲,其实就是角色,如:软件部
、财务部
等等
岗位
从本质上来讲,其实也就是角色,如:软件部经理
、软件部设计岗
、软件部开发岗
等等
资源范围
也是角色。如:Jone
是软件部经理,能够查看软件部
的日志。其中,软件部
就是资源范围
CabloyJS针对各种业务开发的需求,提炼了一套内置角色
,并造成一个规范的角色树
。实际开发中,可经过对角色树
的扩充和调整,知足各种角色相关的需求
名称 | 说明 |
---|---|
root | 角色根节点,包含全部角色 |
anonymous | 匿名 角色,凡是没有登陆的用户自动纳入匿名 角色 |
authenticated | 认证 角色 |
template | 模版 角色,可为模版角色配置一些基础的、通用的权限 |
registered | 已注册 角色 |
activated | 已激活 角色 |
superuser | 超级用户 角色,如用户root 属于超级用户 角色 |
organization | 组织 角色 |
internal | 内部组织 角色,如可添加软件部 、财务部 等子角色 |
external | 外部组织 角色,可为合做伙伴提供角色资源 |
CabloyJS是先后端分离的模式,对API接口权限
的控制需求就提高到一个更高的水平。CabloyJS提供了一个很是天然直观的权限控制方式
好比模块egg-born-module-a-baseadmin
有一个API接口role/children
,是要查询某角色的子角色清单。这个API接口只容许管理员用户访问,咱们能够这样作
咱们把须要受权的对象抽象为功能
。这样处理有一个好处:就是一个功能
能够绑定1个或多个API接口
。当咱们对一个功能
赋予了权限,也就对这一组绑定的API接口
进行了访问控制
先定义一个功能
:role
egg-born-module-a-baseadmin/backend/src/meta.js
...
functions: {
role: {
title: 'Role Management',
},
},
...
复制代码
再将功能
与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 | 功能的名称 |
接下来,咱们就须要把功能role
受权给角色superuser
,而管理员用户归属于角色superuser
,也就拥有了访问API接口role/children
的权限
功能受权
有两种途径:
工具
> 功能权限管理
,进行受权配置便可前面谈到,针对各种业务数据,CabloyJS抽象出来原子
的概念。对数据访问
受权,也就是对原子受权
原子受权
主要解决这类问题:谁
能对哪一个范围内
的原子数据
执行什么操做
,基本格式以下:
角色 | 原子类型 | 原子指令 | 资源范围 |
---|---|---|---|
superuser | todo | read | 财务部 |
角色
superuser
仅能读取财务部
的todo
数据
更详细信息,强烈建议参见
在实际的业务开发中,不免会遇到一些流程需求。好比,CMS中的文章,在做者提交以后,能够转入审核员进行审核,审核经过以后方能发布
当原子数据进入流程时,在不一样的节点,处于不一样的状态(审核中、已发布),只能由指定的角色进行节点的操做
CabloyJS经过原子标记
和原子指令
的配合实现了一个简单的流程机制。也就是说,对于大多数简单流程场景,不须要复杂的流程引擎
,就能够在CabloyJS中很轻松的实现
更详细信息,强烈建议参见
前面说到CabloyJS研发经历了两个阶段:
若是说还有第三阶段的话,那就是解决方案
阶段。EggBornJS构建了完整的NodeJS全栈开发体系,CabloyJS提供了大量面向业务开发的核心模块。那么,在EggBornJS和CabloyJS的基础上,接下来就能够针对不一样的业务场景,研发相应的解决方案
,解决实际的业务问题
CabloyJS是一个单页面、先后端分离的框架,而有些场景(如博客
、社区
等)更看重SEO、静态化
CabloyJS针对这类场景,专门开发了一个模块egg-born-module-a-cms
,提供了一套文章静态渲染
的机制。CabloyJS自己自然的成为CMS的后台管理系统,从而造成动静结合
的特色,主要特性以下:
具体信息,请参见
CabloyJS以CMS模块为基础,开发了一个社区模块egg-born-module-cms-sitecommunity
,配置方式与CMS模块彻底同样,只需选用不一样的社区主题
便可轻松搭建一个交流社区(论坛)
Atwood定律: 凡是能够用JavaScript来写的应用,最终都会用JavaScript来写
CabloyJS将来规划的核心之一,就是持续输出高质量的解决方案
,为提高广大研发团队的开发效率不懈努力
CabloyJS以及全部核心模块均已开源,欢迎你们加入CabloyJS,发Issue,点Star,提PR,更但愿您能开发更多的业务模块,共建CabloyJS的繁荣生态
最后再来聊聊框架名称的由来
这个名称的由来比较简单,由于有了Egg,因此就有了EggBorn。有一部动画片叫《天书奇谭》,里面的萌主就叫“蛋生”,我很喜欢看(不当心暴露了年龄😅)
Cabloy来自蓝精灵的魔法咒语,只有拼对了Cabloy这个单词才会有神奇的效果。一样,CabloyJS是有关JS的魔法,基于模块的组合与生化反应,您将实现您想要的任何东西
亲,您也能够拼对Cabloy吧!这但是神奇的魔法哟!