在当今企业数字化转型浪潮中,业务流程管理(BPM)系统已成为提升组织效率的关键基础设施。然而,真正能在企业环境中稳定运行的BPM解决方案却屈指可数。大多数所谓的"工作流引擎"仅停留在演示阶段,无法应对真实业务场景的复杂性。
企业级BPM系统面临的主要挑战可以归纳为以下五个维度:
流程多样性:一个中型企业通常有50-100种不同类型的业务流程需要管理,从简单的请假审批到复杂的采购合同会签,每种流程都有独特的业务规则和审批路径。
组织架构复杂性:现代企业的组织架构往往呈现矩阵式特点,审批人可能涉及:
表单动态性:业务表单需要支持:
状态一致性:流程状态与业务状态需要保持实时同步,包括:
系统集成性:在微服务架构下,BPM系统需要与多个业务系统集成,同时保持松耦合。
市场上常见的BPM解决方案通常存在以下问题:
过度依赖BPMN标准:要求业务人员理解复杂的BPMN规范,学习成本高。
审批策略单一:仅支持固定审批人或简单角色分配,无法适应企业复杂的组织架构。
表单能力薄弱:要么完全依赖代码开发,要么拖拽功能过于简单。
业务耦合度高:流程引擎直接调用业务系统接口,导致系统难以维护和扩展。
RuoYi Office的BPM架构基于以下技术栈构建:
| 技术领域 | 选型方案 | 版本 | 选择理由 |
|---|---|---|---|
| 流程引擎 | Flowable | 7.0.1 | 轻量级、高性能、完整的BPMN 2.0支持,社区活跃度高 |
| 后端框架 | Spring Boot + Cloud | 3.5 | 企业级Java生态标准,提供完善的微服务支持 |
| 前端框架 | Vue 3 + TypeScript | 3.5 | 组合式API更适合复杂前端交互,类型安全提升代码质量 |
| UI组件库 | Ant Design Vue | 4.x | 企业级UI设计规范,丰富的组件库支持复杂业务场景 |
| 流程设计器 | bpmn-js + 自研Simple设计器 | 最新版 | 同时满足专业流程设计人员和业务人员的需求 |
RuoYi Office采用严格分层的五层架构设计,各层职责明确:
code复制+-----------------------+
| 业务模块层 | (OA/HRM/CRM等)
+-----------------------+
↓
+-----------------------+
| BPM API层 | (定义跨模块通信契约)
+-----------------------+
↓
+-----------------------+
| BPM服务层 | (流程引擎核心实现)
+-----------------------+
↓
+-----------------------+
| Flowable引擎层 | (原生能力扩展定制)
+-----------------------+
↓
+-----------------------+
| 数据持久层 | (流程数据存储)
+-----------------------+
契约式接口设计:
bpm-api模块定义的接口bpm-server实现模块事件驱动架构:
java复制// 事件发布示例
applicationEventPublisher.publishEvent(
new BpmProcessInstanceStatusEvent(this, eventType, processInstanceInfo));
// 事件监听示例
@EventListener
public void handleProcessEvent(BpmProcessInstanceStatusEvent event) {
// 业务处理逻辑
}
策略模式扩展:
RuoYi Office对原生Flowable进行了深度定制,主要扩展点包括:
自定义ActivityBehaviorFactory:
java复制public class BpmActivityBehaviorFactory extends DefaultActivityBehaviorFactory {
@Override
public UserTaskActivityBehavior createUserTaskActivityBehavior(
UserTask userTask, TaskDefinition taskDefinition) {
// 注入自定义任务行为处理器
return new BpmUserTaskActivityBehavior(taskDefinition);
}
}
全局事件监听体系:
java复制configuration.setEventListeners(Arrays.asList(
new BpmProcessStartEventListener(),
new BpmTaskCreateEventListener(),
new BpmProcessCompleteEventListener()
));
自定义流程函数:
java复制public class DeptLeaderFunctionDelegate implements FlowableFunctionDelegate {
@Override
public Set<Long> execute(DelegateExecution execution) {
// 实现部门领导查找逻辑
}
}
| 维度 | BPMN设计器 | Simple设计器 |
|---|---|---|
| 目标用户 | 专业流程管理员/IT人员 | 业务部门管理员 |
| 技术基础 | bpmn-js | 自研JSON Schema+转换引擎 |
| 学习曲线 | 需要BPMN知识 | 零基础即可使用 |
| 表达能力 | 支持全部BPMN元素 | 支持80%常用审批场景 |
| 扩展性 | 通过插件机制扩展 | 通过节点类型扩展 |
| 典型场景 | 采购审批、合同会签等复杂流程 | 请假、用车等简单审批流 |
Simple设计器的核心是将可视化配置转换为标准BPMN模型:
code复制前端JSON配置 → 后端转换引擎 → 标准BPMN模型 → Flowable引擎执行
| Simple节点类型 | BPMN元素类型 | 扩展属性 |
|---|---|---|
| start | startEvent | initiator: "assignee" |
| approver | userTask | candidateStrategy: "DEPT_LEADER" |
| cc | serviceTask | type: "mailNotification" |
| condition | exclusiveGateway | condition: "${amount > 5000}" |
| parallel | parallelGateway | - |
| delay | intermediateTimerEvent | timeDuration: "PT24H" |
| end | endEvent | - |
java复制public class SimpleModelConverter {
public BpmnModel convert(SimpleModel simpleModel) {
BpmnModel bpmnModel = new BpmnModel();
Process process = new Process();
// 转换开始节点
StartEvent startEvent = new StartEvent();
startEvent.setInitiator("assignee");
process.addFlowElement(startEvent);
// 转换审批节点
for (SimpleNode node : simpleModel.getNodes()) {
if ("approver".equals(node.getType())) {
UserTask userTask = new UserTask();
userTask.setName(node.getName());
// 设置审批人策略
ExtensionElement strategyExt = new ExtensionElement();
strategyExt.setName("candidateStrategy");
strategyExt.setElementText(node.getStrategy());
userTask.addExtensionElement(strategyExt);
process.addFlowElement(userTask);
}
// 其他节点类型转换...
}
// 转换连线
for (SimpleEdge edge : simpleModel.getEdges()) {
SequenceFlow flow = new SequenceFlow(
edge.getSource(), edge.getTarget());
if (edge.getCondition() != null) {
flow.setConditionExpression(
new FormalExpression(edge.getCondition()));
}
process.addFlowElement(flow);
}
bpmnModel.addProcess(process);
return bpmnModel;
}
}
两种设计器采用统一的存储模型,便于管理:
java复制@Entity
@Table(name = "bpm_model")
public class BpmModel {
@Id
private String id;
@Enumerated(EnumType.STRING)
private BpmModelType type; // BPMN/SIMPLE
@Enumerated(EnumType.STRING)
private BpmModelFormType formType; // NORMAL/CUSTOM
@Column(columnDefinition = "text")
private String bpmnXml; // BPMN设计器内容
@Column(columnDefinition = "json")
private String simpleConfig; // Simple设计器JSON配置
@Column(columnDefinition = "json")
private String formConf; // 表单配置(流程表单模式)
private String formCustomViewPath; // 自定义表单路径
}
| 技术维度 | 流程表单(NORMAL) | 业务表单(CUSTOM) |
|---|---|---|
| 存储方式 | 集中存储在bpm_form表 |
分散在各业务模块表中 |
| 渲染引擎 | form-create动态渲染 | 自定义Vue组件 |
| 数据绑定 | 自动绑定流程变量 | 手动实现数据同步 |
| 校验机制 | 基于JSON Schema的校验 | 自定义校验逻辑 |
| 权限控制 | 字段级读写控制 | 组件内部实现 |
| 典型场景 | 请假单、通用审批单 | 合同审批、采购申请 |
流程表单设计器基于form-create和antd-designer构建,主要特点包括:
组件分类体系:
javascript复制const componentCategories = [
{
name: '基础组件',
components: [
{ type: 'input', label: '单行文本' },
{ type: 'textarea', label: '多行文本' },
{ type: 'number', label: '数字输入' }
]
},
{
name: '选择组件',
components: [
{ type: 'select', label: '下拉选择' },
{ type: 'checkbox', label: '多选框' },
{ type: 'radio', label: '单选框' }
]
}
];
表单配置模型:
json复制{
"type": "form",
"api": "/api/submit",
"controls": [
{
"type": "input",
"name": "title",
"label": "标题",
"required": true,
"rules": [
{ "max": 50, "message": "不超过50个字符" }
]
}
]
}
动态权限控制:
javascript复制function setFieldPermission(field, permission) {
const rule = { mode: permission };
formCreate.rule(field).change(rule);
// 审批节点A可编辑,节点B只读
if (permission === 'READONLY') {
formCreate.disabled(field, true);
}
}
业务表单组件需要实现的标准接口:
typescript复制interface BusinessFormComponent {
// 加载表单数据
loadData(id: string): Promise<void>;
// 获取表单数据
getFormData(): Record<string, any>;
// 校验表单
validate(): Promise<boolean>;
// 审批前回调
beforeApproval(nodeKey: string): Promise<boolean>;
// 暴露给框架的方法
exposeMethods(): {
loadData: () => Promise<void>;
validate: () => Promise<boolean>;
};
}
vue复制<template>
<a-form :model="formData">
<a-form-item label="合同编号">
<a-input v-model:value="formData.contractNo" />
</a-form-item>
<!-- 其他表单字段 -->
</a-form>
</template>
<script lang="ts" setup>
const props = defineProps<BusinessFormProps>();
const formData = ref({});
const loadData = async (id: string) => {
formData.value = await contractApi.getDetail(id);
};
const validate = async () => {
// 自定义校验逻辑
};
defineExpose({ loadData, validate });
</script>
java复制public enum BpmEventTypeEnum {
// 流程实例事件
PROCESS_INSTANCE_CREATED("流程创建"),
PROCESS_INSTANCE_STARTED("流程启动"),
PROCESS_INSTANCE_COMPLETED("流程完成"),
PROCESS_INSTANCE_TERMINATED("流程终止"),
// 任务事件
TASK_CREATED("任务创建"),
TASK_COMPLETED("任务完成"),
TASK_ASSIGNED("任务分配"),
TASK_REJECTED("任务驳回");
// 其他方法...
}
java复制public interface FlowBillService<T extends Enum<?>> {
/**
* 获取支持的单据类型
*/
T getSupportedBillType();
/**
* 更新流程状态
*/
void updateProcessStatus(String businessKey, Integer status);
/**
* 流程通过后业务处理
*/
default void onProcessApproved(String businessKey) {}
/**
* 流程拒绝后业务处理
*/
default void onProcessRejected(String businessKey) {}
/**
* 获取业务详情URL
*/
default String getDetailUrl(String businessKey) {
return null;
}
}
java复制public abstract class AbstractFlowListener<T extends Enum<?>>
implements ApplicationListener<BpmProcessInstanceStatusEvent> {
protected abstract SystemEnum getSystem();
protected abstract FlowBillServiceFactory<T> getServiceFactory();
@Override
public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
// 1. 系统过滤
if (!matchSystem(event)) {
return;
}
// 2. 获取业务服务
FlowBillService<T> service = getService(event);
// 3. 事件处理
switch (event.getEventType()) {
case PROCESS_INSTANCE_COMPLETED:
handleProcessCompleted(event, service);
break;
case TASK_COMPLETED:
handleTaskCompleted(event, service);
break;
// 其他事件类型...
}
}
private void handleProcessCompleted(BpmProcessInstanceStatusEvent event,
FlowBillService<T> service) {
if (event.isApproved()) {
service.onProcessApproved(event.getBusinessKey());
} else if (event.isRejected()) {
service.onProcessRejected(event.getBusinessKey());
}
service.updateProcessStatus(event.getBusinessKey(), event.getStatus());
}
}
java复制public interface BpmTaskCandidateStrategy {
/**
* 策略类型
*/
BpmTaskCandidateStrategyEnum getStrategyType();
/**
* 计算候选人
*/
Set<Long> calculateCandidates(DelegateExecution execution);
/**
* 是否支持退回
*/
default boolean supportReturn() {
return true;
}
}
java复制@Component
public class DeptLeaderStrategy implements BpmTaskCandidateStrategy {
@Resource
private AdminUserApi adminUserApi;
@Override
public BpmTaskCandidateStrategyEnum getStrategyType() {
return BpmTaskCandidateStrategyEnum.DEPT_LEADER;
}
@Override
public Set<Long> calculateCandidates(DelegateExecution execution) {
Long starterId = (Long) execution.getVariable("starter");
return adminUserApi.getDeptLeaders(starterId);
}
}
java复制@Component
public class FormFieldStrategy implements BpmTaskCandidateStrategy {
@Override
public BpmTaskCandidateStrategyEnum getStrategyType() {
return BpmTaskCandidateStrategyEnum.FORM_FIELD;
}
@Override
public Set<Long> calculateCandidates(DelegateExecution execution) {
String fieldName = (String) execution.getVariable("approverField");
Object fieldValue = execution.getVariable(fieldName);
return convertToUserIds(fieldValue);
}
}
java复制public class BpmTaskCandidateInvoker {
private final Map<BpmTaskCandidateStrategyEnum, BpmTaskCandidateStrategy> strategyMap;
public Set<Long> invoke(DelegateExecution execution) {
// 从流程变量获取策略类型
Integer strategyType = (Integer) execution.getVariable("candidateStrategy");
BpmTaskCandidateStrategyEnum strategyEnum =
BpmTaskCandidateStrategyEnum.valueOf(strategyType);
// 调用对应策略
return strategyMap.get(strategyEnum)
.calculateCandidates(execution);
}
}
java复制public enum BpmTaskOperationType {
APPROVE("通过"),
REJECT("驳回"),
RETURN("退回"),
DELEGATE("委派"),
TRANSFER("转办"),
ADD_SIGN("加签"),
REDUCE_SIGN("减签"),
WITHDRAW("撤回");
// 其他方法...
}
java复制@Service
public class BpmTaskOperationService {
public void approve(Long taskId, String comment) {
Task task = validateTask(taskId);
checkPermission(task);
// 1. 记录审批意见
saveComment(taskId, comment, APPROVE);
// 2. 完成任务
taskService.complete(taskId);
// 3. 发布事件
publishTaskEvent(task, APPROVED);
}
public void reject(Long taskId, String comment, String targetNodeId) {
Task task = validateTask(taskId);
checkPermission(task);
// 1. 记录审批意见
saveComment(taskId, comment, REJECT);
// 2. 跳转到目标节点
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(task.getProcessInstanceId())
.moveActivityIdTo(task.getTaskDefinitionKey(), targetNodeId)
.changeState();
// 3. 发布事件
publishTaskEvent(task, REJECTED);
}
}
sql复制CREATE TABLE `bpm_task_comment` (
`id` bigint NOT NULL COMMENT '主键',
`task_id` varchar(64) NOT NULL COMMENT '任务ID',
`process_instance_id` varchar(64) NOT NULL COMMENT '流程实例ID',
`operation_type` varchar(20) NOT NULL COMMENT '操作类型',
`comment` text COMMENT '审批意见',
`form_data` json DEFAULT NULL COMMENT '表单数据快照',
`operator` bigint NOT NULL COMMENT '操作人',
`operation_time` datetime NOT NULL COMMENT '操作时间',
PRIMARY KEY (`id`),
KEY `idx_process_instance_id` (`process_instance_id`)
) COMMENT='任务审批意见表';
vue复制<template>
<a-tabs v-model:activeKey="activeTab">
<a-tab-pane key="todo" tab="待办任务">
<TaskList :data="todoTasks" @refresh="loadData" />
</a-tab-pane>
<a-tab-pane key="done" tab="已办任务">
<TaskList :data="doneTasks" type="done" />
</a-tab-pane>
</a-tabs>
</template>
<script lang="ts" setup>
const activeTab = ref('todo');
const todoTasks = ref([]);
const doneTasks = ref([]);
const loadData = async () => {
todoTasks.value = await taskApi.getTodoList();
doneTasks.value = await taskApi.getDoneList();
};
</script>
vue复制<template>
<a-page-header :title="processDefinition.name">
<template #extra>
<ProcessActions
:process-instance="processInstance"
@refresh="loadDetail" />
</template>
<a-tabs>
<a-tab-pane key="form" tab="表单信息">
<DynamicForm
v-if="formType === 'NORMAL'"
:config="formConf"
:fields="formFields" />
<component
v-else
:is="businessFormComponent"
:id="businessKey" />
</a-tab-pane>
<a-tab-pane key="process" tab="审批流程">
<ProcessDiagram
:process-instance-id="processInstanceId" />
</a-tab-pane>
</a-tabs>
</a-page-header>
</template>
code复制 +-----------------+
| Load Balancer |
+--------+--------+
|
+---------------+---------------+
| |
+-------+-------+ +-------+-------+
| BPM Service | | BPM Service |
| (Node 1) | | (Node 2) |
+-------+-------+ +-------+-------+
| |
+-------+-------+ +-------+-------+
| Flowable DB | | Flowable DB |
| (Master) | | (Slave) |
+-------+-------+ +-------+-------+
| |
+---------------+---------------+
|
+--------+--------+
| Shared File |
| Storage |
+-----------------+
Flowable引擎配置:
yaml复制flowable:
async-executor-activate: true
async-executor-thread-pool-size: 20
async-history-enabled: true
database-schema-update: false
history-level: audit
Spring Boot配置:
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 30
connection-timeout: 30000
缓存配置:
java复制@Configuration
public class FlowableCacheConfig {
@Bean
public ProcessDefinitionCache processDefinitionCache() {
return new ProcessDefinitionCache(500); // 缓存500个流程定义
}
}
xml复制<bpmn2:process id="seal_apply" name="用印申请流程">
<bpmn2:startEvent id="start" name="开始">
<bpmn2:extensionElements>
<flowable:initiator>initiator</flowable:initiator>
</bpmn2:extensionElements>
</bpmn2:startEvent>
<bpmn2:userTask id="dept_approve" name="部门负责人审批">
<bpmn2:extensionElements>
<flowable:candidateStrategy>DEPT_LEADER</flowable:candidateStrategy>
</bpmn2:extensionElements>
</bpmn2:userTask>
<bpmn2:exclusiveGateway id="gateway" name="用印方式判断" />
<bpmn2:userTask id="seal_keeper_approve" name="印章保管员审批">
<bpmn2:extensionElements>
<flowable:candidateStrategy>ROLE</flowable:candidateStrategy>
<flowable:role>SEAL_KEEPER</flowable:role>
</bpmn2:extensionElements>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="flow1" sourceRef="start" targetRef="dept_approve" />
<bpmn2:sequenceFlow id="flow2" sourceRef="dept_approve" targetRef="gateway" />
<bpmn2:sequenceFlow id="flow3" sourceRef="gateway" targetRef="seal_keeper_approve">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">
${sealUseMode == 1}
</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
</bpmn2:process>
vue复制<template>
<a-form :model="formData">
<a-form-item label="用印方式">
<a-radio-group v-model:value="formData.sealUseMode">
<a-radio :value="1">现场用印</a-radio>
<a-radio :value="2">外借用印</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
v-if="formData.sealUseMode === 2"
label="预计归还时间">
<a-date-picker
v-model:value="formData.returnTime"
show-time />
</a-form-item>
</a-form>
</template>
<script lang="ts" setup>
const props = defineProps<BusinessFormProps>();
const formData = ref({
sealUseMode: 1,
returnTime: null
});
const beforeApproval = async (nodeKey: string) => {
if (nodeKey === 'seal_keeper_approve' && !formData.value.sealUseMode) {
message.error('请选择用印方式');
return false;
}
return true;
};
defineExpose({ beforeApproval });
</script>
实现BpmTaskCandidateStrategy接口:
java复制@Component
public class ProjectManagerStrategy implements BpmTaskCandidateStrategy {
@Override
public BpmTaskCandidateStrategyEnum getStrategyType() {
return BpmTaskCandidateStrategyEnum.PROJECT_MANAGER;
}
@Override
public Set<Long> calculateCandidates(DelegateExecution execution) {
String projectId = (String) execution.getVariable("projectId");
return projectService.getManagers(projectId);
}
}
在流程设计中使用:
xml复制<userTask id="pm_approve" name="项目经理审批">
<extensionElements>
<flowable:candidateStrategy>PROJECT_MANAGER</flowable:candidateStrategy>
</extensionElements>
</userTask>
扩展ActivityBehavior:
java复制public class CustomUserTaskBehavior extends UserTaskActivityBehavior {
@Override
public void execute(DelegateExecution execution) {
// 前置处理
log.info("Task {} is about to start", execution.getCurrentActivityId());
// 标准处理
super.execute(execution);
// 后置处理
eventPublisher.publishTaskEvent(execution);
}
}
注册自定义行为工厂:
java复制@Bean
public ActivityBehaviorFactory customActivityBehaviorFactory() {
return new CustomActivityBehaviorFactory();
}
问题现象:点击"提交"按钮后流程没有启动,也没有错误提示。
排查步骤:
ACT_RE_PROCDEF表确认流程定义状态问题现象:任务创建了但没有人可以处理。
排查步骤:
ACT_RU_TASK表确认任务是否创建成功ACT_RU_IDENTITYLINK表确认任务候选人BpmTaskCandidateStrategy实现问题现象:流程已审批通过,但业务单据状态未更新。
排查步骤:
FlowBillService实现类是否正确注册process_status字段是否更新| 指标名称 | 监控方式 | 健康阈值 |
|---|---|---|
| 流程实例启动耗时 | Prometheus + Grafana | < 500ms |
| 任务分配耗时 | Flowable日志 + ELK | < 300ms |
| 事件处理延迟 | Spring Actuator Metrics | < 200ms |
| 数据库连接池使用率 | HikariCP JMX | < 80% |
| 异步作业积压量 | Flowable管理控制台 | < 100 |
数据库优化:
ACT_RU_TASK、ACT_RU_EXECUTION等高频查询表添加合适索引ACT_HI_*表缓存策略:
java复制@Cacheable(value = "processDefinition",
key = "#processDefinitionId")
public ProcessDefinition getProcessDefinition(String processDefinitionId) {
return repositoryService.getProcessDefinition(processDefinitionId);
}
异步处理:
java复制@Async
@EventListener
public void handleAsyncEvent(BpmProcessInstanceStatusEvent event) {
// 耗时处理逻辑
}
流程定义权限:
sql复制INSERT INTO bpm_process_perm (process_definition_id, role_id, perm_type)
VALUES ('seal_apply:1:123', 'OA_ADMIN', 'EDIT');
数据行级过滤:
java复制@PostFilter("hasPermission(filterObject, 'READ')")
public List<ProcessInstance> getMyProcessInstances() {
return runtimeService.createProcessInstanceQuery()
.startedBy(currentUserId())
.list();
}
java复制@Aspect
@Component
public class BpmAuditLogAspect {
@AfterReturning(
pointcut = "execution(* com.ruoyioffice.bpm..*.*(..))",
returning = "result")
public void logMethodCall(JoinPoint jp, Object result) {
auditLogService.save(
new AuditLog()
.setOperation(jp.getSignature().getName())
.setParams(JsonUtils.toJson(jp.getArgs()))
.setResult(JsonUtils.toJson(result)));
}
}
AI辅助审批:
移动端深度优化:
流程挖掘与分析:
这套基于Flowable的企业级BPM架构已经在多个真实客户环境中得到验证,能够支撑日均10万+流程实例的运行。其核心价值在于将复杂的BPM概念转化为企业用户能够直观理解的操作界面,同时保持底层引擎的灵活性和扩展性。