应用开发平台集成工作流系列之10——流程建模功能环节业务逻辑处理的设计与实现

news2025/1/16 3:38:10

背景

基于工作流的表单流转,在某些特定的环节,需要执行一些业务逻辑处理。例如动态分配节点处理人、发送邮件或短信给待办用户、统计流程处理时长判断是否超时,以及业务层面数据处理(例如,在请假流程中将部门领导审批环节的审批意见和时间数据写入到申请表单中)。

这些业务逻辑可以分为两大类,一类是通用处理逻辑,适用于所有流程,例如发送邮件或短信给待办用户;另一类则是与某个业务流程绑定的业务逻辑,通常是业务层面的数据处理。

技术参考

对于环节的业务逻辑处理,Camunda产品提供了监听器机制来支撑这部分功能实现。
有两种监听器,执行监听器和任务监听器,分别用于处理流程实例和任务实例的生命周期事件。

执行监听器

执行监听器允许你在流程执行过程中发生某些事件时执行外部Java代码或计算一个表达式。可以捕获的事件有:

  • 启动或结束一个流程实例。
  • 进行一个过渡。
  • 启动或结束一个活动。
  • 启动或结束一个网关。
  • 启动或结束一个中间事件。
  • 结束一个 start event 或启动一个 end event.

注:第二条中的过渡,词面意思晦涩,实际是单词Transition的翻译,从上下文语境看,具体是指边Sequence Flow,即可以在边上指定一个执行监听器。

任务监听器

任务监听器被用来在某个任务相关事件发生时执行自定义的Java逻辑或表达式,只能作为用户任务的一个子元素添加到流程定义中。
注意:任务监听器只能加载用户任务类型的节点上,而执行监听器则可以放在流程上、边上、网关等位置

可以触发任务监听的事件,网上查到的资料往往是下面这种:

  • create:在任务被创建时触发。
  • assignment:在任务被分配给用户或组时触发。
  • complete:在任务完成时触发。
  • delete:在任务删除时触发。

咋看上去,感觉没什么问题,实际细想下,太模糊,比如什么情况下会触发任务删除delete,再比如create和assignment这两个事件谁先触发,很多搜索结果显示的是,create事件是在任务所有属性包括处理人初始化完后触发,而assignment是给任务分配处理人,因此assignment会先于create触发。

实际读了下官方原始资料,事件远远不止这几个,并且谁先谁后,还有诸多的条件限定。
传送门:https://docs.camunda.org/manual/latest/user-guide/process-engine/delegation-code/

除了上面四个事件,还有更新事件update和超时事件timeout。
从官方资料可以澄清,create事件一定会先于assignment触发。
assignment在处理人变更时可能会用到,update和delete则只有在极特殊的业务场景下才会用到。

逻辑处理实现

上面说了两种监听器以及对应的事件,我们最终目的是通过监听事件,来触发对应的逻辑处理。Camuda设置逻辑处理有几种方式:
1.class: 指定包含包名在内的全路径类,该类需要实现预置的接口。

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

被调用的委托类,对于任务监听器,需要实现org.camunda.bpm.engine.impl.pvm.delegate.TaskListener接口,逻辑写在notify方法中。

public class MyTaskCreateListener implements TaskListener {

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

}

2.expression(不能与class属性一起使用):指定事件发生时将被执行的表达式。可以将DelegateTask对象和事件的名称(使用 task.eventName )作为参数传递给被调用对象.

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

3.delegateExpression: 允许指定一个表达式,该表达式可解析为实现TaskListener接口的对象,类似于服务任务。

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

4.脚本:camunda:script 子元素可以用来指定一个脚本作为任务监听器。

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

方案设计

监听器:我们关注的任务的处理,因此选用任务监听器,执行监听器有了具体的业务场景后再扩展实现。
事件:结合实际业务场景,实际我们最常用的事件就两个,一是create,即任务处理前置逻辑;二是complete,即任务处理后置逻辑。
**逻辑:**虽然官方提供了四种方式,委托类的方式更符合我们的需求场景。

监听器管理:
在监听器配置界面,虽然可以提供文本框,让流程建模人员直接输入委托类的全路径,但这种方式不太友好,容易出错,更好的方式,是通过选择来添加。

采用选择的方式,则需要实现监听器的管理,即将委托类,作为元数据注册进来。

系统实现

前端

扩展办理环节的配置属性config,定义子属性listenerConfig,存放监听器配置信息。

{
	"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": {}
		}
	}
}

上面代码中的部门审批环节,定义了两个监听器(同一个监听器添加了两遍,主要用于演示可同时附加多个监听器)。

