1 基本概念

1.1 核心组件

1.1.1 流程引擎(Process Engine)

负责流程的解析、执行和管理。
提供 API 用于与流程进行交互。

1.1.2 BPMN 2.0 文件

用 XML 定义的流程文件,描述流程的步骤、条件和参与者。

1.1.3 数据库

存储流程定义、运行时数据和历史记录(默认支持 H2、MySQL、PostgreSQL 等)。

1.1.4 API

提供与流程引擎交互的接口,例如启动流程、完成任务。

1.1.5 用户界面

官方提供了 Activiti Explorer、Activiti Modeler 等工具(可选)。

1.2 基本对象

1.2.1 流程定义(Process Definition):

使用 BPMN 2.0 标准定义的业务流程模板,通常用 BPMN 2.0 XML 文件描述。包含流程的各个环节、决策点、任务等。
是流程的只读元数据,不需要对其进行直接的操作(也不一定)。所有的Model、XML、BpmnModel 动态创建,都只有被部署为ProcessDefinition才能运行。

距离:

  1. 标准化流程(如请假审批、报销审批):
    这些流程固定不变,可以直接用 BPMN XML 部署:
    repositoryService.createDeployment()
        .addClasspathResource("processes/leave.bpmn20.xml")
        .deploy();
    
  2. 代码定义的流程(BpmnModel 动态创建):
    BpmnModel bpmnModel = new BpmnModel();
    Process process = new Process();
    process.setId("myProcess");
    bpmnModel.addProcess(process);
    repositoryService.createDeployment()
        .addBpmnModel("dynamic-process.bpmn", bpmnModel)
        .deploy();
    
  3. 从Model读取
    @Override  
    public String deployModel(String modelId) throws IOException { 
    	// 获取模型
        Model modelData = repositoryService.getModel(modelId);  
        // 读取模型编辑器源
        ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
    	// 转换模型为BpmnModel(BPMN 2.0 XML 的内存表示),再转换为BPNM XML
    	byte[] bpmnBytes;  
        BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(modelNode);  
        bpmnBytes = new BpmnXMLConverter().convertToXML(bpmnModel);  
    	// 创建部署
        String processName = modelData.getName() + ".bpmn20.xml";  
        Deployment deployment = repositoryService.createDeployment().  
                name(modelData.getName()).addString(processName, new String(bpmnBytes, "UTF-8")).deploy();  
        //更新model的部署ID  
        //校验工单类型是否存在  
        modelData.setDeploymentId(deployment.getId());  
        repositoryService.saveModel(modelData);  
        return deployment.getId();  
    }
    

1.2.2 流程实例(Process Instance):

流程定义的具体执行实例。
每个流程实例代表一次具体的业务流程执行。

1.2.3 任务(Task):

流程中的一个执行步骤,通常需要人工参与。
可以分配给特定的用户或用户组。

1.2.4 任务实例(TaskInstance)

简单来说:

1.2.5 变量(Variable)

流程运行时的数据,例如表单输入或状态。

1.2.6 模型(Model)

用户通过 Flowable/Activiti Modeler 设计的流程,必须用 Model 创建。

// 模型基本信息
repositoryService.saveModel(model);
// 流程信息
repositoryService.addModelEditorSource(modelId, jsonXml.getBytes("UTF-8"));
// 缩略图
repositoryService.addModelEditorSourceExtra(modelId, pngBytes[]);

1.2.7 网关(Gateway)

控制流程的分支,例如条件判断(Exclusive Gateway)或并行执行(Parallel Gateway)。

1.3 核心服务(API)

1.3.1 ProcessEngine

ProcessEngine 是Activiti引擎的核心,通过它来创建、管理和获取其他服务类的实例。

ProcessEngine processEngine = ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault().buildProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();

1.3.2 RuntimeService

管理流程实例和执行流程操作。

runtimeService.startProcessInstanceByKey("processKey");
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();

1.3.3 TaskService

