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

通过原子的组合,就可以实现任何想要的功能,如CMS、OA、CRM、ERP等等

正由于从各种业务模型中抽象出来一个通用的原子概念,因而,CabloyJS为原子实现了许多通用的特性和功能,从而可以便利的为各类实际业务赋能

更详细的原子概念请参见:Cabloy:原子基本概念

在这里,主要通过原子类型party介绍最基本的概念与用法

业务模块模版:module-business

这里再次强调一下,如果要开发一项业务功能,建议使用业务模块模版module-business创建业务模块的文件骨架。此模版会自动创建与业务相关的代码,大量简化工作量

$ cd /path/to/project
$ npm init cabloy src/module/test-party --type=module-business

如果网速慢,可以使用淘宝镜像:

$ npm init cabloy src/module/test-party --type=module-business --registry=https://registry.npm.taobao.org

声明原子类型

原子类型原子对应的元数据信息,也是在模块的meta中设置

src/module/test-party/backend/src/meta.js

const meta = {
  base: {
    atoms: {
      party: {
        info: {
          title: 'Party',
          tableName: 'testPartyView',
          flow: 0,
        },
        actions: {
        },
        flags: {
        },
        validator: 'party',
        search: {
          validator: 'partySearch',
        },
      },
    },
  },
};
名称 说明
info.title 原子类型的名称
info.tableName 原子类型对应的业务数据表名称,也可以指定数据视图
info.flow 是否启用简单流程
validator 原子类型对应的验证器,用于渲染业务表单,并验证表单数据
search.validator 与搜索相关的验证器,用于渲染自定义的搜索字段

原子业务数据表

所有与模块数据架构变更相关的代码都在后端API路由version/update中,便于维护模块的数据版本,从而实现模块的无缝升级

src/module/test-party/backend/src/service/version.js

async update(options) {
  if (options.version === 1) {
    // create table: testParty
    let sql = `
      CREATE TABLE testParty (
        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',
        personCount int(11) DEFAULT '0',
        partyTypeId int(11) DEFAULT '0',
        PRIMARY KEY (id)
      )
    `;
    await this.ctx.model.query(sql);
  }
}

model对象

定义与业务数据表对应的model对象,可以更便利的操作数据

src/module/test-party/backend/src/model/party.js

module.exports = app => {
  class Party extends app.meta.Model {
    constructor(ctx) {
      super(ctx, { table: 'testParty', options: { disableDeleted: false } });
    }
  }
  return Party;
};
名称 默认值 说明
table model对象对应的数据表名
disableDeleted false 是否禁用软删除特性

验证器

CabloyJS的验证机制底层采用ajv,建议您对ajv有初步的了解

使用验证器,我们只需定义好与业务相关的JSON Schema,就可以自动渲染表单,同时还可以自动验证表单数据,如果表单数据不符合预期,会自动把错误信息显示出来

关于表单验证的更详细信息请参见:CabloyJS:表单验证

src/module/test-party/backend/src/config/validation/schemas.js

module.exports = app => {
  const schemas = {};
  // party
  schemas.party = {
    type: 'object',
    properties: {
      atomName: {
        type: 'string',
        ebType: 'text',
        ebTitle: 'Party Name',
        notEmpty: true,
      },
      personCount: {
        type: 'number',
        ebType: 'text',
        ebTitle: 'Person Count',
        notEmpty: true,
      },
      partyTypeId: {
        type: 'number',
        ebType: 'select',
        ebTitle: 'Party Type',
        ebOptionsUrl: '/test/party/party/types',
        ebOptionTitleKey: 'name',
        ebOptionValueKey: 'id',
        ebOptionsBlankAuto: true,
        notEmpty: true,
      },
    },
  };
  // party search
  schemas.partySearch = {
    type: 'object',
    properties: {
      partyTypeId: {
        type: 'number',
        ebType: 'select',
        ebTitle: 'Party Type',
        ebOptionsUrl: '/test/party/party/types',
        ebOptionTitleKey: 'name',
        ebOptionValueKey: 'id',
        ebOptionsBlankAuto: true,
      },
    },
  };
  return schemas;
};

原子指令API路由

CabloyJS将所有业务数据的操作称为原子指令,主要分两类:

  1. 基本指令:create、read、write、delete
  2. 扩展指令:与具体业务相关的操作,如reviewpublish等等

CabloyJS提供了一套基础API路由,对原子指令进行了封装,这样可以统一配置数据库事务数据权限等中间件

业务模块只需提供业务API路由即可,这些业务API路由会在合适的时候被基础API路由调用

业务API路由

src/module/test-party/backend/src/routes.js

  // party
  // atom: party
  { method: 'post', path: 'party/create', controller: party, middlewares: 'inner', meta: { auth: { enable: false } } },
  { method: 'post', path: 'party/read', controller: party, middlewares: 'inner', meta: { auth: { enable: false } } },
  { method: 'post', path: 'party/select', controller: party, middlewares: 'inner', meta: { auth: { enable: false } } },
  { method: 'post', path: 'party/write', controller: party, middlewares: 'inner,validate',
        meta: {
          auth: { enable: false },
          validate: { validator: 'party', data: 'item' },
        },
      },
  { method: 'post', path: 'party/delete', controller: party, middlewares: 'inner', meta: { auth: { enable: false } } },
  { method: 'post', path: 'party/action', controller: party, middlewares: 'inner', meta: { auth: { enable: false } } },
  { method: 'post', path: 'party/enable', controller: party, middlewares: 'inner', meta: { auth: { enable: false } } },
名称 说明
middlewares: ‘inner’ 中间件inner,声明此API路由只允许内部访问,在这里是被基础API路由访问
middlewares: ‘auth’ 由于基础API路由在调用业务API路由时都会传入对象user,因此这里就禁用全局中间件auth,从而减少不必要的操作
party/create 新建party时调用
party/read 查询单条party时调用
party/select 查询多条party时调用
party/write 修改party时调用
party/delete 删除party时调用
party/action 在这个路由中实现与业务相关的扩展指令
party/enable party草稿状态转为正常状态时调用

业务逻辑代码

src/module/test-party/backend/src/service/party.js

module.exports = app => {

  class Party extends app.Service {

    async create({ atomClass, key, item, user }) {
      // add party
      const res = await this.ctx.model.party.insert({
        atomId: key.atomId,
      });
      // return key
      return { atomId: key.atomId, itemId: res.insertId };
    }

    async read({ atomClass, key, item, user }) {
      // read
    }

    async select({ atomClass, options, items, user }) {
      // select
    }

    async write({ atomClass, key, item, validation, user }) {
      // update party
      await this.ctx.model.party.update({
        id: key.itemId,
        personCount: item.personCount,
        partyTypeId: item.partyTypeId,
      });
    }

    async delete({ atomClass, key, user }) {
      // delete party
      await this.ctx.model.party.delete({
        id: key.itemId,
      });
    }

    async action({ action, atomClass, key, user }) {
    }

    async enable({ atomClass, key, atom, user }) {
    }

  }

  return Party;
};