点击办理类型的节点,进入配置界面,监听器位于最下方,效果如下:
image.png
点击新增按钮,打开对话框,新增监听器
image.png
事件可下拉选择是创建、完成等,监听器则可以查询添加系统中预置的监听器
image.png
保存后,会将关键信息生成json数据,作为节点配置的一部分,如下图所示

"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": "完成"
			}]

对应源码如下:

<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>

后端

监听器

使用平台低代码配置功能,定义实体如下:
image.png
配置属性如下:
image.png
然后生成库表与代码。

监听器配置

使用平台低代码配置功能,定义实体如下:
image.png
配置属性如下:
image.png
然后生成库表与代码。

模型转换

读取json数据,解析后调用Camunda 的api,将监听器作为扩展元素加入到bpmn模型中

// 监听器配置
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);
}

监听器实例

为请假申请的部门环节审批实现一个特定的监听器,用于将部门领导将部门审批意见、审批人、审批时间更新到表单。

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);

        }
    }
}

运行效果如下:

image.png
红框中的内容是由监听器自动处理完成的。

开发平台资料

平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:csdn专栏
开源地址:Gitee
开源协议:MIT
开源不易,欢迎收藏、点赞、评论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/998360.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity之Android项目的打包

一 Unity里面配置Android运行环境 1.1 首先unity需要集成android编译环境&#xff0c;点击FIle->Build Settings 1.2 没是否有Android模块&#xff0c;没的话先下载Android模块 1.3 按下面的操作&#xff0c;下载Android支持&#xff0c;SDK&#xff0c;NDK&#xff0c;和J…

15 - 多线程调优(上):哪些操作导致了上下文切换?

1、初识上下文切换 我们首先得明白&#xff0c;上下文切换到底是什么。 其实在单个处理器的时期&#xff0c;操作系统就能处理多线程并发任务。处理器给每个线程分配 CPU 时间片&#xff08;Time Slice&#xff09;&#xff0c;线程在分配获得的时间片内执行任务。 CPU 时间…

【图解RabbitMQ-6】说说交换机在RabbitMQ中的四种类型以及使用场景

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;CSDN实力新星&#xff0c;后端开发两年经验&#xff0c;曾担任甲方技术代表&#xff0c;业余独自创办智源恩创网络科技工作室。会点点Java相关技术栈、帆软报表、低代码平台快速开…

自然语言处理: 第十二章LoRA解读

论文地址:[2106.09685] LoRA: Low-Rank Adaptation of Large Language Models (arxiv.org) 理论基础 自从GPT-3.5问世以来&#xff0c;整个AI界基本都走向了大模型时代&#xff0c;而这种拥有数亿参数的大模型对于普通玩家来说作全量微调基本是不可能的事。从而微软公司提出了…

指令延迟隐藏

一、指令延迟隐藏 1. 延迟和延迟隐藏 指令延迟指计算指令从调度到指令完成所需的时钟周期如果在每个时钟周期都有就绪的线程束可以被执行&#xff0c;此时GPU处于满符合状态指令延迟被GPU满负荷计算状态所掩盖的现象称为延迟隐藏延迟隐藏对GPU编程开发很重要&#xff0c;GPU设…

BeanFactory 和 FactoryBean傻傻分不清楚

&#x1f935;‍♂️ 个人主页&#xff1a;香菜的个人主页&#xff0c;加 ischongxin &#xff0c;备注csdn ✍&#x1f3fb;作者简介&#xff1a;csdn 认证博客专家&#xff0c;游戏开发领域优质创作者,华为云享专家&#xff0c;2021年度华为云年度十佳博主 &#x1f40b; 希望…

HTTPS双向认证

双向认证&#xff0c;指的是客户端和服务器端都需要验证对方的身份&#xff0c;在建立HTTPS连接的过程中&#xff0c;握手的流程相对于单向认证多了几步。 单向认证的过程&#xff0c;客户端从服务器端下载服务器端公钥证书进行验证&#xff0c;然后建立安全通信通道。 双向通信…

java的数据类型与变量(超详细每个都有小结论,习题巩固)

【本文章的目标】 1.字面常量 2.数据类型 3.变量 文章最后有习题等来帮助巩固&#xff0c;加深印象&#xff0c;相信看完这篇文章&#xff0c;大家会有收获 1.字面常量 在上节课HelloWorld程序中&#xff0c;System.Out,println(Hello World"); 语句&#xff0c;不论…

算法[动态规划]---买卖股票最佳时机

