应用开发平台集成工作流系列之11——流程模板功能设计与实现

背景

原方案

之前直接使用Camunda自带的流程建模设计器,流程建模输出的是xml,所有的信息,包括流程的名称、编码、分类等流程定义信息,都是放在xml里的。
前端传给后端一个xml数据,后端Camunda引擎解析xml后将流程持久化下来。
Camunda自带的流程设计器虽然是符合BPMN2.0规范,但对用户并不友好,有大量的冗余的配置信息需要设置。

新方案

对于流程设置不友好的问题,国内钉钉另行设计与实现了一套流程建模模式,跟bpmn规范无关,有人仿照实现了下,并做了开源(https://github.com/StavinLi/Workflow-Vue3),效果图如下:

实现大致原理是基于无限嵌套的子节点,输出json数据,传给后端,后端进行解析后,调用Camunda引擎的api,转换成流程模型后持久化。

流程模板功能设计与实现

关键设计

流程模板不同于一般的业务实体对象,有“版本”的概念。并且,在同一时间点,某个流程模板只有一个版本处于使用状态,可基于模板创建流程实例。但是,在同一时间点,可能存在多个流程实例,属于同一个流程模板的不同版本。因为流程实例从创建到流转再到最后结束,是一个持续的过程。在持续期间,如业务需求变更,需要调整流程模板,则遵循的原则是在途的流程实例,继续按照原流程模板走完。新发起的流程实例,直接参照修改后的流程模板来创建和流转。

增加业务版本号

Camunda的内置版本号是一个整形数字,用于内部版本控制,不具备业务含义。
对于流程模板而言,适合类似于1.0这样的两段式版本,第一段代表大调整,第二段代表小调整。
我们新增一个属性,就叫模板版本templateVersion,用于自定义的业务版本号。

引入临时流程定义标识

对于Camunda,每个流程模板有唯一性的流程编码,内部会有个整形的版本号,然后每次发布,都会版本号加1,然后使用uuid生成发布标识,然后再按 “流程编码:版本号:发布标识”的规则,拼成一个唯一性的标识,作为该流程模板该版本的唯一性标识。对应的库表是act_re_procdef,如下图所示:
image.png
流程模型中的相关配置,如环节人员设置、流程权限配置,需要使用流程定义标识来确定关联到哪个流程模板的哪个版本。但这个流程标识,只有在流程发布后,Camunda引擎才会生成。
这里引入了一个临时流程定义标识,其组成只有两段,流程模板编码:Camunda内置版本号+1,用于流程建模阶段暂存配置信息,后续流程正式发布后,使用生成的流程定义标识更新,从而保证配置与流程模板的版本对应关系不变。

规划流程模板状态

工作流模板多版本的特性,对流程模板的状态提出了要求,具体设计如下:
未发布:新建或版本升级操作后产生的记录,尚未发布。
运行中:发布后,处于生效中的流程模板,新建流程实例基于该模板实现
已归档:流程模板的历史版本,在途的流程实例会流转至结束,但新建的流程实例不会基于该模板实现。

生命周期梳理

基于上面的关键设计,梳理下流程模板的生命周期

新建

新建流程模板记录,输入名称、编码,业务版本默认设置为1.0,状态默认为“未发布”,进行流程建模。在正式发布之前,可以对流程模板的基础数据和模型进行任意调整。
此时,使用上文提到的临时流程定义标识,来暂存对应的环节人员设置、流程权限配置。

发布

在流程模板列表中,对于状态为“未发布”的行记录,增加行上按钮“发布”。
点击该按钮后,调用Camunda的仓储服务repositoryService的发布方法createDeployment来正式发布。
拿到正式的流程定义标识,更新流程模板数据、环节人员设置、环节权限配置。
更新流程模板状态为“运行中”。

升级

对于一个已经发布过的流程模板,后续因为业务需求的变更,需要对模板进行调整,这时候实际进行的操作是版本升级。一般来说,版本升级会在原流程的基础上进行调整,而不是从零开始重新搭建。并且,要保障使用现有流程模板版本的在途流程实例能正常流转至结束。

基于上述考虑,平台增加行按钮“升级”,点击后,将当前流程模板数据拷贝一份,需要注意的是,环节人员设置和环节权限配置,也需要同步拷贝,并且将其流程定义标识属性,全部更换为临时的流程定义标识。

业务版本号由用户自行通过修改功能来指定,根据调整的大小,自主决定是将1.0版本更改为1.1还是2.0。
状态设置为“未发布”。

发布

对于版本升级操作产生的新流程模板,处于未发布状态,执行发布操作时,除了要完成新建流程首次发布的工作外,还有一件额外的事情,即需要将编码相同且状态为运行中的流程模板,状态更改为“已归档”。这件事情实际是在新版本流程模板发布前完成。

功能扩展

统一发布

上述发布操作存在于两个业务场景,一是新建流程模板后的首次发布;二是对于流程模板版本升级产生的新版本的流程模板进行发布。二者唯一的差别在于查找已存在的处于运行中状态的流程模板,将其归档。这两种场景从技术实现来说可以合并。无非是新建流程模板后的首次发布场景下没找到需要归档的数据,不做处理而已。

启用/停用

在一些业务场景下,如临近年底需要停止发起报销流程,或者新流程模板存在技术问题,需要暂停发现新流程,这时候需要对是否可发起流程实例进行控制,即为流程模板增加一个状态。该状态的业务含义与上面说的未发布、运行中、已归档是不同的,需要独立设置和处理。

版本“回退”

实际业务需求中,还有一种常见的情况,版本升级后,发现存在问题,需要回退上一版本;或者过了一段时间,业务需求调整,而调整后的结果跟该流程某一历史版本是一致的。这时候,我们允许选中历史版本,将其作为新建流程实例的模板,而不应该按照常规模式,进行版本升级,调整流程模型和设置人员权限等。
该功能是自行设计和实现,跟Camunda原生功能无关,为业务系统提供更好的支撑而已。

“回退”是一个比较形象的说话,但含义不一定准确,例如某个流程模板,从版本1.0升级到了1.5,回退到了1.3,然后又需要启用最新的版本1.5,这时候其实就不是“回退”,但“启用”这个词又被状态管理占用了,因此命名为“启用版本”。

选择一个已归档的版本,然后点击“启用版本”,系统将同编码的状态为运行中的模板设置为已归档,然后将该条数据状态设置为运行中。

注意:
Camunda内部实际没有状态位或其他机制来管理哪个模板处于生效运行中,但是在生成流程实例时,若选择runtimeService.startProcessInstanceByCode方法,则会自动根据编码找最新的版本;只有使用runtimeService.startProcessInstanceById方法,才会根据processDefinitionId去生成对应版本的实例。

实体设计

使用平台低代码配置功能,在工作流模块workflow下新建实体流程模板WorkflowTemplate
image.png

配置实体属性,如下:
image.png
生成库表,提示version属性重复,将其重命名为templateVersion,再次建表。
image.png
考虑到复杂流程建模生成的json数据可能超出8096的varchar,在这先手工修改为TEXT类型。
注:对于长文本,平台目前还未处理,后面会完善该功能,在可视化配置时直接可选。

配置列表视图,如下:
image.png
配置新增、修改、查看视图,以新增视图为例,如下:
image.png
生成代码,前后端分别拷贝到相应位置,编译。

配置权限项
image.png
运行
image.png

逻辑实现

发布

	@Override
    @Transactional(rollbackFor = Exception.class)
    public void publish(String id) {
    
    
        WorkflowTemplate entity = query(id);
        // 状态检查,仅未发布的模板允许执行发布操作
        if (entity.getTemplateStatus().equals(WorkflowTemplateStatusEnum.UNPUBLISHED.name()) == false) {
    
    
            throw new CustomException(WorkflowException.HAVE_PUBLISHED);
        }

        // 获取临时流程定义标识
        String tempProcessDefinitionId = this.generateTemporaryVersion(entity.getCode());

        // 生成模型及配置信息
        BpmnModelInstance modelInstance = convertJsonToModel(entity, true);
        // 发布
        String name = entity.getName();
        Deployment deployment = repositoryService.createDeployment().name(name)
                .addModelInstance(name + ".bpmn", modelInstance).deploy();
        String deploymentId = deployment.getId();


        // 获取流程定义标识
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploymentId).singleResult();
        String processDefinitionId = processDefinition.getId();


        // 更新环节设置,使用正式流程定义标识替换临时流程定义标识
        workflowNodeConfigService.updateProcessDefinitionId(processDefinitionId, tempProcessDefinitionId);
        // 更新环节权限设置,使用正式流程定义标识替换临时流程定义标识
        workflowNodePermissionConfigService.updateProcessDefinitionId(processDefinitionId, tempProcessDefinitionId);

        // 将现有运行中的模板归档
        setArchived(entity.getCode());

        // 补填流程模板中的流程定义标识
        entity.setProcessDefinitionId(processDefinitionId);
        // 更新状态
        entity.setTemplateStatus(WorkflowTemplateStatusEnum.RUNNING.name());
        super.updateById(entity);



    }


   private void setArchived(String templateCode){
    
    
        Optional<WorkflowTemplate> workflowTemplate = this.lambdaQuery().eq(WorkflowTemplate::getTemplateStatus, WorkflowTemplateStatusEnum.RUNNING.name())
                .eq(WorkflowTemplate::getCode, templateCode).oneOpt();
        if(workflowTemplate.isPresent()){
    
    
            WorkflowTemplate entity = workflowTemplate.get();
            entity.setTemplateStatus(WorkflowTemplateStatusEnum.ARCHIVED.name());
            // 此处直接调用底层更新,避免修改前检查名称是否相同(同一流程多版本,名称通常是一样的)
            super.updateById(entity);
        }

    }

