CRM —— 一个业务模块的解剖

本页用开源 crm-starter 插件(com.auraboot.crm-starter,命名空间 crm)讲清楚一件事:AuraBoot 上的业务模块长什么样、怎么搭出来。CRM 最普适——线索、商机、活动人人都懂。

更重要的是:AuraBoot 上每个业务模块都是同一个形状——模型 → 命令 → 权限 → 页面,全用 DSL 声明。所以读懂这一篇,采购库存质量生产 那些模块你也就懂了大半——它们只是换了名词和领域规则。全部模块见 业务场景目录

crm-starterconfig 型 demo(6 个 model);更完整的企业版 CRM(报价 / SLA / 投诉)是另一个插件。本页只描述 demo 真实交付的内容,每个标识符都能 grep 到。 源码:crm-starter on GitHub ↗

1. 用户场景

一支 B2B 销售团队:市场活动收集线索,跟进、资格化后转成商机,推进到赢单;过程中记录活动(电话/拜访/邮件),客户和联系人沉淀为账户主数据。难点不是存数据,而是每一步状态变更都要被授权、留痕,并且销售经理、市场、财务看到的视图和能做的操作各不相同。

CRM 线索列表 —— 中文演示数据:评分以进度条呈现、电话脱敏、来源与状态带语义色,顶部按状态分页签

2. 常规解法 vs AuraBoot 的设计哲学

这一节是平台级的设计哲学,各个 deep-dive 都引用回这里。

2.1 常规解法是怎样的

如果让一个团队从零做这个,通常是这条路:

  1. 领域建模:画实体和关系——账户、联系人、线索、商机、活动……
  2. 建表:翻译成一堆 CREATE TABLE,加外键、索引、状态字段。
  3. 写 CRUD:每个实体一套 Repository / Service / Controller。
  4. 把业务规则 hard-code 进 service:线索资格化、商机阶段流转(discovery→qualification→…)、转化时建商机、字段校验——全写死在 Java 里。
  5. 各处补横切关注点:权限判断散落在每个 controller;审计靠手动插日志;想把「商机赢单」事件发给下游(订单/财务),再搭一套 MQ。
  6. 前端:每个页面手写表单、列表、详情、看板。
  7. 想接 AI / 自动化:再包一层 API,再定义一遍权限和风险边界。

能跑,但业务规则散落在代码各处,改一条规则要同时动 service + controller + 前端;并发、审计、事件、AI 入口每一样都得自己扛。

2.2 AuraBoot 的设计哲学

AuraBoot 把这件事反过来:业务的「是什么」用声明描述,「怎么执行」由运行时统一兜底。四条核心理念,正好消掉上面的痛:

  • 元数据驱动:model / field / command / page / permission 都是声明(DSL JSON),不是手写代码。平台据此自动建表、生成接口、渲染页面——上面第 2、3、6 步基本消失。
  • 单一命令管道:所有写入都是一条命令,走同一条 命令管道。鉴权、校验、事务、审计、发事件这些横切关注点由管道统一处理,不写进每个业务里(第 4、5 步从「自己扛」变成「平台默认」)。
  • 命令即契约:同一条命令同时服务 UI 按钮、自动化规则、BPM 节点、AI agent(靠 cmd_risk_level + agent_hint 分级)。AI 原生不是事后包 API,而是天生的(第 7 步免了)。
  • 五层权限内建:RBAC + ReBAC + 组织域 + ABAC + 字段级 在管道里统一求值,不在 controller 里手写 if

2.3 同一个功能,两种活法

能力自己写(手写后端)打包 ERP / 典型低代码AuraBoot
状态流转状态机写死在 service,改流程改代码流程现成但改不动声明 crm:qualify_opportunity(discovery→qualification),状态机即配置
操作的唯一入口UI 调 controller,自动化/脚本各走各的多为 UI 封闭一条命令,UI / 自动化 / BPM / AI 同一条路径
权限粒度controller 里手写判断,字段级靠拼角色粗粒度,字段级常缺五层权限内建,字段级开箱
审计 / 事件手动插日志、再搭 MQ闭源难 hook管道自动留痕 + 发事件
被 AI 调用再包一层 API,无风险分级基本无原生 AI 入口命令带 cmd_risk_level + agent_hint,安全可控

