Application Development Platform Integrated Workflow Series 10 - Design and Implementation of Business Logic Processing in Process Modeling Functional Links

background

Workflow-based form flow requires the execution of some business logic processing in certain specific links. For example, dynamically assigning node handlers, sending emails or text messages to to-do users, counting process processing time to determine whether it has timed out, and business-level data processing (for example, in the leave process, writing the approval opinions and time data of the department leader's approval process to in the application form).

These business logic can be divided into two major categories. One is general processing logic, which is applicable to all processes, such as sending emails or text messages to to-do users; the other is business logic bound to a certain business process, usually Data processing at the business level.

Technical reference

For the business logic processing of the link, Camunda products provide a listener mechanism to support the implementation of this part of the function.
There are two types of listeners, execution listeners and task listeners, which are used to handle life cycle events of process instances and task instances respectively.

Execution listener

Execution listeners allow you to execute external Java code or evaluate an expression when certain events occur during process execution. Events that can be captured are:

  • Start or end a process instance.
  • Make a transition.
  • Start or end an activity.
  • Start or stop a gateway.
  • Start or end an intermediate event.
  • End a start event or start an end event.

Note: The transition in the second article has an obscure meaning, but it is actually the translation of the word Transition. From the context, it specifically refers to the edge Sequence Flow, that is, you can specify an execution listener on the edge.

task listener

Task listeners are used to execute custom Java logic or expressions when a task-related event occurs, and can only be added to the process definition as a sub-element of the user task.
Note: Task listeners can only be loaded on nodes of user task type, while execution listeners can be placed on processes, edges, gateways, etc.

Events that can trigger task monitoring, the information found on the Internet are often the following:

  • create: Triggered when the task is created.
  • assignment: Triggered when a task is assigned to a user or group.
  • complete: Triggered when the task is completed.
  • delete: Triggered when the task is deleted.

At first glance, there seems to be no problem. However, when I think about it, it is too vague. For example, under what circumstances will the task delete be triggered? And for example, who triggers the create and assignment events first? Many search results show that the create event is in All attributes of the task, including the handler, are triggered after initialization, and assignment assigns a handler to the task, so assignment will be triggered before create.

After actually reading the official source materials, I found that there are far more events than these, and there are many conditions to determine who comes first and who comes last.
Portal: https://docs.camunda.org/manual/latest/user-guide/process-engine/delegation-code/

In addition to the above four events, there are also update events and timeout events.
It can be clarified from official information that the create event will definitely be triggered before the assignment.
Assignment may be used when handling person changes, while update and delete are only used in very special business scenarios.

Logic processing implementation

The two types of listeners and corresponding events are mentioned above. Our ultimate goal is to trigger the corresponding logical processing by listening to the events. Camuda has several ways to set up logical processing:
1. class : Specify the full path class including the package name. This class needs to implement the preset interface.

  <userTask id="myTask" name="My Task" >
    <extensionElements>
      <camunda:taskListener event="create" class="org.camunda.bpm.MyTaskCreateListener" />
    </extensionElements>
  </userTask>

The called delegate class, for the task listener, needs to implement the org.camunda.bpm.engine.impl.pvm.delegate.TaskListener interface, and the logic is written in the notify method.

public class MyTaskCreateListener implements TaskListener {
    
    

  public void notify(DelegateTask delegateTask) {
    
    
    // Custom logic goes here
  }

}

2.expression (cannot be used with the class attribute): Specifies the expression that will be executed when the event occurs. A DelegateTask object and the name of the event (using task.eventName) can be passed as arguments to the called object.

<camunda:taskListener event="create" 
expression="${myObject.callMethod(task, task.eventName)}" />

3. delegateExpression : allows you to specify an expression that can be parsed into an object that implements the TaskListener interface, similar to a service task.

<camunda:taskListener event="create" delegateExpression="${myTaskListenerBean}" />

4. Script: The camunda:script sub-element can be used to specify a script as a task listener.

  <userTask id="task">
    <extensionElements>
      <camunda:taskListener event="create">
        <camunda:script scriptFormat="groovy">
          println task.eventName
        </camunda:script>
      </camunda:taskListener>
    </extensionElements>
  </userTask>

Design

Listener : We focus on the processing of tasks, so we choose a task listener, and then expand the implementation after executing the listener with specific business scenarios.
Event : Based on actual business scenarios, there are actually two most commonly used events. One is create, which is the pre-logic of task processing; the second is complete, which is the post-logic of task processing.
**Logic:** Although the official provides four methods, the delegation class method is more in line with our demand scenarios.

Listener management:
In the listener configuration interface, although a text box can be provided to allow process modelers to directly enter the full path of the delegate class, this method is not very friendly and error-prone. A better way is to add it by selection. .

Using the selection method, you need to implement the management of the listener, that is, register the delegation class as metadata.

System implementation

front end

Expand the configuration attribute config of the management link, define the sub-attribute listenerConfig, and store the listener configuration information.

{
    
    
	"name": "填报",
	"id": "root",
	"type": "ROOT",
	"config": {
    
    
		"permissionConfig": [{
    
    
			"areaCode": "applyArea",
			"permission": "EDITABLE"
		}, {
    
    
			"areaCode": "organizationApproval",
			"permission": "READONLY"
		}, {
    
    
			"areaCode": "hrApproval",
			"permission": "INVISIBLE"
		}],
		"jumpNodeList": [{
    
    
			"id": "node2268_3ea5_a5db_15b0",
			"name": "人事审批"
		}, {
    
    
			"id": "node1938_8b28_c3ed_030f",
			"name": "部门审批"
		}]
	},
	"branchList": [],
	"child": {
    
    
		"name": "部门审批",
		"id": "node1938_8b28_c3ed_030f",
		"type": "HANDLE",
		"config": {
    
    
			"personConfig": {
    
    
				"mode": "NORMAL",
				"setAssigneeFlag": "YES",
				"userGroup": "99",
				"userGroupName": "系统管理员"
			},
			"permissionConfig": [{
    
    
				"areaCode": "applyArea",
				"permission": "READONLY"
			}, {
    
    
				"areaCode": "organizationApproval",
				"permission": "READONLY"
			}, {
    
    
				"areaCode": "hrApproval",
				"permission": "READONLY"
			}],
			"listenerList": [{
    
    
				"category": "TASK",
				"type": "CLASS",
				"name": "请假申请部门审批完成",
				"code": "tech.abc.platform.businessflow.listener.LeaveDepartApprovalCompleteListener",
				"event": "COMPLETE",
				"eventName": "完成"
			}, {
    
    
				"category": "TASK",
				"type": "CLASS",
				"name": "请假申请部门审批完成",
				"code": "tech.abc.platform.businessflow.listener.LeaveDepartApprovalCompleteListener",
				"event": "COMPLETE",
				"eventName": "完成"
			}]
		},
		"child": {
    
    
			"name": "人事审批",
			"id": "node2268_3ea5_a5db_15b0",
			"type": "HANDLE",
			"config": {
    
    
				"personConfig": {
    
    
					"mode": "NORMAL",
					"setAssigneeFlag": "YES",
					"userGroup": "99",
					"userGroupName": "系统管理员"
				},
				"permissionConfig": [{
    
    
					"areaCode": "applyArea",
					"permission": "READONLY"
				}, {
    
    
					"areaCode": "organizationApproval",
					"permission": "READONLY"
				}, {
    
    
					"areaCode": "hrApproval",
					"permission": "READONLY"
				}],
				"backNodeList": [{
    
    
					"id": "root",
					"name": "填报"
				}, {
    
    
					"id": "node1938_8b28_c3ed_030f",
					"name": "部门审批"
				}]
			},
			"child": {
    
    }
		}
	}
}

The department approval link in the above code defines two listeners (the same listener is added twice, mainly used to demonstrate that multiple listeners can be attached at the same time).

Click the processing type node to enter the configuration interface. The listener is at the bottom. The effect is as follows:
image.png
click the Add button to open the dialog box. When adding a new listener
image.png
event, you can drop down to select creation, completion, etc. The listener can be queried and added to the system.
image.png
After the preset listener is saved, the key information will be generated as json data as part of the node configuration, as shown in the figure below

"listenerList": [{
    
    
				"category": "TASK",
				"type": "CLASS",
				"name": "请假申请部门审批完成",
				"code": "tech.abc.platform.businessflow.listener.LeaveDepartApprovalCompleteListener",
				"event": "COMPLETE",
				"eventName": "完成"
			}, {
    
    
				"category": "TASK",
				"type": "CLASS",
				"name": "请假申请部门审批完成",
				"code": "tech.abc.platform.businessflow.listener.LeaveDepartApprovalCompleteListener",
				"event": "COMPLETE",
				"eventName": "完成"
			}]

The corresponding source code is as follows:

<template>
  <el-drawer
    :append-to-body="true"
    title="环节设置"
    v-model="visible"
    :show-close="false"
    :size="550"
    :before-close="close"
    destroy-on-close
  >
    <el-collapse v-model="activeName">
      <el-collapse-item title="权限设置" name="permissionConfig">
        <el-table :data="permissionData" style="width: 100%" highlight-current-row border>
          <el-table-column label="区域" width="120">
            <template #default="scope">{
   
   { scope.row.areaName }}</template>
          </el-table-column>
          <el-table-column label="权限">
            <template #default="scope">
              <dictionary-radio-group
                v-model="scope.row.permission"
                code="NodePermissionCode"
                class="form-item"
              />
            </template>
          </el-table-column>
        </el-table>
      </el-collapse-item>
    </el-collapse>
    <template #footer>
      <el-button type="primary" @click="save">确 定</el-button>
      <el-button @click="close">取 消</el-button>
    </template>
  </el-drawer>
