流程引擎

背景

流程引擎是OA系统中不可或缺的一部分,能够通过UI实现批量化流程定制,减少代码开发工作量,提高开发效率。现阶段若依官方并没有实现流程引擎,包含工作流的版本多为个人开发者维护,更新频次有待考证。研究了几个开源的带工作流拓展项目,几乎都是在主流流程引擎ActivitiFlowableCamunda的基础上进行了二次封装。以下是对次模式的优缺点分析:

优点

  1. 使用通用流程引擎框架,健壮性和稳定性经过市场检验;
  2. 有完整的生态和技术支持,功能健全。

缺点

  1. 对若依框架的兼容性太差,需要系统去兼容它,而不是它来兼容系统;
  2. 学习成本高,需要针对流程引擎api及实现逻辑有所研究;
  3. 过于庞大臃肿,有种“杀鸡用牛刀”的感觉;
  4. 针对国内流程审批习惯需要做一系列的改动和优化;
  5. 所有表单界面和数据都要内置到流程中,个性定制化难度大;
  6. 流程设计界面不美观,无法美化和自定义。

简介

  1. 尽量兼容若依框架,作为框架工具的一部分;
  2. 功能小而精,满足日常业务中使用到的流程需要即可;
  3. 只关注流程运转,不关注数据展示,与数据表为关联而不是包含关系;
  4. 即插即用,去除流程部分不影响其他功能的正常使用;
  5. 支持开发者在原有基础上的拓展和自定义;
  6. 所有的数据展示、界面交互和权限控制交由开发者自行处理,只提供调用接口;
  7. 好看且支持自定义的流程设计界面;

快速开始

  1. 创建业务表,需要额外添加审批状态字段,流程实例在状态改变时会自动更新该字段;
  2. 打开系统工具 -> 流程设计,添加新的流程定义,并关联数据表及其主键和审批字段;

image

  1. 打开系统工具 -> 代码生成,关联定义好的流程,生成代码放置到对应位置;

image

注意:生成的代比普通功能多了assign-form.vue页面,用于待办任务审批时的操作界面显示

  1. 打开系统工具 -> 流程设计,点击操作栏设计按钮,打开流程设计界面;
  2. 在流程配置中填写摘要和审批组件,点击继续

image

填写摘要时可点击置入字段按钮置入数据表字段,审批组件为步骤3中提到的assign-form.vue页面

  1. 设计流程图,通过右键节点选择追加节点、删除节点或设置节点属性;

image

  1. 流程图设计完成后点击继续,校验未通过会提示错误信息,确认无误点击一键部署完成流程部署;
  2. 将流程状态更改为已开启,测试功能并按需求调整。

流程界面

待办任务

所有流程审批过程中需要当前登录人处理的待办任务均可以在首页 -> 任务中心中看到,每条记录内容从左往右、从上往下依次是:流程图标、概要、当前节点、流程名称、提交人、任务创建时间。

image

审批界面

点击任务中心中的概要信息,打开审批组件中填写的路径对应的页面,执行流程任务审批操作。

image

审批进度

在功能界面和审批界面均可以查看当前流程实例的审批进度,左侧为审批记录,右侧为流程图及当前节点。

image

事件监听

在流程实例执行过程中,可以对流程执行流程完成流程退回任务执行任务完成任务取消六个事件进行监听,监听发生在事件成功执行后触发,事件监听在流程配置中进行设置。

image

消息通知:当事件触发后,发送站内信(系统消息)给指定用户,消息中可使用数据表字段;

注意:任务执行事件消息发送对象为任务处理人,其余事件发送对象为提交人。

执行类:当事件触发后需要执行的操作,需要注解@ComponentSpring Bean并实现com.ruoyi.project.workflow.listener.IEventListener接口的onEvent()方法。

/**
 * 监听类demo
 */
@Commponent
public class OrderFinishListener implements IEventListener
{
    @Override
    public void onEvent(RequestData requestData)
    {
        System.out.println("订单完成事件:" + requestData.getDataId());
    }
}

接口调用

常用方法

流程引擎暴露的接口为com.ruoyi.project.workflow.engine.IFlowEngine,提供了常见的流程操作函数和默认实现,只需要实现接口并重写getProcessId()函数,指定流程id即可直接调用。

/**
 * 所有实现类均需要实现该方法,指定流程id
 */
