一文读懂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吧!这但是神奇的魔法哟!

相关文章
相关标签/搜索