欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

Camunda入门(四)

时间:2023-08-01
目录

1、支付流程 - 场景设定2、业务DB设计3、流程模型设计

3.1 新建BPMN - 支付流程定义

3.1.1 任务Task相关设置

Business Rule Task - 商品折扣规则User Task - 用户确认付款Service Task - 调用支付服务 3.1.2 网关Gateway相关设置 3.2 新建DMN - 商品折扣规则定义3.3 新建FORM - 支付请求表单、用户确认表单 4、启动流程应用

4.1 流程定义自动部署4.2 流程场景示例代码4.3 流程变量与业务数据同步4.4 调用支付服务 - JavaDelegate实现4.5 使用业务数据查询代替流程引擎查询4.6 测试用例 1、支付流程 - 场景设定

为了快速上手Camunda,结合Camunda官方QuickStart示例,
本文设计了如下支付请求实例:

由用户发起支付请求(商品名称、金额、支付用户ID)根据商品判断对应的折扣若折扣后实际支付金额 < 1000则直接 调用支付接口(JavaDelegate)若折扣后实际支付金额 >= 1000则需要征求用户同意若用户同意后,才可继续调用支付接口,否则支付失败

如上案例需创建以下4个模型:

功能对应文件类型支付流程模型PaymentProcess.bpmnBPMN商品折扣决策模型ProductDiscountDecision.dmnDMN支付请求表单模型PaymentInitForm.formForm用户确认表单模型Payment/confirm/iForm.formForm2、业务DB设计

该流程比较简单,且各个活动间数据相差不大,
所以采用整个流程对应一个业务对象的设计。
注: 采用流程实例的BusinessKey来存储对应业务数据的ID

数据库采用Mysql 5.7,
首先新建Camunda流程引擎的对应的数据库Camunda_716(该名称可根据业务需求适当调整),
然后在此库中新建业务数据表biz_payment_process_info,该表具体定义如下:

CREATE TABLE `biz_payment_process_info` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '业务主键', `process_instance_id` varchar(36) DEFAULT NULL COMMENT '流程实例ID', `product_name` varchar(64) DEFAULT NULL COMMENT '商品名称', `product_price` decimal(10,2) DEFAULT NULL COMMENT '商品金额', `product_discount` decimal(3,2) DEFAULT NULL COMMENT '商品折扣', `product_discount_price` decimal(10,2) DEFAULT NULL COMMENT '商品折扣后金额', `payment_assignee` varchar(64) DEFAULT NULL COMMENT '支付用户ID', `approval_result` tinyint(3) unsigned DEFAULT '0' COMMENT '用户是否同意付款(0:未确认,1:同意, 2:不同意)', `approval_time` datetime DEFAULT NULL COMMENT '用户确认时间', `payment_result` tinyint(3) unsigned DEFAULT '0' COMMENT '支付结果(0:未支付,1:成功, 2:失败)', `payment_time` datetime DEFAULT NULL COMMENT '支付时间', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付流程 - 业务数据';

注: Camunda流程引擎相关的其他表可在Camunda流程应用启动时连接同一数据库后自动创建。

3、流程模型设计 3.1 新建BPMN - 支付流程定义

首先根据之前流程定义,创建如下PaymentProcess.bpmn,
其中Id=PaymentProcess,此属性比较重要,后续通过流程应用启动流程皆需要通过此Id来标识对应的流程定义,
如下图中processDefinitionKey即对应此Id,businessKey可对应业务数据唯一ID。

注: 一定记得勾选流程的Excutable属性,否则流程不可执行。

其中Event事件包括:

Start Event - 支付请求

Form - 关联支付请求表单 End Event - 支付成功End Event - 用户拒绝支付

其中Task任务包括:

Business Rule Task - 商品折扣规则User Task - 用户确认付款

Form - 关联用户确认表单 Service Task - 调用支付服务

其中Gateway网关包括:

Exclusive Gateway - 是否需要用户确认?Exclusive Gateway - 用户是否同意支付? 3.1.1 任务Task相关设置

新建Task后,如下图选择扳手图标即可设置不同的Task类型。

Business Rule Task - 商品折扣规则

Business Rule Task - 商品折扣规则任务,主要需要定义如下内容:

Id、Name属性DMN绑定 - 设置任务绑定DMN(引用DMN文件中的具体Decision.id),通过单独DMN文件定义决策规则,即定义商品折扣规则。设置规则输出结果变量 - 设置对Decision中规则表格的输出结果的引用变量名称及类型

注: 关于Business Rule Task中的输出变量类型,包括:

single Result(Map>) - 单行规则多个output,且允许同时命中多条规则
User Task - 用户确认付款

User Task - 用户确认付款任务,主要需要定义如下内容:

Id、Name属性设置用户任务的处理人 - 通过流程变量paymentAssignee(可任意调整变量名)设置需要处理该任务的用户ID。绑定用户确认的form表单 - 即通过form-key的方式来绑定单独的FORM定义,在后续执行到此处用户任务时,即可在Camunda管理平台TaskList中展示对应的表单。

注:
不建议在BPMN中定义内嵌及关联表单,可由业务应用动态设置流程变量,增加灵活性,
此示例中的相关FORM定义皆是为了学习、测试使用。

Service Task - 调用支付服务

关于Service Task(调用服务任务)调用服务的方式有多种:

调用Java代码External Task调用web服务(REST、SOAP)

本示例使用调用Java代码方式,即自定义JavaDelegate实现。

Service Task - 调用支付服务任务,主要定义内容如下:

Id、Name属性设置对应Java Delegate实现 - 即执行到当前Service Task时会调用指定的JavaDelegate实现。 3.1.2 网关Gateway相关设置

本示例中使用的皆是排他网关Exclusive Gateway。

网关后即产生不同的流程分支,关于分支条件的设置如下:

3.2 新建DMN - 商品折扣规则定义


点击商品折扣规则的左上角蓝色表格图标后,即可进入规则表格编辑界面,

3.3 新建FORM - 支付请求表单、用户确认表单 4、启动流程应用

SpringBoot集成Camunda流程应用具体代码参见:
https://gitee.com/luoex/camunda-demo

4.1 流程定义自动部署

如前文描述的BPMN、DMN、FORM文件皆可以放在应用代码resource目录下,
支持嵌套文件夹,如resources/bpmn、resources/payment,如下图:

同时在resources/meta-INF下添加processes.xml文件,
该文件用于描述流程应用关于流程自动部署的相关设置,

default bpmn/PaymentInitForm.form bpmn/Payment/confirm/iForm.form false true

添加如上设置后,在流程应用启动后,即可自动将resources下的流程定义部署到Camunda仓库(数据库)中,

4.2 流程场景示例代码

关于流程应用的几个简单编程场景示例代码如下:

//启动流程 - 如开启支付请求String businessKey = "111";ProcessInstance processInstance = this.runtimeService.startProcessInstanceByKey("PaymentProcess", businessKey);Map processVariablesMap = CamundaUtils.convertProcessVariablesFromEntity(processVariables);ProcessInstance processInstance = this.runtimeService.startProcessInstanceByKey("PaymentProcess", businessKey, processVariablesMap); //查询待处理任务 - 如获取指定用户待确认的支付请求List taskList = this.taskService.createTaskQuery() .taskAssignee("luo") .processDefinitionKey("PaymentProcess") .taskDefinitionKey("Task_User/confirm/iPayment") .orderByTaskCreateTime() .listPage(0, 10); //完成任务 - 如确认支付Map processVariablesMap = CamundaUtils.convertProcessVariablesFromEntity(processVariables);this.taskService.complete(taskId, processVariablesMap);//查看历史数据List historicTaskInstanceList = this.historyService.createHistoricTaskInstanceQuery() .taskAssignee("luo") .processDefinitionKey("PaymentProcess") .taskDefinitionKey("Task_User/confirm/iPayment") .finished() .orderByHistoricActivityInstanceStartTime() .listPage(0, 10);

4.3 流程变量与业务数据同步

由于使用了自定义业务数据表biz_payment_process_info记录流程数据,
所以需要在流程执行过程中将流程变量中的相关数据同步到biz_payment_process_info中,