Long getProcessId();

/**
 * 提交
 *
 * @param requestData 请求数据
 * @return 执行结果
 */
boolean audit(RequestData requestData);

/**
 * 同意
 *
 * @param requestData 请求数据
 * @return 执行结果
 */
boolean agree(RequestData requestData);

/**
 * 不同意
 *
 * @param requestData 请求数据
 * @return 执行结果
 */
boolean disagree(RequestData requestData);

/**
 * 退回编辑人
 *
 * @param requestData 请求数据
 * @return 执行结果
 */
boolean disall(RequestData requestData);

/**
 * 数据是否禁止操作
 *
 * @param dataId 数据id
 * @return 是否禁用
 */
boolean disabled(Long dataId);

/**
 * 获取审批进度数据
 *
 * @param dataId 数据id
 * @return 进度数据
 */
ProgressData progress(Long dataId);

请求数据

public class RequestData
{
    // 数据id,不可为空
    private final Long dataId;
    // 批注/评论
    private final String comment;
    // 表单数据
    private final Object data;
    // 任务id
    private final Long taskId;
    // 任务分配id
    private final Long assignId;
    // 其他参数
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private final JSONObject params;

    // 获取指定类型的表单数据
    public <T> T getData(Class<T> clazz);
}

环绕函数

auditagreedisagreedisall接口函数在调用前后,通常需要一些额外的操作,如保存审批时的数据更改、发送复杂消息、生成额外数据等......
因此,com.ruoyi.project.workflow.engine.IFlowEngine还提供了以上方法的环绕函数重载。

/**
 * 函数入参重载,fun代表以上四个函数
 *
 * @param requestData 请求数据
 * @param beforeFun   前置函数
 * @param afterFun    后置函数
 * @return 执行结果
 */
boolean fun(RequestData requestData, FlowFunction beforeFun, FlowFunction afterFun);

前置/后置函数需要实现函数式接口com.ruoyi.project.workflow.function.FlowFunction,以下为执行agree前保存表单更改的demo。

/**
     * 同意订单管理
     */
@Log(title = "订单管理", businessType = BusinessType.AGREE)
@PostMapping("/agree")
public AjaxResult agree(@RequestBody RequestData requestData)
{
	// 前后置函数为函数式接口,可直接使用lambda表达式,无需定义类
	return toAjax(testOrderService.agree(requestData,
		() -> testOrderService.save(requestData.getData(TestOrder.class)),
		null));
}

普通功能改造

针对于已经实现并调整优化的功能,重新用代码生成器生成代码会覆盖原有自定义样式和逻辑,因此需要在现有功能基础上拓展流程引擎能力,以下为改造步骤:

  1. 数据表和实体类添加流程审批状态字段;
  2. 参照快速开始中的步骤定义和设计流程;
  3. IService接口继承com.ruoyi.project.workflow.engine.IFlowEngine
/**
 * 订单管理Service接口
 *
 * @author orange
 * @date 2024-04-29
 */
public interface ITestOrderService extends IFlowEngine {}
  1. ServiceImpl中实现getProcessId()方法,返回结果为设计好的流程id,可直接点击流程id复制文本;
@Service
public class TestOrderServiceImpl implements ITestOrderService
{
    @Override
    public Long getProcessId()
    {
        return 541607657201733L;
    }
}
  1. Controller中增加6个api,分别为提交、同意、退回、退回编辑人、表单数据、审批进度;
/**
 * 提交订单管理
 */
@PreAuthorize("@ss.hasPermi('test:order:audit')")
@Log(title = "订单管理", businessType = BusinessType.AUDIT)
@PostMapping("/audit")
public AjaxResult audit(@RequestBody RequestData requestData)
{
	return toAjax(testOrderService.audit(requestData));
}

/**
 * 同意订单管理
 */
@Log(title = "订单管理", businessType = BusinessType.AGREE)
@PostMapping("/agree")
public AjaxResult agree(@RequestBody RequestData requestData)
{
	return toAjax(testOrderService.agree(requestData));
}

/**
 * 退回订单管理
 */
@Log(title = "订单管理", businessType = BusinessType.DISAGREE)
@PostMapping("/disagree")
public AjaxResult disagree(@RequestBody RequestData requestData)
{
	return toAjax(testOrderService.disagree(requestData));
}

