activiti.md 17 KB

Activiti 集成

TL;DR:Activiti 已接线,流程引擎会启动,但没有流量经过它。 引擎已完成 bootstrap,act_* schema 已按 6.0.0.4 版本建好,BPMN modeler 可访问。但所有工作流表都是 0 行:没有 BPMN、没有 procdef、没有流程实例、没有任务;没有 gdsmodule.bCheck=1,也没有 gdsmoduleflow 关联。用户看到的审批按钮(/business/doExamine完全绕过 Activiti,只是通过 SQL 把 bCheck 更新为 1。细节见下面的 xly 如何在不使用 Activiti 的情况下处理工作流

本页记录实际已接线的内容(具体类、URL、流程引擎状态),以及要让它真正工作需要满足什么条件。

xly 如何在不使用 Activiti 的情况下处理工作流 {#xly-如何在不使用-activiti-的情况下处理工作流}

xly 有三种类似工作流的机制,按实际使用程度从高到低排序如下。

路径 1:存储过程 + bCheck 标志的一步审批(主流模式)

这是 xly 中 99% 审批的样子。这里没有流程引擎,也没有状态机;工作流就是那次过程调用本身。

机制:

  1. 每张业务表携带同一组三个审计列:bCheck(审批布尔值)、sCheckPersontCheckDate。在当前 dev DB 中,bCheck 出现在 426 张表上,tCheckDate 出现在 400 张表上,sCheckPerson 出现在 398 张表上。也就是说,几乎每个业务单据都把审批审计轨迹放在自己行里。
  2. 每个模块在 gdsmodule.sProcName 中声明一个单一审批过程名(列注释是“存储过程(审核)名称”)。例如报价模块的 Sp_Quo_QuotationCheck、销售订单的 Sp_SalSalesCheck
  3. 用户点击“审核”按钮时:
    • POST /business/doExamineBusinessBaseController.java:384-391BusinessBaseServiceImpl.doExamine()ExamineServiceImpl.doExcuExamine()
    • service 获取一个基于行 id 的 Redis 锁(ws_update_*_{sGuid}_*),避免两个用户并发审批。
    • 它通过通用过程调用机制分发到 sProcName 命名的过程,见通用存储过程分发
    • 过程本身拥有业务逻辑:校验必填字段、子行和主表合计是否平衡、相关单据是否锁定等。如果全部通过,过程更新该行:bCheck = 1sCheckPerson = <user>tCheckDate = NOW()
    • 过程返回 OUT sCode INT(1 表示成功,≤0 表示错误)和 OUT sReturn LONGTEXT(错误消息)。
  4. 反审核是同一调用,只是 iFlag = 0 而不是 1;过程同时处理两个方向。
  5. Sp_System_CheckSave 是每个带 bCheck 行保存后的通用钩子,由 BusinessBaseServiceImpl.java:1828 调用。它写入 sFormId 审计字段,并保留跨单据校验的占位逻辑(当前大多已注释)。

这条路径没有“下一个审批人”或“审批队列”的概念。一个有权限的用户点击审核且过程成功后,该行就审批完成。

路径 2:单据串联形成的隐式多步工作流

多单据业务流程(报价 → 客户确认 → 销售订单 → 发货 → 发票)不是靠一个单据在状态之间推进,而是靠多个独立模块和表单实现。用户视角是:

  1. 模块 A(例如 quoQuotationMaster):填单,点击审核,行变成 bCheck = 1
  2. 模块 A 上有一个按钮(通过 gdsconfigformslave.sButtonParam 指向 Sp_Quo_ToSalesOrder 或类似过程)。点击后,过程创建模块 B(例如 salSalesOrderMaster)中的一行,并用模块 A 的数据预填。
  3. 用户进入模块 B,补充字段,再在那里审核,依次推进。

FROUNT KPI 工作中心 上的 “01/04、02/04、03/04、04/04” 步骤编号反映的就是这个模型:每个“流程”是一个父模块,下面有 N 个有序子模块;步骤只是父 gdsmodule 条目下的子模块。没有流程引擎在跟踪“你处于四步中的第二步”;用户只是操作当前打开的单据,框架用父模块下子模块的 iOrder 提供上下文。

因此,多步“工作流”来自:

  • 一个按主题分组步骤的父 gdsmodule(例如 估价管理流程,包含 4 个子模块)。
  • 每个子模块自己的 sProcName(一步审批)。
  • 每个子模块自己的 sButtonParam 过程,点击后创建下一张单据。
  • 每张业务单据自己的 bCheck 标志。

没有状态机,也没有 FSM 库;只是通过表单按钮把一串存储过程接起来。这是当前 dev DB 中能看到的机制。

路径 3:Activiti BPMN 工作流(有闸门,目前代码中禁用) {#路径-3activiti-bpmn-工作流有闸门目前代码中禁用}

路径 3 作为第三条通道存在于代码库中:启用后会经由 Activiti。但它没有在当前 dev DB 中运行,而且目前不重新编译无法启用。

闸门硬编码在 xlyPersist/.../utils/ConstantUtils.java

public static Boolean bCheckflowCheck = false;

ExamineServiceImpl.doExcuExamine() 内部分发逻辑是:

if (ConstantUtils.bCheckflowCheck) {
    Map<String,Object> reMap = checkExamineFlowService
        .doSendCheckFolw(sGuid, sUserName, sBrandsId, sSubsidiaryId,
                         sFormId, map, searMap, sBtnName, request);
    if (MapUtil.isNotEmpty(reMap)) { return reMap; }
}

所以即使租户部署了 BPMN,并通过 gdsmoduleflow 关联了模块,这个 if 也会因为 bCheckflowCheck 是 Java 常量 false 而短路。要启用路径 3,需要:

  1. 把源码中的 ConstantUtils.bCheckflowCheck 改为 true 并重新构建 WAR,或在运行时 patch 该常量。
  2. 插入一行以 (sFormId, sBtnName) 为 key 的 gdsmoduleflow,把表单上的审批按钮映射到已部署 BPMN。
  3. 通过 modeler 部署 BPMN,让 act_re_procdef 有数据。
  4. 确认相关模块的 bCheck 语义与 BPMN 起止事件对齐。

启用后,CheckExamineFlowServiceImpl.doSendCheckFolw 会读取 gdsmoduleflow 行,调用 checkExamineFlowDataService.doSendCheckFolwData 预置数据,再经 doSendFlowUrl 跳到 xlyFlow controller。随后 ProcessServiceImpl.submitApply() 调用 runtimeService.startProcessInstanceByKey(...),Activiti 接管流程;biz_flow + biz_todo_item 填充,审批人在收件箱中看到任务,CurrencyFlowController.complete(...) 推进流程实例。

对比

方面 路径 1(proc + bCheck) 路径 2(单据链) 路径 3(Activiti)
状态存储 单据行上的 bCheck 无;状态等于用户打开哪张单据 act_ru_taskact_hi_*biz_flowbiz_todo_item
步骤转换 每张单据一步 每个链路按钮触发“转下一张单据”的过程 流程引擎按 BPMN 图驱动转换
转派 / 委托 不支持 不支持 Activiti 支持
并行分支 不支持 不支持 BPMN 网关支持
当前是否活跃 是,每次“审核”点击都会用 是,多单据业务流都在用 否,代码中 bCheckflowCheck = false
工具 只有存储过程 存储过程 + 模块树配置 /modeler/* 下的 BPMN modeler

真实路径 1 定制示例

切片 5 追踪了万昌的 领班驳回.sql:这是客户侧多级审批驳回的典型例子。它展示了客户在单个 bCheck 不够时如何扩展路径 1:ALTER TABLE 增加多个审批标志(bManagerbIPQCbDeputy 等),按 Sp_<table>_check<currentState>_<nextState> 约定编写状态转换过程,并通过自定义 sp_add_flow_log 写审计。这是代码库中实证可见的定制通道;script/客户/ 下没有任何目录部署 BPMN。

为什么这个设计适合 xly 的用户

印刷行业 ERP 客户的业务流程通常是规则驱动的(报价 → 订单 → 生产 → 发货 → 开票 → 收款),每一步按惯例都是自己的单据和自己的表单。用户预期的是“现在打开下一张表单继续填”,而不是“系统告诉我有一个 task 等我处理”。对这类用户:

  • 路径 1 + 路径 2 覆盖了当前 dev DB 中观察到的所有场景。
  • 路径 3 的价值(BPMN 建模、转派、并行网关)留给极少数审批图确实需要它的租户。

代价是:工作流逻辑分散在存储过程中,而不是集中声明在一个地方。给流程增加新步骤意味着写或改一个或多个过程,而不是编辑 BPMN 图。对复杂且频繁变化的流程,这会很脆弱;但对印刷厂现实中的 quote-to-cash 链条(每客户不常变化)来说,这是务实选择。

Activiti 已接线:流程引擎已开启

尽管 dev DB 处于空转状态,流程引擎会随 xlyEntry 启动:

  • xlyFlow/build.gradle:15 引入 org.activiti:activiti-spring-boot-starter-rest-api:6.0.0。该 starter 传递引入 activiti-spring-boot-starter,触发 Spring Boot 的 ProcessEngineAutoConfiguration 创建 SpringProcessEngineConfiguration bean。
  • xlyEntry/build.gradle 通过 api project(':xlyFlow') 引入 xlyFlow,因此 starter 在 xlyEntry WAR 的运行时 classpath 上。
  • xlyEntry/.../EntryApplicationBoot.java:23-24 只排除了 org.activiti.spring.boot.SecurityAutoConfiguration(REST 端点安全适配器)和 Spring 自己的 SecurityAutoConfigurationActiviti 主流程引擎 auto-config 没有被排除,所以引擎会启动。
  • xlyFlow/.../activiti/config/ActivitiConfig.java 是一个 @Configuration implements ProcessEngineConfigurationConfigurer,只做两件事:把图生成器字体设为中文友好的 宋体(activity / annotation / label fonts),并安装自定义 ICustomProcessDiagramGenerator
  • xlyApiApiApplicationBoot 也没有排除 Activiti,但 xlyApi 不依赖 xlyFlow。因此 xlyApi classpath 上有流程引擎里的 org.activiti.engine.identity.User 类(仅由 IdGen.java 加密工具使用),但不会触发 Activiti auto-config。

实时 schema 中看到的 24 张基础 act_* 表,是流程引擎首次启动时通过 auto-DDL 创建的。

classpath 上的两个 Activiti 版本

模块 版本 说明
xlyPersistxlyApi org.activiti:activiti-engine:5.17.0 较老的 5.x 线,两个模块都有声明。遗留依赖xlyEntry WAR 中实际运行的流程引擎是 xlyFlow starter 拉入的 6.0。5.17 声明只是 classpath 上的包袱。
xlyFlow org.activiti:activiti-spring-boot-starter-rest-api:6.0.0activiti-json-converter:6.0.0 较新的 6.0 线,这才是会运行的版本。

做清理的维护人员应从 xlyPersistxlyApibuild.gradle 中移除 5.17.0。已验证:这两个模块只有 IdGen.java 会触碰 org.activiti.engine.identity.User,而该类型签名由 6.0 流程引擎也能满足,因此移除是安全的。

代码中实际调用了什么

xlyFlow/src/main/java/com/xly/activiti/ 下 154 个 Java 文件加 modeler 子包是真实调用点。选取锚点如下:

活动 类 : 行 使用的 Activiti API
启动流程实例 ProcessServiceImpl.submitApply() :107 runtimeService.startProcessInstanceByKey(module, businessKey, variables)
完成任务 CurrencyFlowController.complete(...) :167 / :200;WechatFlowPostThread :132 processService.complete(taskId, ...)taskService.complete()
查询活跃任务 CurrencyFlowController :409、:480 taskService.createTaskQuery().active().list()
查询运行中实例 CurrencyFlowController :485、:659 runtimeService.createProcessInstanceQuery()
在 modeler 中保存模型 ModelerController.create() :122 repositoryService.saveModel() + addModelEditorSource()
运行时部署 BPMN ModelerController.deploy() :147 repositoryService.createDeployment().addString(name, bpmnXml).deploy()
列出流程定义 ProcessDefinitionController :135 repositoryService.createProcessDefinitionQuery()
读取流程引擎配置 ProcessActController :281 ProcessEngines.getDefaultProcessEngine()
把 xly 用户桥接到 Activiti identity act_id_user / act_id_group / act_id_membership 是投影 xly sftlogininfo* schema 的视图 xly 不写 Activiti identity 表;这些视图伪装成 identity 表

modeler 暴露的 URL(xlyFlow controller 挂在 xlyEntry 端口上) {#modeler-暴露的-urlxlyflow-controller-挂在-xlyentry-端口上}

xlyFlow 被 xlyEntry 作为库消费(api project(':xlyFlow')),因此 xlyFlow controller 会编译进 xlyEntry WAR,并在 xlyEntry context-path(/xlyEntry)下服务。重要 URL:

  • POST /xlyEntry/modeler/model/{modelId}/save:保存 BPMN modeler XML。
  • GET /xlyEntry/modeler/model/{modelId}/json:为编辑器加载模型。
  • GET /xlyEntry/modeler/editor/stencilset:modeler stencil 定义。
  • GET /xlyEntry/modeler/create / /modeler/deploy/{modelId}:创建和部署。
  • POST /xlyEntry/currencyFlow/complete/{taskId}/{sBrandsId}/{sSubsidiaryId}/{sUserId}:完成任务(CurrencyFlowController)。
  • POST /xlyEntry/currencyFlow/completeerp/{sBrandsId}/{sSubsidiaryId}/{sUserName}:ERP 侧完成任务变体。

这些 URL 不在内部 API 页中完整编目,因为它们是很少触碰的工作流接口面;维护时以源码为准。

CheckFlowController.java 实际包含什么

这是一个需要明确标出的 wiki 内部纠正:xlyEntry/src/main/java/com/xly/web/businessweb/CheckFlowController.java 文件存在,但类体只有 22 行、0 个 handler 方法,只是一个带 @RestController @RequestMapping(value="/checkflow") 的空壳。早期 wiki 把 /checkflow/* 描述为“Activiti 工作流接口面(审批 / 驳回 / 查看)”,这不符合当前文件。实时系统上任何 /checkflow/* 子路径都会返回 404。 真正的审批 / 驳回 / 查看 URL 来自上面列出的 CurrencyFlowController 等 controller。

act_* schema 状态(当前 dev DB)

行数 有数据时的含义
act_re_model 0 modeler 中保存的 BPMN 模型
act_re_procdef 0 已部署流程定义
act_ru_task 0 活跃等待任务
act_hi_procinst 0 历史流程实例
act_id_user / act_id_group / act_id_membership 视图 把 xly 的 sftlogininfo* 用户投影成 Activiti identity 形状
gdsmoduleflow 0 xly 从 gdsmodule 到流程定义的关联
biz_flow 0 xly 的每单据流程状态
biz_todo_item 0 待审批任务(xly wrapper,不是 Activiti 的 act_ru_task
biz_todo_copyto 0 流程抄送方

所以 Activiti 当前是彻底空转:流程引擎在运行,schema 已就绪,没有流量。

什么会让它动起来

要让一个流程真正运行,大致顺序如下:

  1. 工程师或 PM 打开 modeler UI(静态资源位于 xlyFlow/src/main/resources/static/modeler/,通过 /modeler/* 端点服务)。他们绘制 BPMN 并保存,act_re_model 填充。
  2. 在 modeler 中点击 DeployModelerController.deploy() 调用 repositoryService.createDeployment().addString(name, bpmnXml).deploy()act_re_procdef 填充。
  3. 某个 gdsmodule 行标记 bCheck = 1,并在 gdsmoduleflow 中插入一行,把该模块关联到已部署的 act_re_procdef.KEY_
  4. 用户在该模块保存一行时,保存 service 检测 bCheck = 1,调用 ProcessServiceImpl.submitApply(applyUserId, businessKey, itemName, itemContent, module, variables)。该方法执行 runtimeService.startProcessInstanceByKey(module, businessKey, variables),于是 act_ru_* 表填充,biz_flow + biz_todo_item 也得到 xly 侧行。
  5. 审批人在 FROUNT 收件箱中看到待办(很可能是“审批”tab,独立于 KPI 工作中心)。点击通过 / 驳回 → CurrencyFlowController.complete()taskService.complete()
  6. 当流程实例到达 endEvent,行的 bCheck 发生转换;下游过滤 bCheck = 1 的查询开始看到它。

xly 为什么要接 Activiti

代码库已有自己的 biz_flow / biz_todo_item 表,理论上可以手写审批系统。把 Activiti 放在后面的收益是:

  • 标准 BPMN 建模(JS modeler 使用 Activiti Explorer 同源的 stencilset)。
  • 免费的状态机语义:流程引擎处理“task A 完成 → task B 可用”,xly 不需要用 SQL 维护 FSM。
  • 图渲染能力(ProcessActController 中的页面转 PNG)。

成本是:JVM 中多一个流程引擎,DB 中多一套可能发生 DDL 漂移的 schema,以及一个额外认证接口面(xly 通过 act_id_* 视图把它遮住)。

本页不是什么

  • 不是切片 7 的替代品。切片 7(暂缓)应基于一个真实运行流程的部署做端到端追踪。
  • 不是 modeler 教程。modeler 来自 Activiti 项目;xly 只是把它作为静态资源嵌入,没有修改。
  • 不是从 Activiti 迁移到其他方案的计划。那会是更大的架构决策,不是 wiki finding。