アプリケーション開発プラットフォーム 統合ワークフロー シリーズ 10 - プロセス モデリング機能リンクにおけるビジネス ロジック処理の設計と実装

背景

ワークフローベースのフォーム フローでは、特定のリンクでビジネス ロジック処理を実行する必要があります。たとえば、ノード ハンドラーの動的割り当て、To-Do ユーザーへの電子メールまたはテキスト メッセージの送信、タイムアウトしたかどうかを判断するためのプロセス処理時間のカウント、ビジネス レベルのデータ処理 (たとえば、休暇プロセス、承認意見の作成など)部門長の承認プロセスの時間データを申請書に記載します)。

これらのビジネス ロジックは、2 つの主要なカテゴリに分類できます。1 つは、To-Do ユーザーへの電子メールやテキスト メッセージの送信など、すべてのプロセスに適用できる一般的な処理ロジックであり、もう 1 つは、通常は特定のビジネス プロセスにバインドされたビジネス ロジックです。ビジネスレベルでのデータ処理。

技術資料

リンクのビジネス ロジック処理のために、Camunda 製品は、関数のこの部分の実装をサポートするリスナー メカニズムを提供します。
リスナーには実行リスナーとタスク リスナーの 2 種類があり、それぞれプロセス インスタンスとタスク インスタンスのライフサイクル イベントを処理するために使用されます。

実行リスナー

実行リスナーを使用すると、プロセス実行中に特定のイベントが発生したときに、外部 Java コードを実行したり、式を評価したりできます。キャプチャできるイベントは次のとおりです。

  • プロセス インスタンスを開始または終了します。
  • 移行を行います。
  • アクティビティを開始または終了します。
  • ゲートウェイを開始または停止します。
  • 中間イベントを開始または終了します。
  • 開始イベントを終了するか、終了イベントを開始します。

注: 2 番目の記事のトランジションにはあいまいな意味がありますが、実際には Transition という単語の翻訳です。コンテキストからは、特にエッジ シーケンス フローを指します。つまり、エッジで実行リスナーを指定できます。

タスクリスナー

タスク リスナーは、タスク関連のイベントが発生したときにカスタム Java ロジックまたは式を実行するために使用され、ユーザー タスクのサブ要素としてプロセス定義にのみ追加できます。
注: タスク リスナーはユーザー タスク タイプのノードにのみロードできますが、実行リスナーはプロセス、エッジ、ゲートウェイなどに配置できます。

タスク監視をトリガーする可能性のあるイベント、インターネット上で見つかる情報は、次のとおりです。

  • create: タスクの作成時にトリガーされます。
  • 割り当て: タスクがユーザーまたはグループに割り当てられるとトリガーされます。
  • complete: タスクが完了するとトリガーされます。
  • delete: タスクが削除されるとトリガーされます。

一見すると問題なさそうですが、よく考えてみるとタスクの削除はどのような状況でトリガーされるのか、作成イベントや割り当てイベントは誰が最初にトリガーするのかなど、よく考えると漠然としすぎています。多くの検索結果では、作成イベントが にあることが示されています。ハンドラーを含むタスクのすべての属性は初期化後にトリガーされ、割り当てではハンドラーがタスクに割り当てられるため、割り当ては作成前にトリガーされます。

実際に公式のソースを読んでみると、これらよりもはるかに多くのイベントがあり、誰が最初で誰が最後になるかを決定するための条件がたくさんあることがわかりました。
ポータル: https://docs.camunda.org/manual/latest/user-guide/process-engine/delegation-code/

上記 4 つのイベントの他に、更新イベントやタイムアウト イベントもあります。
公式情報から、割り当て前に必ず create イベントがトリガーされることが明らかにされています。
割り当ては人の変更を処理するときに使用される場合がありますが、更新と削除は非常に特殊なビジネス シナリオでのみ使用されます。

ロジック処理の実装

2 種類のリスナーと対応するイベントについては上で説明しましたが、最終的な目標は、イベントをリッスンすることで、対応する論理処理をトリガーすることです。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 インターフェイスを実装する必要があり、ロジックは通知メソッドに記述されます。

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>

デザイン

リスナー: タスクの処理に重点を置くため、タスク リスナーを選択し、特定のビジネス シナリオでリスナーを実行した後、実装を拡張します。
イベント: 実際のビジネス シナリオに基づくと、実際に最もよく使用されるイベントは 2 つあり、1 つはタスク処理の前ロジックである create 、もう 1 つはタスク処理の後ロジックである complete です。
**ロジック:** 公式では 4 つのメソッドが提供されていますが、委任クラス メソッドの方が需要シナリオにより一致しています。

リスナー管理:
リスナー構成インターフェイスでは、プロセス モデラーがデリゲート クラスのフル パスを直接入力できるようにテキスト ボックスを提供できますが、この方法はあまりフレンドリーではなく、エラーが発生しやすくなります。選択です。

選択メソッドを使用して、リスナーの管理を実装する必要があります。つまり、委任クラスをメタデータとして登録する必要があります。

システム導入

フロントエンド

管理リンクの構成属性 config を展開し、サブ属性 listenrConfig を定義し、リスナー構成情報を保管します。

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

上記のコードの部門承認リンクは 2 つのリスナーを定義しています (同じリスナーが 2 回追加されており、主に複数のリスナーを同時に接続できることを示すために使用されます)。

処理タイプ ノードをクリックして構成インターフェイスに入ります。リスナーは一番下にあります。結果は次のとおりです: [
画像.png
追加] ボタンをクリックしてダイアログ ボックスを開きます。新しいリスナー
画像.png
イベントの場合は、ドロップダウンで作成、完了などを選択できます。 ., リスナーをクエリしてシステムに追加することができます。プリセット リスナーが保存される
画像.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>

後部

リスナー

プラットフォームのローコード構成機能を使用して、エンティティを次のように定義します:
画像.png
構成プロパティを次のように定義し
画像.png
、ライブラリ テーブルとコードを生成します。

リスナーの設定

プラットフォームのローコード構成機能を使用して、エンティティを次のように定義します:
画像.png
構成プロパティを次のように定義し
画像.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);

        }
    }
}

ランニング効果は以下の通りです。

画像.png
赤いボックス内の内容は、リスナーによって自動的に処理されます。

開発プラットフォーム情報

プラットフォーム名: 123 開発プラットフォーム
紹介: エンタープライズレベルの総合開発プラットフォーム
設計情報: csdn コラム
オープンソースアドレス: Gitee
オープンソース契約: MIT
オープンソースは簡単ではありません、収集、いいね、コメントを歓迎します。

おすすめ

転載: blog.csdn.net/seawaving/article/details/132713560