管理任务(如用户任务)的生命周期,提供了创建、查询和处理任务的功能。

List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
taskService.complete(taskId);
taskService.addCandidateUser(taskId, "userId");
taskService.addComment(taskId, processInstanceId, str);

1.3.4 RepositoryService

管理流程定义(ProcessDefinition)以及模型(Model)的部署、查询、删除等操作。

repositoryService.createDeployment().addInputStream("process.bpmn", bpmnStream).deploy();
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
List<String> resourceNames = repositoryService.getDeploymentResourceNames("deploymentId");
for (String resourceName : resourceNames) {
    System.out.println("资源名称:" + resourceName);
}
InputStream inputStream = repositoryService.getResourceAsStream("deploymentId", "my-process.bpmn");
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
    System.out.println(line);
}
reader.close();
Model model = repositoryService.newModel();

// 设置基本信息
model.setKey("myProcess");
model.setName("我的流程模型");
model.setCategory("category1");
model.setVersion(1);

// 保存 Model
repositoryService.saveModel(model);
System.out.println("Model ID: " + model.getId());
Model model = repositoryService.createModelQuery()
        .modelKey("modelKey")
        .singleResult();
//Model 本身不存流程数据,需要将 BPMN XML 以 byte[] 形式存入 ModelEditorSource:
String bpmnXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?> ...";  // BPMN XML 字符串
repositoryService.addModelEditorSource(model.getId(), bpmnXml.getBytes(StandardCharsets.UTF_8));

1.3.5 HistoryService

负责查询历史数据,包括历史任务、历史流程实例、变量等。它通常用于查看已完成流程的状态和细节。

List<HistoricTaskInstance> historicTasks = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).list();
List<HistoricProcessInstance> historicProcessInstances = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).list();
List<HistoricVariableInstance> variables = historyService.createHistoricVariableInstanceQuery()
        .processInstanceId(processInstanceId).list();

1.3.6 IdentityService

用于管理用户、组、角色等身份信息,用来创建用户、查询用户信息等。

identityService.newUser("userId");
User user = identityService.createUserQuery().userId("userId").singleResult();
identityService.setAuthenticatedUserId("userId");

1.3.7 FormService

处理与任务表单的相关操作,通常用于动态生成表单并与用户交互。表单可以用于用户任务中,允许用户输入信息。

FormData formData = formService.getTaskFormData(taskId);
formService.submitTaskFormData(taskId, formFields);

1.3.8 ManagementService

执行与引擎管理相关的任务,允许你对引擎进行控制,例如清除历史数据、获取引擎状态等。

managementService.deleteHistoricProcessInstances("processInstanceId");
ProcessEngineConfiguration configuration = processEngine.getProcessEngineConfiguration();

1.3.9 EventService

管理和监听引擎中的事件,用于监听流程中的事件(例如任务完成、流程实例结束等)。

eventService.addEventListener(new TaskEventListener());

1.3.10 TaskListener 和 ExecutionListener

事件监听器用于在任务和流程执行的特定时刻进行自定义操作,可以在任务创建、任务完成、流程结束等时刻执行自定义操作。

TaskListener taskListener = new TaskListener() {
    public void notify(DelegateTask delegateTask) {
        // 自定义任务操作
    }
};
ExecutionListener executionListener = new ExecutionListener() {
    public void notify(DelegateExecution delegateExecution) {
        // 自定义执行操作
    }
};

2 应用流程

2.1 示例