</template>
<script>
import DictionaryRadioGroup from '@/components/abc/DictionarySelect/DictionaryRadioGroup.vue'

import { useStore } from '../../stores/index'
let store = useStore()
export default {
  components: { DictionaryRadioGroup },
  data() {
    return {
      activeName: ['permissionConfig'],
      // 权限数据
      permissionData: []
    }
  },
  computed: {
    visible() {
      return store.rootNodeConfigVisible
    },
    rootNodeConfig() {
      return store.rootNodeConfig
    },
    processDefinitionId() {
      return store.processDefinitionId
    }
  },
  watch: {
    rootNodeConfig(value) {
      // 加载权限设置
      this.$api.workflow.workflowNodePermissionConfig
        .getNodePermissionConfig(this.processDefinitionId, value.id)
        .then((res) => {
          if (res.data) {
            this.permissionData = res.data
            // 根据配置更新
            const permissionConfig = value.config.permissionConfig
            if (permissionConfig && permissionConfig.length > 0) {
              this.permissionData.forEach((item) => {
                permissionConfig.forEach((config) => {
                  if (config.areaCode == item.areaCode) {
                    item.permission = config.permission
                    return
                  }
                })
              })
            }
          }
        })
    }
  },
  methods: {
    close() {
      store.setRootNodeConfigVisible(false)
    },
    save() {
      const permissionConfig = this.permissionData.map((item) => {
        return {
          areaCode: item.areaCode,
          permission: item.permission
        }
      })

      const nodeConfig = Object.assign(
        store.rootNodeConfig,
        {
          config: { permissionConfig: permissionConfig }
        },
        { flag: true }
      )

      store.setRootNodeConfig(nodeConfig)
      this.close()
    }
  }
}
</script>
<style scoped></style>