启动流程时 - 初始biz_payment_process_info数据,设置process_instance_id启动流程时 - 需设置流程实例的businessKey为对应biz_payment_process_info的记录id用户确认时 - 同步userApproval用户确认结果及折扣金额 到biz_payment_process_info中调用支付服务时 - 同步paymentResult支付结果及支付时间 到biz_payment_process_info中

关于流程应用的编码及数据同步,可参见:

camunda-process-application/payment-application/…/BizPaymentProcessInfoServiceImpl.java

import com.baomidou.mybatisplus.core.metadata.IPage;import com.baomidou.mybatisplus.core.toolkit.Wrappers;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.luo.camunda.app.constants.PaymentProcessConstants;import com.luo.camunda.app.enums./confirm/iResultEnum;import com.luo.camunda.app.enums.PaymentResultEnum;import com.luo.camunda.app.mapper.BizPaymentProcessInfoMapper;import com.luo.camunda.app.model.entity.BizPaymentProcessInfo;import com.luo.camunda.app.model.param.Payment/confirm/iParam;import com.luo.camunda.app.model.param.PaymentQueryParam;import com.luo.camunda.app.model.param.PaymentRequestParam;import com.luo.camunda.app.model.wrapper.PaymentProcessVariablesWrapper;import com.luo.camunda.app.service.IBizPaymentProcessInfoService;import com.luo.camunda.app.utils.CommonUtils;import com.luo.camunda.common.model.param.ProcessVariablesQueryParam;import com.luo.camunda.common.model.param.TaskQueryParam;import com.luo.camunda.common.model.vo.TaskVo;import com.luo.camunda.common.servcie.CamundaCommonService;import com.luo.camunda.common.utils.CamundaUtils;import com.luo.demo.sc.base.model.result.RespResult;import lombok.extern.slf4j.Slf4j;import org.camunda.bpm.engine.history.HistoricTaskInstance;import org.camunda.bpm.engine.runtime.ProcessInstance;import org.springframework.beans.BeanUtils;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.time.LocalDateTime;import java.util.Map;@Service@Slf4jpublic class BizPaymentProcessInfoServiceImpl extends ServiceImpl implements IBizPaymentProcessInfoService { @Resource private CamundaCommonService camundaCommonService; @Override @Transactional(rollbackFor = Exception.class) public RespResult startPaymentProcess(PaymentRequestParam paymentRequestParam) { BizPaymentProcessInfo bizPaymentProcessInfo = new BizPaymentProcessInfo(); //拷贝参数中的属性:productName, productPrice, paymentAssignee BeanUtils.copyProperties(paymentRequestParam, bizPaymentProcessInfo); //设置业务数据默认状态 bizPaymentProcessInfo.setPaymentResult(PaymentResultEnum.NOT_PAY.getCode()); bizPaymentProcessInfo.setApprovalResult(/confirm/iResultEnum.NOT_/confirm/i.getCode()); log.info("保存支付流程业务数据,参数:{}", paymentRequestParam); Boolean result = this.save(bizPaymentProcessInfo); log.info("保存支付流程业务数据,结果:{}", result); log.info("开启支付流程处理,processKey:{}, businessKey:{}", PaymentProcessConstants.PAYMENT_PROCESS_ID, bizPaymentProcessInfo.getId()); ProcessInstance processInstance = this.camundaCommonService.startProcessInstance( PaymentProcessConstants.PAYMENT_PROCESS_ID, String.valueOf(bizPaymentProcessInfo.getId()), paymentRequestParam); log.info("开启支付流程处理,结果:{}", processInstance); result = this.updateById(BizPaymentProcessInfo.builder() .processInstanceId(processInstance.getId()) .id(bizPaymentProcessInfo.getId()) .build()); log.info("更新支付流程业务数据 - 流程实例ID,结果:{}", result); return RespResult.successData(bizPaymentProcessInfo.getProcessInstanceId()); } @Override public RespResult> queryTasks(TaskQueryParam taskQueryParam) { log.info("查询待处理任务列表,参数:{}", taskQueryParam); RespResult> respResult = this.camundaCommonService.queryRuntimetasks(taskQueryParam, (bizKey) -> { return this.getById(Long.valueOf(bizKey)); }); log.info("查询待处理任务列表,结果:{}", respResult); return respResult; } @Override public RespResult queryHistoryTasks(TaskQueryParam taskQueryParam) { return this.camundaCommonService.queryHistoryTasks(taskQueryParam); } @Override @Transactional(rollbackFor = Exception.class) public RespResult /confirm/iPayment(PaymentConfirmParam payment/confirm/iParam) { log.info("用户确认支付, 参数:{}", payment/confirm/iParam); ProcessVariablesQueryParam processVariablesQueryParam = ProcessVariablesQueryParam.builder() //.taskId(payment/confirm/iParam.getTaskId()) .processInstanceId(payment/confirm/iParam.getProcessInstanceId()) .build(); Map existProcessVariables = this.camundaCommonService.getRuntimeProcessVariables(processVariablesQueryParam); log.info("获取当前已存在流程变量,结果:{}", existProcessVariables); //使用流程变量包装器(便于获取属性) PaymentProcessVariablesWrapper existPaymentProcessVariablesWrapper = new PaymentProcessVariablesWrapper(existProcessVariables); BizPaymentProcessInfo bizPaymentProcessInfo = BizPaymentProcessInfo.builder() .productDiscount(existPaymentProcessVariablesWrapper.getProductDiscount()) .productDiscountPrice(existPaymentProcessVariablesWrapper.getProductDiscountPrice()) .approvalResult(payment/confirm/iParam.getApprovalResult()) .approvalTime(LocalDateTime.now()) .id(Long.valueOf(payment/confirm/iParam.getBizKey())) .build(); log.info("更新用户确认信息,参数:{}", bizPaymentProcessInfo); Boolean result = this.updateById(bizPaymentProcessInfo); log.info("更新用户确认信息,结果:{}", result); Map processVariables = CamundaUtils.convertProcessVariablesFromPair(PaymentProcessConstants.PAYMENT_APPROVAL_RESULT_VAR_NAME, payment/confirm/iParam.getApprovalResult()); this.camundaCommonService.completeTask(payment/confirm/iParam.getTaskId(), processVariables); return RespResult.success(); } @Override public RespResult queryPayments(PaymentQueryParam paymentQueryParam) { IPage pageResult = this.page( CommonUtils.convertPage(paymentQueryParam), Wrappers.lambdaQuery().eq(null != paymentQueryParam.getId(), BizPaymentProcessInfo::getId, paymentQueryParam.getId()) .eq(null != paymentQueryParam.getProcessInstanceId(), BizPaymentProcessInfo::getProcessInstanceId, paymentQueryParam.getProcessInstanceId()) .like(null != paymentQueryParam.getProductName(), BizPaymentProcessInfo::getProductName, paymentQueryParam.getProductName()) .ge(null != paymentQueryParam.getProductPriceStart(), BizPaymentProcessInfo::getProductPrice, paymentQueryParam.getProductPriceStart()) .le(null != paymentQueryParam.getProductPriceEnd(), BizPaymentProcessInfo::getProductPrice, paymentQueryParam.getProductPriceEnd()) .ge(null != paymentQueryParam.getProductDiscountStart(), BizPaymentProcessInfo::getProductDiscount, paymentQueryParam.getProductDiscountStart()) .le(null != paymentQueryParam.getProductDiscountEnd(), BizPaymentProcessInfo::getProductDiscount, paymentQueryParam.getProductDiscountEnd()) .ge(null != paymentQueryParam.getProductDiscountPriceStart(), BizPaymentProcessInfo::getProductDiscountPrice, paymentQueryParam.getProductDiscountPriceStart()) .le(null != paymentQueryParam.getProductDiscountPriceEnd(), BizPaymentProcessInfo::getProductDiscountPrice, paymentQueryParam.getProductDiscountPriceEnd()) .eq(null != paymentQueryParam.getApprovalResult(), BizPaymentProcessInfo::getApprovalResult, paymentQueryParam.getApprovalResult()) .eq(null != paymentQueryParam.getPaymentResult(), BizPaymentProcessInfo::getPaymentResult, paymentQueryParam.getPaymentResult()) .eq(null != paymentQueryParam.getPaymentAssignee(), BizPaymentProcessInfo::getPaymentAssignee, paymentQueryParam.getPaymentAssignee()) .ge(null != paymentQueryParam.getCreateTimeStart(), BizPaymentProcessInfo::getCreateTime, paymentQueryParam.getCreateTimeStart()) .le(null != paymentQueryParam.getCreateTimeEnd(), BizPaymentProcessInfo::getCreateTime, paymentQueryParam.getCreateTimeEnd()) .ge(null != paymentQueryParam.getApprovalTimeStart(), BizPaymentProcessInfo::getApprovalTime, paymentQueryParam.getApprovalTimeStart()) .le(null != paymentQueryParam.getApprovalTimeEnd(), BizPaymentProcessInfo::getApprovalTime, paymentQueryParam.getApprovalTimeEnd()) .ge(null != paymentQueryParam.getPaymentTimeStart(), BizPaymentProcessInfo::getPaymentTime, paymentQueryParam.getPaymentTimeStart()) .le(null != paymentQueryParam.getPaymentTimeEnd(), BizPaymentProcessInfo::getPaymentTime, paymentQueryParam.getPaymentTimeEnd()) ); return CommonUtils.convertPageResult(pageResult); }}

4.4 调用支付服务 - JavaDelegate实现

import com.luo.camunda.app.enums.PaymentResultEnum;import com.luo.camunda.app.model.wrapper.PaymentProcessVariablesWrapper;import com.luo.camunda.app.model.entity.BizPaymentProcessInfo;import com.luo.camunda.app.service.IBizPaymentProcessInfoService;import com.luo.demo.sc.base.execption.MsgRuntimeException;import lombok.extern.slf4j.Slf4j;import org.camunda.bpm.engine.delegate.DelegateExecution;import org.camunda.bpm.engine.delegate.JavaDelegate;import org.springframework.stereotype.Component;import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.time.LocalDateTime;@Component@Slf4jpublic class PaymentDelegate implements JavaDelegate { @Resource private IBizPaymentProcessInfoService bizPaymentProcessInfoService; @Override @Transactional(rollbackFor = Exception.class) public void execute(DelegateExecution execution) { String bizKey = execution.getProcessBusinessKey(); String processInstanceId = execution.getProcessInstanceId(); log.info("调用支付服务,processInstanceId: {}, bizKey: {}", processInstanceId, bizKey); PaymentProcessVariablesWrapper paymentProcessVariablesWrapper = new PaymentProcessVariablesWrapper(execution.getVariables()); log.info("RPC调用支付服务, 流程变量:{}", paymentProcessVariablesWrapper); BizPaymentProcessInfo bizPaymentProcessInfo = BizPaymentProcessInfo.builder() .id(Long.valueOf(bizKey)) .paymentResult(PaymentResultEnum.PAY_SUCCESS.getCode()) .paymentTime(LocalDateTime.now()) .build(); log.info("更新支付结果, 参数:{}", bizPaymentProcessInfo); Boolean result = this.bizPaymentProcessInfoService.updateById(bizPaymentProcessInfo); log.info("更新支付结果, 结果:{}", result); //抛出异常,则此task执行失败,回退到上一步 //throw new MsgRuntimeException("支付异常"); }}

4.5 使用业务数据查询代替流程引擎查询

由于使用了自定义业务数据表biz_payment_process_info记录流程数据,
则之后的流程相关数据查询可优先采用查询biz_payment_process_info表来实现,
与流程引擎解耦,可根据自身业务特性优化自定义业务存储实现。
例如:

查询所有支付请求记录查询指定用户的支付请求记录(payment_assignee=‘luo’)查询用户不同意支付的请求(approval_result = 2 )查询支付成功的支付请求(payment_result=1)…可根据自身需求调整业务数据设计 4.6 测试用例

支付示例 - 流程应用的具体调用逻辑可参见测试用例:
camunda-process-application/payment-application/…/CamundaPaymentApplicationTest.java

首先用户开启支付流程 - test01_startPayment然后用户获取待处理任务 - 待用户确认的支付请求 - test02_getTasks提交用户确认请求 - test03_/confirm/iPayment流程应用调用支付服务 - PaymentDelegate查询流程引擎历史任务 - test04_getHistoryTasks查询支付数据 - test05_getPayments_all, test06_getPayments_not_/confirm/i, test07_getPayments_with_condition

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。