1、题目&#xff1a; 给你一个整数数组 prices&#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你在任何时候最多只能持一股股票。你也可以先购买&#xff0c;然后在同一天出售。 返回你能获得的最大利润 。 2…

PLSQL

文章目录 基本pl/sql语法流程控制条件判断&#xff08;两种&#xff09;循环结构&#xff08;三种&#xff09;goto&#xff0c;exit关键字 游标的使用异常的处理存储过程&#xff08;无返回值&#xff09;&#xff0c;存储函数&#xff08;有返回值&#xff09;触发器 命令行窗…

苹果手机远程控制安卓手机,为什么不能发起控制?

这位用户想要用iOS设备远程控制安卓设备&#xff0c;在被控端安装好AirDroid之后&#xff0c;就在控制端的苹果手机上也安装了AirDroid&#xff0c;然而打开控制端的软件&#xff0c;却没有在手机界面上看到【远程控制】按钮&#xff0c;于是提出了以上疑问。 解答 想要让iOS设…

A,B,C , D, E类地址的划分及子网划分汇总的详解

一、 A类地址 &#xff08;1&#xff09;A类地址第1字节为网络地址&#xff0c;其它3个字节为主机地址。它的第1个字节的第一位固定为0. &#xff08;2&#xff09;A类地址范围&#xff1a;1.0.0.1—126.255.255.254 &#xff08;3&#xff09;A类地址中的私有地址和保留地…

苹果电脑快捷键集合

苹果电脑Windows系统下的ALT键是组合键。苹果电脑键盘左下角的Fnoption是Windows的alt键。同时按下两个键是ALT键的功能。在非组合状态下&#xff0c;单独按Option键。 补充&#xff1a; 1. 按controlalt&#xff08;选项&#xff09;delete 启动任务管理器。 2. Option-Del…

nrf52832 使用ADC点LED

#define SAMPLES_IN_BUFFER 5 volatile uint8_t state 1;/*** brief UART events handler.*/void saadc_callback(nrf_drv_saadc_evt_t const * p_event) { // }//saadc的初始化 void saadc_init(void) {ret_code_t err_code;nrf_saadc_channel_config_t channel_config NR…

C#,数值计算——柯西微分(Cauchy deviates)的计算方法与源代码

1 文本格式 using System; namespace Legalsoft.Truffer { /// <summary> /// Cauchy deviates /// </summary> public class Cauchydev : Ran { private double mu { get; set; } private double sig { get; set; } public…

C++ -- 学习系列 static 关键字的使用

static 是 C 中常用的关键字&#xff0c;被 static 修饰的变量只会在 静态存储区&#xff08;常量数据也存放在这里&#xff09; 被分配一次内存&#xff0c;生命周期与整个程序一样&#xff0c;随着程序的消亡而消亡。 一 static 有以下几种用法&#xff1a; 1. 在文件中定义…

管理类联考——数学——汇总篇——知识点突破——应用题——交叉比例法/杠杆原理

读书笔记 甲有&#xff1a;x个a&#xff0c;乙有&#xff1a;y个b&#xff0c;甲乙的平均值为c&#xff0c;根据总数相等&#xff0c;得&#xff1a;axbyc(xy)&#xff0c;即ax-cxcy-by&#xff0c;则 x y c − b a − c \frac{x}{y}\frac{c-b}{a-c} yx​a−cc−b​ &#…

【Vue2.0源码学习】生命周期篇-初始化阶段(initState)

文章目录 1. 前言2. initState函数分析3. 初始化props3.1 规范化数据3.2 initProps函数分析3.3 validateProp函数分析3.4 getPropDefaultValue函数分析3.5 assertProp函数分析 4. 初始化methods5. 初始化data6. 初始化computed6.1 回顾用法6.2 initComputed函数分析6.3 defineC…

rv1126之isp黑电平(BLC)校准!

前言&#xff1a; 大家好&#xff0c;今天我们继续来讲解isp第二期内容&#xff0c;这期内容主要分三个部分&#xff1a; 1、tunning的工作流程 2、利用RKISP2.x_Tuner来创建tunning工程&#xff0c;并连接上rv1126开发板进行抓图 3、BLC(黑电平校准)的原理和校准方法以及实战…

UE4(Unreal Engine 4)运行setup.bat发生403报错的问题

最近UE官方在迁移服务器&#xff0c;有些D:\UE4\Engine\Build\Commit.gitdeps.xml文件需要更新。此时需要你去往UE对应的版本下载新的Commit.gitdeps.xml文件&#xff0c;并且覆盖原有的Commit.gitdeps.xml文件。UE的官方说明 覆盖前 覆盖后