Flowable7.x学习笔记(十二)流程激活与挂起

前言

        在 Flowable 中,挂起(suspend)与激活(activate)是通过 RepositoryService 或 REST API 对流程定义(Process Definition)和流程实例(Process Instance)执行生命周期管理的关键功能,挂起时将流程置于暂停状态,防止任何新的实例启动或现有实例中的任务、定时器及异步作业执行​;激活则是相反操作,可将已挂起的流程恢复到可执行状态,重新允许流程操作与任务完成​。

一、挂起(Suspend)的用途与影响

1.1 用途场景

  • 维护与升级:在对流程定义或底层数据模型进行升级、维护时,可先挂起相关流程,以防止新实例启动及现有实例继续执行造成数据不一致​。

  • 资源管理:当系统资源紧张或高并发测试时,挂起非关键流程实例可释放 CPU/内存用于更重要的任务​。

  • 业务合规:在合规审计或外部审批待决场景下,需暂停流程执行直至审核结束,保障审计过程的中断一致性。

1.2 技术影响

  • 新实例禁用:对于挂起的流程定义,不允许再启动新的流程实例,尝试启动将抛出异常或返回错误​。

  • 暂停现有任务:当挂起流程实例时,其下所有活动的用户任务会自动置为挂起状态,完成操作时会抛出异常,保障任务状态不被误操作。

  • 定时/异步作业停调度:所有与该定义或实例关联的定时器(Timer Jobs)及异步执行作业(Async Jobs)将被移入 ACT_RU_SUSPENDED_JOB(或相应表)中,不再被作业执行器(Job Executor)获取与执行。

  • 数据库标记:Flowable 会在 ACT_RE_PROCDEF(流程定义表)或 ACT_RU_EXECUTION(运行时执行表)的 SUSPENSION_STATE_ 字段中设置值 2,代表挂起状态​。

二、激活(Activate)的用途与影响

2.1 恢复执行

  • 重新启用新启动:对已激活的流程定义,恢复允许使用 RuntimeService.startProcessInstanceByKey() 等 API 启动新实例​。

  • 唤醒挂起任务:激活流程实例后,之前被挂起的用户任务、脚本任务等将恢复为可执行状态,允许流程继续推进。

  • 重新调度作业:所有原先移入挂起表的定时器和异步作业将重新置为可运行状态,恢复正常调度与执行​。

2.2 技术影响

影响类型挂起状态激活状态
启动新实例禁止,通过 API 或 RuntimeService 抛异常允许,正常启动
用户任务所有活动用户任务变为挂起,调用 complete() 抛异常恢复为可完成状态,可调用 complete()
定时器/异步作业移入 ACT_RU_SUSPENDED_JOB 表,不再调度恢复至正常作业表,Job Executor 可获取执行
历史查询历史服务(HistoryService)仍可查询,但无新变更恢复后历史数据继续记录

三、后端:完成激活和挂机功能

① 创建操作类型枚举值

这里的枚举值是用来表明当前流程的状态的,和数据库表中的数据对应;1是激活;2是挂起。

package com.ceair.enums;

import lombok.Getter;

/**
 * @author wangbaohai
 * @ClassName SuspensionState
 * @description: 激活/挂起状态枚举
 * @date 2025年04月21日
 * @version: 1.0.0
 */
@Getter
public enum SuspensionState {

    /**
     * 1:激活
     */
    ACTIVE(1, "激活"),

    /**
     * 2:挂起
     */
    SUSPENDED(2, "挂起");

    private final Integer code;

    private final String description;

    SuspensionState(Integer code, String description) {
        this.code = code;
        this.description = description;
    }

}

② 创建操作参数

参数用来指明需要对具体哪个定义执行操作,同时指定需要做的是激活还是挂起操作。

package com.ceair.entity.request;

import lombok.Data;

import java.io.Serial;
import java.io.Serializable;

/**
 * @author wangbaohai
 * @ClassName OperateActReProcdefReq
 * @description: 激活/挂起流程定义请求对象
 * @date 2025年04月21日
 * @version: 1.0.0
 */
