流程引擎
背景
流程引擎是OA系统中不可或缺的一部分,能够通过UI实现批量化流程定制,减少代码开发工作量,提高开发效率。现阶段若依官方并没有实现流程引擎,包含工作流的版本多为个人开发者维护,更新频次有待考证。研究了几个开源的带工作流拓展项目,几乎都是在主流流程引擎Activiti、Flowable、Camunda的基础上进行了二次封装。以下是对次模式的优缺点分析:
优点:
- 使用通用流程引擎框架,健壮性和稳定性经过市场检验;
- 有完整的生态和技术支持,功能健全。
缺点:
- 对若依框架的兼容性太差,需要系统去兼容它,而不是它来兼容系统;
- 学习成本高,需要针对流程引擎
api及实现逻辑有所研究; - 过于庞大臃肿,有种“杀鸡用牛刀”的感觉;
- 针对国内流程审批习惯需要做一系列的改动和优化;
- 所有表单界面和数据都要内置到流程中,个性定制化难度大;
- 流程设计界面不美观,无法美化和自定义。
简介
- 尽量兼容若依框架,作为框架工具的一部分;
- 功能小而精,满足日常业务中使用到的流程需要即可;
- 只关注流程运转,不关注数据展示,与数据表为关联而不是包含关系;
- 即插即用,去除流程部分不影响其他功能的正常使用;
- 支持开发者在原有基础上的拓展和自定义;
- 所有的数据展示、界面交互和权限控制交由开发者自行处理,只提供调用接口;
- 好看且支持自定义的流程设计界面;
快速开始
- 创建业务表,需要额外添加
审批状态字段,流程实例在状态改变时会自动更新该字段; - 打开
系统工具->流程设计,添加新的流程定义,并关联数据表及其主键和审批字段;

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

注意:生成的代比普通功能多了
assign-form.vue页面,用于待办任务审批时的操作界面显示
- 打开
系统工具->流程设计,点击操作栏设计按钮,打开流程设计界面; - 在流程配置中填写摘要和审批组件,点击
继续;

填写摘要时可点击
置入字段按钮置入数据表字段,审批组件为步骤3中提到的assign-form.vue页面
- 设计流程图,通过右键节点选择追加节点、删除节点或设置节点属性;

- 流程图设计完成后点击
继续,校验未通过会提示错误信息,确认无误点击一键部署完成流程部署; - 将流程状态更改为
已开启,测试功能并按需求调整。
流程界面
待办任务
所有流程审批过程中需要当前登录人处理的待办任务均可以在首页 -> 任务中心中看到,每条记录内容从左往右、从上往下依次是:流程图标、概要、当前节点、流程名称、提交人、任务创建时间。

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

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

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

消息通知:当事件触发后,发送站内信(系统消息)给指定用户,消息中可使用数据表字段;
注意:
任务执行事件消息发送对象为任务处理人,其余事件发送对象为提交人。
执行类:当事件触发后需要执行的操作,需要注解@Component为Spring 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);
}
环绕函数
audit、agree、disagree、disall接口函数在调用前后,通常需要一些额外的操作,如保存审批时的数据更改、发送复杂消息、生成额外数据等......
因此,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));
}
普通功能改造
针对于已经实现并调整优化的功能,重新用代码生成器生成代码会覆盖原有自定义样式和逻辑,因此需要在现有功能基础上拓展流程引擎能力,以下为改造步骤:
- 数据表和实体类添加流程
审批状态字段; - 参照快速开始中的步骤定义和设计流程;
IService接口继承com.ruoyi.project.workflow.engine.IFlowEngine;
/**
* 订单管理Service接口
*
* @author orange
* @date 2024-04-29
*/
public interface ITestOrderService extends IFlowEngine {}
- 在
ServiceImpl中实现getProcessId()方法,返回结果为设计好的流程id,可直接点击流程id复制文本;
@Service
public class TestOrderServiceImpl implements ITestOrderService
{
@Override
public Long getProcessId()
{
return 541607657201733L;
}
}
- 在
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));
}
- 增加对审批中数据的更改和删除操作限制;
注意:通过
IFlowEngine的disabled方法可以判断当前数据是否禁止操作,限制有两种实现思路:
(1) 查询数据详细信息时将禁用标志传到前端,由前端控制元素状态和操作判断;
(2) 后台直接在修改和删除接口中校验;
- 前端
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'
})
}
- 前端页面中添加提交、审批进度按钮及对应的点击事件;
/** 提交按钮操作 */
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);
});
},
- 使用代码生成器生成审批页面
assignForm并配置审批组件路径; - 将流程状态更改为
已开启,测试功能并按需求调整。
自定义策略
自定义策略主要包含分配策略和完成策略,需要实现对应的策略接口,并注解@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)
{
// ......
}
}

浙公网安备 33010602011771号