升级

   @Override
    @Transactional(rollbackFor = Exception.class)
    public void upgrade(String id) {
    
    

        // 获取模板
        WorkflowTemplate originEntity = query(id);
        // 状态检查,仅运行中的模板允许执行升级操作
        if (originEntity.getTemplateStatus().equals(WorkflowTemplateStatusEnum.RUNNING.name()) == false) {
    
    
            throw new CustomException(WorkflowException.IS_NOT_RUNNING);
        }
        // 拷贝记录
        addByCopy(id, YesOrNoEnum.YES.name());

    }

   @Override
    protected void copyPropertyHandle(WorkflowTemplate entity, String... value) {
    
    
        // 重置属性
        entity.setTemplateStatus(WorkflowTemplateStatusEnum.UNPUBLISHED.name());
        entity.setProcessDefinitionId(null);
        // 版本升级时,模板版本号小段自动加1
        if(StringUtils.isNotBlank(value[0]) && value[0].equals(YesOrNoEnum.YES.name())) {
    
    
            String templateVersion = entity.getTemplateVersion();
            String[] versionArray = templateVersion.split("\\.");
            int minVersion = Integer.parseInt(versionArray[1]);
            String newVersion = versionArray[0] +"." +(minVersion + 1);
            entity.setTemplateVersion(newVersion);
        }else{
    
    
            // 复制新增时,模板版本号默认
            entity.setTemplateVersion(DEFAULT_TEMPLATE_VERSION);
        }
    }

启用版本

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void valid(String id) {
    
    

        // 获取模板
        WorkflowTemplate entity = query(id);
        // 状态检查,仅已归档的模板允许执行操作
        if (entity.getTemplateStatus().equals(WorkflowTemplateStatusEnum.ARCHIVED.name()) == false) {
    
    
            throw new CustomException(WorkflowException.IS_NOT_ARCHIVED);
        }

        // 将现有运行中的模板归档
        setArchived(entity.getCode());

        // 将当前选中置为运行状态
        entity.setTemplateStatus(WorkflowTemplateStatusEnum.RUNNING.name());
        // 此处直接调用底层更新,避免修改前检查名称是否相同(同一流程多版本,名称通常是一样的)
        super.updateById(entity);

    }

开发平台资料

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

猜你喜欢

转载自blog.csdn.net/seawaving/article/details/132321739