@Data
public class OperateActReProcdefReq implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    /**
     * 流程定义ID
     */
    private String processDefinitionId;

    /**
     * 操作类型 1:激活 2:挂起
     */
    private Integer operateType;

}

③ 创建服务

/**
 * 操作流程定义的函数,根据传入的请求对象执行相应的操作。
 *
 * @param operateActReProcdefReq 包含操作流程定义所需信息的请求对象。
 *                               该对象通常包含流程定义的ID、操作类型(如启动、挂起、删除等)
 *                               以及其他与操作相关的参数。
 * @return Boolean 返回操作结果。如果操作成功,则返回 true;否则返回 false。
 * 返回值可以用于判断操作是否按预期完成。
 */
Boolean operateProcessDefinitionById(OperateActReProcdefReq operateActReProcdefReq);

④ 创建服务实现

具体是的实现我们需要使用Flowable的原生API,激活的API是【activateProcessDefinitionById】,第一个参数就是主键ID,第二个参数代表是否需要一起激活关联的实例,第三个参数是指定激活的时间,一般时间参数不指定,让生效动作立刻触发;挂起的API是【suspendProcessDefinitionById】,参数与激活API相同。

/**
 * 激活或挂起指定的流程定义。
 * <p>
 * 该方法根据请求对象中的流程定义ID和操作类型,执行激活或挂起流程定义的操作。
 * 支持级联操作相关流程实例,并可设置操作时间(立即或指定时间)。
 * <p>
 * 参数说明:
 * - operateActReProcdefReq: 请求对象,包含流程定义ID和操作类型。
 * 如果为 null 或其字段不符合要求,则会抛出异常。
 * <p>
 * 返回值:
 * - Boolean: 操作成功时返回 true。如果发生异常,则不会返回值,而是抛出 BusinessException。
 * <p>
 * 异常说明:
 * - BusinessException: 当参数校验失败、操作类型非法或发生其他异常时抛出。
 */