假设一个请假审批流程:

  1. 流程设计:
    • 开始 → 员工提交请假申请(User Task) → 经理审批(User Task) → 结束。
    • 使用网关判断:如果经理拒绝,流程结束;如果同意,通知 HR。
  2. BPMN XML(简版):
    <process id="leaveProcess" name="请假流程">
      <startEvent id="start"/>
      <userTask id="submitRequest" name="提交申请"/>
      <userTask id="managerApprove" name="经理审批"/>
      <exclusiveGateway id="decision"/>
      <endEvent id="end"/>
    </process>
    
  3. JAVA代码
    // 部署流程
    RepositoryService repositoryService = processEngine.getRepositoryService();
    repositoryService.createDeployment()
        .addClasspathResource("leaveProcess.bpmn20.xml")
        .deploy();
    
    // 启动流程
    RuntimeService runtimeService = processEngine.getRuntimeService();
    runtimeService.startProcessInstanceByKey("leaveProcess");
    
    // 完成任务
    TaskService taskService = processEngine.getTaskService();
    Task task = taskService.createTaskQuery().taskAssignee("employee").singleResult();
    taskService.complete(task.getId());
    

2.2 具体应用

创建工单类型->配置工单类型对应的模板->发起工单

创建单分支工单类型addWorkOrderType

  • 查询是否存在已有工单类型
  • 插入工单类型表
  • 插入工单节点信息表。每个节点自己设为上一个节点的下一个节点,previousNode设为上一个节点。
  • 插入一个空的工单类型模板

请求示例

WorkOrderTypeEntity:
    workOrderTypeKey: test_single
    workOrderTypeName: 测试单分支
    workOrderTypeDesc: null
    templateId: null
    gmtCreate: Wed Mar 19 18:01:47 CST 2025
    gmtModified: Wed Mar 19 18:01:47 CST 2025
    creator: supper2
    modifier: null
    workOrderNodeInfoList:
        WorkOrderNodeInfoEntity:
            id: 323
            workOrderTypeKey: test_single
            nodeKey: null
            nodeName: jieidian1
            nodeDesc: null
            nodeAuditorType: user
            nodeAuditUserGroup: husui, supper2
            previousNodeId: null
            nextNodeId: null
            gmtCreate: Wed Mar 19 18:01:47 CST 2025
            gmtModified: Wed Mar 19 18:01:47 CST 2025
            creator: null
            modifier: null
        WorkOrderNodeInfoEntity:
            id: 324
            workOrderTypeKey: test_single
            nodeKey: null
            nodeName: jiedian2
            nodeDesc: null
            nodeAuditorType: user
            nodeAuditUserGroup: supper2
            previousNodeId: 323
            nextNodeId: null
            gmtCreate: Wed Mar 19 18:01:47 CST 2025
            gmtModified: Wed Mar 19 18:01:47 CST 2025
            creator: null
            modifier: null
    workOrderTypeTemplate: null

创建多分支工单类型