rear end

listener

Using the platform's low-code configuration function, define entities as follows:
image.png
configuration properties as follows:
image.png
and then generate library tables and codes.

Listener configuration

Using the platform's low-code configuration function, define entities as follows:
image.png
configuration properties as follows:
image.png
and then generate library tables and codes.

Model conversion

Read json data, call Camunda's api after parsing, and add the listener to the bpmn model as an extension element

// 监听器配置
List<WorkflowListenerConfig> listenerConfigList = JSON.parseArray(handleNodeConfig.getString("listenerList")
																  , WorkflowListenerConfig.class);
if(CollectionUtils.isNotEmpty(listenerConfigList)) {
    
    
	ExtensionElements extensionElements = modelInstance.newInstance(ExtensionElements.class);

	for(WorkflowListenerConfig listenerConfig:listenerConfigList) {
    
    
		CamundaTaskListener listener = modelInstance.newInstance(CamundaTaskListener.class);
		listener.setCamundaEvent(listenerConfig.getEvent().toLowerCase());
		listener.setCamundaClass(listenerConfig.getCode());
		extensionElements.addChildElement(listener);
	}
	userTask.setExtensionElements(extensionElements);
}

Listener instance

Implement a specific listener for the department approval of leave applications, which is used to update the department leader's department approval opinions, approvers, and approval time to the form.

package tech.abc.platform.businessflow.listener;

import org.apache.commons.collections4.CollectionUtils;
import org.camunda.bpm.engine.delegate.DelegateTask;
import org.camunda.bpm.engine.delegate.TaskListener;
import tech.abc.platform.businessflow.entity.Leave;
import tech.abc.platform.businessflow.service.LeaveService;
import tech.abc.platform.common.utils.SpringUtil;
import tech.abc.platform.workflow.constant.WorkFlowConstant;
import tech.abc.platform.workflow.entity.WorkflowComment;
import tech.abc.platform.workflow.service.WorkflowCommentService;

import java.util.ArrayList;
import java.util.List;


/**
 * 任务监听器——请假申请部门审批完成监听器
 *
 * @author wqliu
 * @date 2023-07-25
 */
public class LeaveDepartApprovalCompleteListener implements TaskListener {
    
    
    @Override
    public void notify(DelegateTask delegateTask) {
    
    
        // 获取事件名称
        String eventName = delegateTask.getEventName();
        if(eventName.equals(WorkFlowConstant.EVENT_NAME_COMPLETE)){
    
    
            // 将部门审批意见、审批人、审批时间更新到表单
            String processInstanceId = delegateTask.getProcessInstanceId();

            // 获取请假申请单据
            LeaveService leaveService= SpringUtil.getBean(LeaveService.class);
            Leave leave = leaveService.getByprocessInstanceId(processInstanceId);

            // 获取审批信息
            String nodeId = delegateTask.getTaskDefinitionKey();
            WorkflowCommentService workflowCommentService= SpringUtil.getBean(WorkflowCommentService.class);
            WorkflowComment workflowComment=workflowCommentService.getLastHandleInfo(processInstanceId,nodeId);

            // 更新表单
            leave.setOrganizationApprovalAdvice(workflowComment.getComment());
            leave.setOrganizationApprovalName(workflowComment.getAssigneeName());
            leave.setOrganizationApprovalTime(workflowComment.getCommitTime());

            leaveService.modify(leave);

        }
    }
}

The running effect is as follows:

image.png
The content in the red box is automatically processed by the listener.

Development platform information

Platform name: 123 development platform
Introduction: Enterprise-level general development platform
Design information: csdn column
Open source address: Gitee
open source agreement: MIT
open source is not easy. Welcome to collect, like and comment.

Guess you like

Origin blog.csdn.net/seawaving/article/details/132713560