@Override
public Boolean operateProcessDefinitionById(OperateActReProcdefReq operateActReProcdefReq) {
    try {
        // 参数校验
        if (operateActReProcdefReq == null) {
            log.error("激活或者挂起流程失败,原因:请求对象不能为空");
            throw new BusinessException("激活或者挂起流程失败,原因:请求对象不能为空");
        }

        String processDefinitionId = operateActReProcdefReq.getProcessDefinitionId();
        Integer operateType = operateActReProcdefReq.getOperateType();

        if (processDefinitionId == null || processDefinitionId.trim().isEmpty()) {
            log.error("激活或者挂起流程失败,原因:流程定义ID不能为空或空字符串");
            throw new BusinessException("激活或者挂起流程失败,原因:流程定义ID不能为空或空字符串");
        }

        if (operateType == null) {
            log.error("激活或者挂起流程失败,原因:操作类型不能为空");
            throw new BusinessException("激活或者挂起流程失败,原因:操作类型不能为空");
        }

        // 操作类型校验
        if (!Objects.equals(SuspensionState.ACTIVE.getCode(), operateType)
                && !Objects.equals(SuspensionState.SUSPENDED.getCode(), operateType)) {
            log.error("激活或者挂起流程失败,原因:操作类型错误,非法值为:{}", operateType);
            throw new BusinessException("激活或者挂起流程失败,原因:操作类型错误");
        }

        // 根据操作类型执行相应逻辑
        if (Objects.equals(SuspensionState.ACTIVE.getCode(), operateType)) {
            log.info("尝试激活流程定义,流程定义ID:{}", processDefinitionId);
            /*
             * 激活指定的流程定义。
             *
             * 该方法通过调用 repositoryService 的 activateProcessDefinitionById 方法,
             * 激活与给定流程定义 ID 对应的流程定义。支持级联激活相关实例,并可设置激活时间。
             *
             * 参数说明:
             * - operateActReProcdefReq.getProcessDefinitionId(): 流程定义的唯一标识符,用于定位需要激活的流程定义。
             *                                                    如果为空,则无法定位目标流程定义,可能导致异常。
             * - true: 表示是否级联激活相关流程实例。如果为 true,则所有关联的流程实例也会被激活;
             *         如果为 false,则仅激活流程定义本身。
             * - null: 表示激活时间。如果为 null,则立即激活;如果提供具体时间,则会在指定时间激活。
             */
            repositoryService.activateProcessDefinitionById(processDefinitionId,
                    true, null);
        } else if (Objects.equals(SuspensionState.SUSPENDED.getCode(), operateType)) {
            log.info("尝试挂起流程定义,流程定义ID:{}", processDefinitionId);
            /*
             * 暂停指定的流程定义。
             *
             * 该方法通过调用 repositoryService 的 suspendProcessDefinitionById 方法,
             * 暂停与给定流程定义 ID 对应的流程定义。支持级联暂停相关实例,并可设置暂停时间。
             *
             * 参数说明:
             * - operateActReProcdefReq.getProcessDefinitionId(): 流程定义的唯一标识符,用于定位需要暂停的流程定义。
             *                                                    如果为空,则无法定位目标流程定义,可能导致异常。
             * - true: 表示是否级联暂停相关流程实例。如果为 true,则所有关联的流程实例也会被暂停;
             *         如果为 false,则仅暂停流程定义本身。
             * - null: 表示暂停时间。如果为 null,则立即暂停;如果提供具体时间,则会在指定时间暂停。
             */
            repositoryService.suspendProcessDefinitionById(processDefinitionId,
                    true, null);
        }
    } catch (IllegalArgumentException e) {
        if (operateActReProcdefReq != null) {
            log.error("激活或者挂起流程失败,原因:非法参数,流程定义ID:{}, 操作类型:{}",
                    operateActReProcdefReq.getProcessDefinitionId(), operateActReProcdefReq.getOperateType(), e);
        }
        throw new BusinessException("激活或者挂起流程失败,原因:非法参数", e);
    } catch (Exception e) {
        if (operateActReProcdefReq != null) {
            log.error("激活或者挂起流程失败,原因:未知异常,流程定义ID:{}, 操作类型:{}",
                    operateActReProcdefReq.getProcessDefinitionId(), operateActReProcdefReq.getOperateType(), e);
        }
        throw new BusinessException("激活或者挂起流程失败,原因:未知异常", e);
    }

    return true;
}

⑤ 创建接口

/**
 * 操作流程定义(激活/挂起)。
 * <p>
 * 该方法通过调用服务层的 `operateProcessDefinitionById` 方法,根据请求对象中的信息对流程定义进行操作(如激活或挂起)。
 * 如果操作成功,则返回成功的响应结果;如果发生异常,则记录错误日志并返回失败的响应结果。
 *
 * @param operateActReProcdefReq 流程定义操作请求对象,包含操作所需的参数信息,不能为空。
 *                               该对象通过 HTTP 请求体传递,需符合 `OperateActReProcdefReq` 的结构定义。
 * @return 返回一个 `Result<Boolean>` 对象:
 * - 如果操作成功,返回 `Result.success(true)`;
 * - 如果操作失败,返回 `Result.error`,并包含失败原因的描述信息。
 */
@PreAuthorize("hasAnyAuthority('/api/v1/actReProcdef/operate')")
@Parameter(name = "operateActReProcdefReq", description = "流程定义操作请求对象", required = true)
@Operation(summary = "操作流程定义(激活/挂起)")
@PostMapping("/operate")
public Result<Boolean> operateProcessDefinitionById(@RequestBody OperateActReProcdefReq operateActReProcdefReq) {
    try {
        // 调用服务层方法执行流程定义操作,传入请求对象并获取操作结果
        Boolean operateResult = actReProcdefService.operateProcessDefinitionById(operateActReProcdefReq);

        // 返回操作成功的响应结果
        return Result.success(operateResult);
    } catch (Exception e) {
        // 捕获异常,记录详细的错误日志,并返回包含失败原因的响应结果
        log.error("操作流程定义失败 具体原因为 : {}", e.getMessage());
        return Result.error("操作流程定义失败,失败原因:" + e.getMessage());
    }
}