/**
 * 退回编辑人订单管理
 */
@Log(title = "订单管理", businessType = BusinessType.DISALL)
@PostMapping("/disall")
public AjaxResult disall(@RequestBody RequestData requestData)
{
	return toAjax(testOrderService.disall(requestData));
}

/**
 * 获取订单管理表单数据
 */
@GetMapping(value = "/form/{orderId}")
public AjaxResult form(@PathVariable("orderId") Long orderId)
{
	return success(testOrderService.getById(orderId));
}

/**
 * 获取订单管理审批进度
 */
@GetMapping(value = "/progress/{orderId}")
public AjaxResult progress(@PathVariable("orderId") Long orderId)
{
	return success(testOrderService.progress(orderId));
}
  1. 增加对审批中数据的更改和删除操作限制;

注意:通过IFlowEnginedisabled方法可以判断当前数据是否禁止操作,限制有两种实现思路:
(1) 查询数据详细信息时将禁用标志传到前端,由前端控制元素状态和操作判断;
(2) 后台直接在修改和删除接口中校验;

  1. 前端api.js文件中添加以上六个接口;
// 提交订单管理
export function auditOrder(data) {
  return request({
    url: '/test/order/audit',
    method: 'post',
    data: data
  })
}

// 同意订单管理
export function agreeOrder(data) {
  return request({
    url: '/test/order/agree',
    method: 'post',
    data: data
  })
}

// 退回订单管理
export function disagreeOrder(data) {
  return request({
    url: '/test/order/disagree',
    method: 'post',
    data: data
  })
}

// 退回编辑人订单管理
export function disallOrder(data) {
  return request({
    url: '/test/order/disall',
    method: 'post',
    data: data
  })
}

// 查询订单管理表单数据
export function formOrder(orderId) {
  return request({
    url: '/test/order/form/' + orderId,
    method: 'get'
  })
}

// 查询订单管理审批进度
export function progressOrder(orderId) {
  return request({
    url: '/test/order/progress/' + orderId,
    method: 'get'
  })
}
  1. 前端页面中添加提交、审批进度按钮及对应的点击事件;
/** 提交按钮操作 */
handleAudit(row) {
  const orderId = row.orderId || this.ids[0];
  this.$modal.confirm('是否确认提交订单管理编号为"' + orderId + '"的数据项?').then(() => {
    return auditOrder({dataId: orderId});
  }).then(() => {
    this.$modal.msgSuccess("提交成功");
    this.getList();
  }).catch(() => {
  });
},
/** 审批进度按钮操作 */
handleProgress(row) {
  const orderId = row.orderId || this.ids[0];
  progressOrder(orderId).then(response => {
    this.$refs.progressDialog.openDialog(response.data);
  });
},
  1. 使用代码生成器生成审批页面assignForm并配置审批组件路径;
  2. 将流程状态更改为已开启,测试功能并按需求调整。

自定义策略

自定义策略主要包含分配策略和完成策略,需要实现对应的策略接口,并注解@Component@StrategyAnno@StrategyAnno由两个属性,value不可与已有的值重复,否则项目启动会报错。使用注解后,任务节点属性界面会自动显示该选项。

已有分配策略:0、按用户分配;1、按角色分配;9、自定义sql;
已有完成策略:0、或签;1、会签;

/**
 * 任务分配策略-自定义分配
 */
@Component
@StrategyAnno(value = 5, name = "自定义分配")
public class TaskAssignCustomStrategy implements TaskAssignStrategy
{

    /**
     * 执行策略
     *
     * @param context     上下文
     * @param requestData 请求数据
     * @return 用户ID列表
     */
    @Override
    public Set<Long> execute(FlowContext context, RequestData requestData)
    {
        // ......
    }
}

/**
 * 任务完成策略-自定义完成
 */
@Component
@StrategyAnno(value = 5, name = "自定义完成")
public class TaskFinishCustomStrategy implements TaskFinishStrategy
{

    /**
     * 执行策略
     *
     * @param context     上下文
     * @param requestData 请求数据
     * @return 是否完成
     */
    @Override
    public boolean execute(FlowContext context, RequestData requestData)
    {
        // ......
    }
}
posted @ 2024-05-06 19:01  暴走的小橘子  阅读(566)  评论(0)    收藏  举报