新增模型

  • newModel.htm->saveModelBase()初始化一个空的模型实体ActivitiModelEntity

    ModelVO:
      serialVersionUID: 1316333722355013667
      modelId: null
      modelName: "多分支测试"
      modelKey: "多分支测试tt"
      modelDescription: "多分支测试"
      creator: "supper2"
      modifier: "supper2"
      createTime: null
      lastUpdateTime: null
      category: null
      metaInfo: null
      deploymentId: null
      editorSourceValueId: null
      editorSourceExtraValueId: null
    
    ActivitiModelEntity:
      serialVersionUID: 3612213522563382365
      id: null
      revision: 1
      name: "多分支测试"
      key: "多分支测试tt"
      category: null
      createTime: null
      lastUpdateTime: null
      version: 1
      metaInfo: null
      deploymentId: null
      editorSourceValueId: null
      editorSourceExtraValueId: null
      creator: "supper2"
      modifier: "supper2"
      description: "多分支测试"
    
  • 返回列表点击编辑流程图(modeler.html?modelId=)跳转到activiti官方的流程图编辑页面。

  • 编辑完成后保存,url为http://c1wrkordmgr.yiwupay.com:8080/wrkordmgr/static/activitiModelService/model/[modelId]/save,调用方法ModelFacade.saveModel(ModelSaveRequest request)

    $scope.save = function (successCallback) {  
        // 校验模型名称
        if (!$scope.saveDialog.name || $scope.saveDialog.name.length == 0) {  
            return;  
        }  
      
        // Indicator spinner image  
        $scope.status = {  
            loading: true  
        };  
          // 更新模型元数据
        modelMetaData.name = $scope.saveDialog.name;  
        modelMetaData.description = $scope.saveDialog.description;
          
        // 从编辑器($scope.editor)中获取流程图的 JSON 数据,并将其转换为字符串格式。
        var json = $scope.editor.getJSON();  
        json = JSON.stringify(json);  
        
        // 保存当前选中的节点或连线,然后清空选择状态,以便生成干净的 SVG 图像。
        var selection = $scope.editor.getSelection();  
        $scope.editor.setSelection([]);  
          
        // Get the serialized svg image source  
        var svgClone = $scope.editor.getCanvas().getSVGRepresentation(true);  
        $scope.editor.setSelection(selection); 
        
        // 移除不需要的 SVG 元素 
        if ($scope.editor.getCanvas().properties["oryx-showstripableelements"] === false) {  
            var stripOutArray = jQuery(svgClone).find(".stripable-element");  
            for (var i = stripOutArray.length - 1; i >= 0; i--) {  
                stripOutArray[i].remove();  
            }  
        }  
      
        // Remove all forced stripable elements  
        var stripOutArray = jQuery(svgClone).find(".stripable-element-force");  
        for (var i = stripOutArray.length - 1; i >= 0; i--) {  
            stripOutArray[i].remove();  
        }  
      
        // Parse dom to string  序列化
        var svgDOM = DataManager.serialize(svgClone);  
        
        // 构造请求参数
        var params = {  
            json_xml: json,  // 流程图json数据
            svg_xml: svgDOM,  // 流程图SVG图像
            name: $scope.saveDialog.name,  // 模型名称
            description: $scope.saveDialog.description  // 模型描述
        };  
      
        // Update 发送 HTTP PUT 请求 
        $http({    method: 'PUT',  
            data: params,  
            ignoreErrors: true,  
            headers: {'Accept': 'application/json',  
                      'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},  
            transformRequest: function (obj) {  
                var str = [];  
                for (var p in obj) {  
                    str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));  
                }  
                return str.join("&");  
            },  
            url: KISBPM.URL.putModel(modelMetaData.modelId)})  
    		// 触发保存事件
            .success(function (data, status, headers, config) {  
                $scope.editor.handleEvents({  
                    type: ORYX.CONFIG.EVENT_SAVED  
                });  
                $scope.modelData.name = $scope.saveDialog.name;  
                $scope.modelData.lastUpdated = data.lastUpdated;  
                  
                $scope.status.loading = false;  
                $scope.$hide();  
      
                // Fire event to all who is listening  
                var saveEvent = {  
                    type: KISBPM.eventBus.EVENT_TYPE_MODEL_SAVED,  
                    model: params,  
                    modelId: modelMetaData.modelId,  
              eventType: 'update-model'  
                };  
                KISBPM.eventBus.dispatch(KISBPM.eventBus.EVENT_TYPE_MODEL_SAVED, saveEvent);  
      
                // Reset state  
                $scope.error = undefined;  
                $scope.status.loading = false;  
      
                // Execute any callback  
                if (successCallback) {  
                    successCallback();  
                }  
      
            })  
            .error(function (data, status, headers, config) {  
                $scope.error = {};  
                console.log('Something went wrong when updating the process model:' + JSON.stringify(data));  
                $scope.status.loading = false;  
            });  
    };
    
    KISBPM.URL = {  
      
        getModel: function(modelId) {  
            return ACTIVITI.CONFIG.contextRoot + '/model/' + modelId + '/json';  
        },  
      
        getStencilSet: function() {  
            return ACTIVITI.CONFIG.contextRoot + '/editor/stencilset?version=' + Date.now();  
        },  
      
        putModel: function(modelId) {  
            return ACTIVITI.CONFIG.contextRoot + '/model/' + modelId + '/save';  
        }  
    };
    
    ACTIVITI.CONFIG = {  
        'contextRoot' :  'activitiModelService',  
    };
    
  • 保存方法调用repositoryService来保存模型和流程图JSON数据(addModelEditorSource)以及流程图的 SVG 图像并转换为PNG用于展示(addModelEditorSourceExtra

部署模型

  • 获取模型相关信息,转化为ProcessDefinition部署。
    @Override  
    public String deployModel(String modelId) throws IOException { 
    	// 获取模型
        Model modelData = repositoryService.getModel(modelId);  
        // 读取模型编辑器源
        ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
    	// 转换模型为BpmnModel(BPMN 2.0 XML 的内存表示),再转换为BPNM XML
    	byte[] bpmnBytes;  
        BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);  
        bpmnBytes = new BpmnXMLConverter().convertToXML(model);  
    	// 创建部署
        String processName = modelData.getName() + ".bpmn20.xml";  
        Deployment deployment = repositoryService.createDeployment().  
                name(modelData.getName()).addString(processName, new String(bpmnBytes, "UTF-8")).deploy();  
        //更新model的部署ID  
        //校验工单类型是否存在  
        modelData.setDeploymentId(deployment.getId());  
        repositoryService.saveModel(modelData);  
        return deployment.getId();  
    }
    