二、前端:使用API

① 创建参数

与后端接口参数保持一致

// 激活/挂起流程定义请求对象
export interface OperateActReProcdefReq {
  processDefinitionId: string // 流程定义ID
  operateType: number // 操作类型 1:激活 2:挂起
}

② 创建API

// 激活/挂起流程定义
export function operateActReProcdef(data: OperateActReProcdefReq) {
  return request.post<any>({
    url: '/pm-process/api/v1/actReProcdef/operate',
    data,
  })
}

③ 修改 表格数据 区域增加操作列

操作列里增加激活/挂起按钮,按钮的名称需要根据当前流程定义的状态动态修改按钮名称

<!-- 表格数据 区域 -->
<el-table-column
  v-for="(column, index) in tableColumns"
  :key="index"
  :type="column.type"
  :label="column.label"
  :prop="column.prop"
  :align="column.align"
  :width="column.width"
>
  <!-- 使用单个 template 包裹所有条件 -->
  <template #default="scope">
    <!-- 翻译 suspensionState 状态 1 为 激活,2 为 挂起 -->
    <template v-if="column.label === '定义状态'">
      <template v-if="scope.row.suspensionState === 1">
        激活
      </template>
      <template v-else>
        挂起
      </template>
    </template>
    <!-- 判断是否是操作列 -->
    <div v-if="column.label === '操作'">
      <el-button v-hasButton="`btn.actReProcdef.operate`" type="primary" @click="onOperate(scope.row)">
        {{ scope.row.suspensionState === 1 ? '挂起' : '激活' }}
      </el-button>
    </div>
  </template>
</el-table-column>

④ 创建按钮方法

/**
 * 异步函数:onOperate
 *
 * 该函数用于对流程定义进行操作(如挂起或激活),并根据操作结果更新页面状态。
 *
 * @param {ActReProcdefVO} row - 流程定义对象,包含流程定义的详细信息,例如 ID 和挂起状态。
 *                                - `id`: 流程定义的唯一标识符。
 *                                - `suspensionState`: 流程定义的挂起状态,1 表示激活,2 表示挂起。
 * @returns {Promise<void>} - 无返回值,函数执行完成后会根据操作结果更新页面状态或显示提示信息。
 */
async function onOperate(row: ActReProcdefVO) {
  try {
    // 组装操作参数,包括流程定义 ID 和操作类型
    const param: OperateActReProcdefReq = {
      processDefinitionId: row.id,
      operateType: row.suspensionState === 1 ? 2 : 1,
    }

    // 调用后端接口进行流程定义的操作(挂起或激活)
    const result: any = await operateActReProcdef(param)

    // 判断操作结果是否成功
    if (result.success && result.code === 200) {
      // 操作成功时,显示成功提示信息
      ElMessage({
        message: '操作成功',
        type: 'success',
      })

      // 初始化分页参数,设置当前页为第一页,每页显示 10 条数据
      currentPage.value = 1
      pageSize.value = 10

      // 初始化流程名称为空字符串
      processName.value = ''

      // 刷新页面数据以反映最新的操作结果
      getActReProcdefPageData()
    }
  }
  catch (error) {
    // 捕获异常并提取错误信息
    let errorMessage = '未知错误'
    if (error instanceof Error) {
      errorMessage = error.message
    }

    // 显示操作失败的错误提示信息
    ElMessage({
      message: `操作失败: ${errorMessage || '未知错误'}`,
      type: 'error',
    })
  }
}

三、配置权限

① 增加按钮

② 分配权限

四、界面操作验证

① 挂起

② 激活

后记

本篇文章的前后端仓库地址请查询专栏第一篇文章,后续打算把xml和流程图片展示出来

本文的后端分支是 process-6

本文的前端分支是 process-8

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值