一句话:手写灵活但什么都要自己扛;打包 ERP 快但改不动;AuraBoot 用声明式配置拿到一套带权限、审计、AI 入口的业务内核,而且能被你自由扩展。

3. 一个业务模块的解剖

每个业务模块都是这四件套。看懂 CRM 的,其它模块照搬。

3.1 模型(model + field)——声明数据

crm-starter 有 6 个 model:crm_account(账户)、crm_contact(联系人)、crm_lead(线索)、crm_opportunity(商机)、crm_activity(活动)、crm_campaign(营销活动)。

字段是物理列,按 model 加前缀。例如 crm_lead:crm_lead_companycrm_lead_contact_emailcrm_lead_source(enum)、crm_lead_score(integer)、crm_lead_status(enum)。类型用平台 dataType(string / integer / enum / text / date / decimal…),没有 string(120) 这种内联长度语法。详见 Model 与 Field

Model 管理中的声明式模型列表

3.2 命令(command)——声明操作 + 状态机

命令命名 crm:<动词>_<名词>。线索的生命周期就是一串状态流转命令:crm:contact_lead(new→contacted)、crm:qualify_leadcrm:convert_lead(转成商机)。一条命令的真实定义:

{
  "code": "crm:contact_lead",
  "displayName:zh-CN": "标记已联系",
  "type": "state_transition",
  "modelCode": "crm_lead",
  "stateField": "crm_lead_status",
  "fromStates": ["new"],
  "toState": "contacted",
  "permissions": ["crm.lead.manage"],
  "agent_hint": "Transition crm lead status from NEW to CONTACTED.",
  "cmd_risk_level": "L1"
}

读出来的设计信息:这是一条 state_transition(new → contacted),要求 crm.lead.manage 权限,风险级 L1,并通过 agent_hint 告诉 AI agent 它能做什么。页面上的「标记已联系」按钮接的就是这个命令码,而不是裸写表——所以 UI、自动化、agent 走的是同一条被授权、被审计的路径。详见 Command

3.3 页面(page)——声明 UI

crm-starter 用 21 个页面资源(6 个 model 各 list / form / detail = 18,加线索对账台,加 2 个 dashboard〔总览 + 线索分析〕)呈现这些数据,全部由 DSL 声明,Page Designer 拖出来。页面上的按钮绑命令码,列表/详情绑 model 字段。

线索表单 —— DSL 声明的录入页:客户信息 / 线索详情 / 分配与需求 分组,公司名称·联系人必填、字段带字符计数,电话保存后自动脱敏

3.4 权限(permission + role)——声明谁能做什么

12 个权限码(<模块>.<资源>.<动作>,每资源 manage + read),2 个角色 crm_admin / crm_sales。它们由平台的五层权限引擎求值。

四件套就是全部:换个领域(采购、库存、质量),就是换一组 model + 命令 + 权限 + 页面,形状不变。

4. 具体开发与实施

先掌握基础:Model 与 Field · Command · 命令管道 · Permission · 插件清单 · 纯配置 Plugin · Page Designer

落地一个 config 插件的步骤:① 定义 model 与字段 → ② 声明命令(状态流转用 type: state_transition + 绑权限)→ ③ 配 model-field binding(独立 bindingRules.json 并在 resourceDirs 注册)→ ④ 设计页面 → ⑤ 权限/角色/字典/菜单 → ⑥ 用 aura CLI import-directory-sync 导入,校验返回 success:true

5. 典型错误

  • 命令码用点号:写成 crm.lead.contact 跑不通——真实是冒号 + 动词_名词:crm:contact_lead
  • 绕过命令直接改表:跳过状态机、权限、审计和事件。一切写入都走命令。
  • bindingRules 内联进 commands.json:不会被导入;必须独立 bindingRules.json 并在 resourceDirs 注册。详见 纯配置 Plugin
  • 命令执行 payload 结构:字段放 { "payload": { ... }, "operationType": ... },目标记录用 targetRecordId(不是 recordId)。

下一步