开启工单startCommonWorkOrder

  1. runtimeService查找是否存在原流程实例(工单)。如果存在,则查出该流程未完成的任务节点,将处理人替换为当前请求处理人,将流程状态设为重新发起后删除流程。
  2. 校验工单类型是否存在:根据请求的工单类型查询workOrderTypeDO (单分支工单)和Model(多分支工单),确保只存在一种。
  3. 校验工单类型模板:检查是否有模板。如果是单分支类型则填充节点信息。
  4. 启动流程实例:记录操作用户,将操作请求、工单分支、所有节点信息、流程状态(处理中)填充进信息,判断是否需要设置分支条件。如果为单分支,则使用通用keycommon-audit-process,否则使用请求中的工单类型key。开启流程。
  5. 获取流程key,存进mongodb。

请求示例:

StartCommonWorkOrderRequest:
  serialVersionUID: 4971683305498978801
  version: null
  processInstanceId: null
  startUser: supper2
  workOrderType: MMPF_EMAIL_TO_WORKORDERCENTER
  bizData:
    attachment: ""
    content: "123"
    emailGroupId: "24"
    gmtCreate: 1742375032649
    gmtModified: 1742375032649
    operator: "supper2"
    status: "A"
    tag: "system_up"
    title: "123"
  businessKey: "测试邮件"
  auditLabel: null
  flowSelfCondition: null
  requestOperator: null

提交工单commitCommonWorkOrder

  1. 从request中取出committer并设置处理人
  2. 设置paramMap(保存流程参数)和localParamMap(保存意见等本地参数)并填充。
  3. 填充流程自定义条件。
  4. 审核附件上传
  5. 评论保存localParamMap
  6. 完成任务,工作流引擎根据流程定义文件推进到下一个节点。

处理工单handleCommonWorkOrder

  1. 找出对应流程实例和任务
  2. 从request中取出auditor并设置处理人
  3. 设置paramMap(保存流程参数)和localParamMap(保存意见等本地参数)并填充。
  4. 审核附件上传
  5. 评论保存localParamMap
  6. 完成任务,工作流引擎根据流程定义文件推进到下一个节点。

model流程和具体业务如何结合?

WorkOrderTypeService存了WorkOrderTypeTemplateEntity。
新建工单,页面上显示流程图,多分支页面(modelDetailPage.html)中镶嵌展示png图片

<img th:src="${session.contextPath}+'/workOrder/myStart/workOrderTypeImage.htm?deploymentId='+${modelVO.deploymentId}"  
 style="margin-left: 15%">

工单类型->工单类型模板->

发起工单时,都是使用runtimeService来发起,单/多分支靠是WorkOrderTypeDO还是Model来区分,如果单分支则将节点信息存进初始变量。

Listener在流程图中配置。

其他