详情见git:https://gitee.com/silverconx/newfiber-workflow-release
历史版本
版本 | 日期 | 备注 |
---|---|---|
1.0.0 | 2021/08/20 | 初版 |
1.1.0 | 2021/08/31 | 「会签」「消息通知」「功能优化」 |
1.2.0 | 2022/02/28 | 「框架由Activiti6.0.0变更为Flowable6.0.1」 「加入流程图自定义颜色功能」 「加入会签一票否决」 「功能优化」 |
1.3.0 | 2023/02/22 | 「框架由Flowable6.0.1变更为Flowable6.8.0」 「集成到通用产品框架」 「加入数据库分库功能」 「MySQL触发器改为视图」 |
1.4.0 | 2023/03/17 | 「添加附件上传及展示功能」 |
1.5.0 | 2023/11/13 | 「集成国产数据库」 开源 |
概述
工作流,从业务角度看,就是审核流程,其表现就是控制不同的角色看到并执行不同的数据;不同的操作使得数据的流向不同,一般情况下会有申请,审核等步骤;
从技术实现的角度看,就是通过控制一个业务表数据的状态字段,用户在执行过程中实现数据的状态变更。
模块特性
1)基于开源Flowable 6.8.0工作流框架。
2)基于BPMN图形拖拽方式定义工作流程。
3)支持在流程图中静态指定各个节点任务的执行人/角色。
4)支持在流程中动态指定各个节点任务的执行人/角色。
5)支持会签、邮件/短信通知功能。
6)代码侵入性低,集成方便。
7)集成国产达梦、人大金仓数据库。
模块架构
官方提供的Flowable框架提供了工作流引擎,包括流程、实例、任务等核心功能,已经能够满足业务需求。但是对于日常开发而言还不够友好,还需要解决几个问题:
1、由于Flowable框架和业务系统解耦,任务的代办/已办状态在工作流引擎里面,业务系统无法直接感知代办/已办状态,需要添加额外的操作才能在业务数据里面保存状态信息。
2、Flowable官网框架提供的API和业务系统不贴合,编码过程中会产生很多无用代码。如果做一层代码封装用来适应业务,并且配合代码生成器,可以减少80%的手动编码。
为了解决这些问题,在基础框架之上开发了工作流SDK工具包newfiber-common-workflow。工具包使用回调方式更新业务数据状态,并且构建了一套核心类,实现了一键启动、提交、查询工作流等功能。业务系统引入工具包后,配合核心类,可以在最少编码的情况下实现工作流。
业务时序
核心类设计
技术选型
1、系统环境
- Java EE 8
- Servlet 3.0
- Apache Maven 3
- Redis > 3
- MySQL > 5.7
- Node >= 12
2、主框架
- Spring Boot 2.3.x
- Spring Cloud Hoxton.SR9
- Spring Framework 5.2.x
- Spring Security 5.2.x
3、持久层
- Apache MyBatis 3.5.x
- Baomidou MyBatis Plus 3.5.x
- Hibernate Validation 6.0.x
- Alibaba Druid 1.2.x
对接流程
概述
在业务模块对接过程中,主要包括前端和后端。前端主要用于BPMN流程图的绘制,后端主要用于实现业务逻辑。
1)前端:前端开发人员将前端界面集成到业务模块后,绘制BPMN流程图。在极端情况下,如果业务系统无法集成前端界面,也可以通过后端接口直接导入BPMN文件,BPMN文件可以由第三方工具绘制后导出。
2)后端:后端完成业务表结构设计后,在MySQL中执行视图,在代码中引入工作流模块依赖,并依照文档「后端代码对接」开始编码。
BPMN流程图
BPMN全称业务流程建模与标注,通过一系列的节点符号来定义一个流程图,包括开始事件、可执行节点、节点可执行的动作、结束事件等。工作流模块提供了前端页面来绘制BPMN流程图,通过拖拽节点符号,连接各个节点来绘制。如下图所示:其中定义了一个管网巡查申请流程。
BPMN中主要涉及到的符号包括:开始事件、人工任务、单一网关、结束事件。在绘制BPMN流程图中有相应的规范,接下来一一进行讲解。
流程图编辑器
支持通用BPMN流程图编辑器。可以在Flowable Git上下载对应版本的War包,启动后可访问流程编辑器:
Release Flowable 6.0.1 release · flowable/flowable-engine · GitHub
除了使用官网的War包启动设计器之外,也可以使用newfiber-workflow-design,启动后也是流程设计器。
BPMN定义
点击BPMN编辑器的空白处,在下方的编辑栏编辑BPMN定义的相关信息。重点关注:
1)流程唯一标识:对应后端代码中的IWorkflowDefinition.WorkflowKey
2)流程名称:对应后端代码中的IWorkflowDefinition.WorkflowName
开始事件
开始事件用来定义一个流程的开始,任何流程都应包含该事件。按钮位置在【开始事件】–>【开始事件】。重点关注:
1)ID:节点唯一编号,对应业务数据中的status字段。
2)名称:节点名称。
人工任务
人工任务也就是在流程中需要人工来执行、审核的任务,例如需要用户/角色来审核。按钮位置在【活动】–>【人工任务】。重点关注:
1)ID:节点唯一编号,对应业务数据中的status字段。
2)名称:节点名称。
3)任务派遣:指派任务到指定的执行人/角色,即只有被指派的人/角色才有任务该节点下的数据执行权限。
在人工任务中包含了数据执行权限,现包括以下两种方式指定:
1)在流程图中静态指定执行人/角色。即指定固定的人/角色,任务只能由指定的人/角色执行,其他人/角色无权操作。
点击【任务派遣】,在弹出的页面中选择【Identity store(身份仓库)】,在【assignment(指派)】中包括三个选项:
● Assigned to single user(指派给单个用户):将任务执行权限指派给单个的用户。
● Candidate users(候选用户):将任务执行权限指派给一个或多个候选用户,这些用户都有可执行权限,其中任何一个用户执行则算该数据执行完成。
● Candidate groups(候选组/角色):将任务执行权限指派给一个或多个候选角色,这些角色下的用户都有可执行权限,其中任何一个用户执行则算该数据执行完成。
2)在流程中动态指定执行人/角色。即在流程流转过程中动态指定,可以通过上一个节点执行后来指定下一个节点的执行人/角色。
点击【任务派遣】,在弹出的页面中选择【Fixed value(灵活变量)】,其中包括三个输入框,输入框用于声明执行人/角色的变量,然后在程序中动态指定。注:三个输入框互斥,同时只能指定一个。
● Assignee(指派给单个用户):其值固定为 ∗ a p p r o v e U s e r I d ∗ 。在流程启动或者提交时代码中动态赋值变量来指定,对应后端代码中的 ∗ W o r k f l o w S t a r t R e q . n e x t T a s k A p p r o v e U s e r I d ∗ 和 ∗ W o r k f l o w S u b m i t R e q . n e x t T a s k A p p r o v e U s e r I d ∗ 。注: {*approveUserId*}。在流程启动或者提交时代码中动态赋值变量来指定,对应后端代码中的*WorkflowStartReq.nextTaskApproveUserId*和*WorkflowSubmitReq.nextTaskApproveUserId*。注: ∗approveUserId∗。在流程启动或者提交时代码中动态赋值变量来指定,对应后端代码中的∗WorkflowStartReq.nextTaskApproveUserId∗和∗WorkflowSubmitReq.nextTaskApproveUserId∗。注:{approveUserId}不能改变,否则后端代码无法识别。
● Candidate users(候选用户),暂未开放,后续看业务模块是否需要。
● Candidate groups(候选组/角色):其值固定为 ∗ a p p r o v e R o l e I d ∗ ,在流程启动或者提交时代码中动态赋值变量来指定,对应后端代码中的 ∗ W o r k f l o w S t a r t R e q . n e x t T a s k A p p r o v e R o l e I d ∗ 和 ∗ W o r k f l o w S u b m i t R e q . n e x t T a s k A p p r o v e R o l e I d ∗ 。注: {*approveRoleId*},在流程启动或者提交时代码中动态赋值变量来指定,对应后端代码中的*WorkflowStartReq.nextTaskApproveRoleId*和*WorkflowSubmitReq.nextTaskApproveRoleId*。注: ∗approveRoleId∗,在流程启动或者提交时代码中动态赋值变量来指定,对应后端代码中的∗WorkflowStartReq.nextTaskApproveRoleId∗和∗WorkflowSubmitReq.nextTaskApproveRoleId∗。注:{approveRoleId}不能改变,否则后端代码无法识别。
单一网关
在用户执行任务时,会有不同的动作,比如通过、不通过等,不同的动作会指向不同的结果。BPMN提供了网关来实现这一功能,我们一般用到的是单一网关(有的也叫互斥网关)。按钮位置在【网关】–>【单一网关】。重点关注:
1)网关流出的节点。详情见下方的【顺序流】。
结束事件
结束事件用来定义一个流程的结束,任何流程都应包含该事件。按钮位置在【结束事件】–>【结束事件】。重点关注:
1)ID:节点唯一编号,其值需固定配置为「end」,对应业务数据中的status字段。
2)名称:节点名称。
3)执行监听器:用于监听结束事件的完成。需将其配置为com.newfiber.workflow.support.listener.WorkflowEndListener
顺序流
在BPMN流程图中,除【结束事件】,其他所有节点都会指向下一个节点,其指向的方向用顺序流表示,也就是一条实线实心箭头。每条顺序流都有其各自的跳转条件,在单一网关中,不同的执行动作就是通过顺序流的跳转条件来控制。重点关注:
1)名称:顺序流对应的名称,也就是可执行的动作,对应前端界面可操作的按钮,工作流模块提供了接口供前端来查询当前节点的下一步可执行节点,接口详情见接口文档:/workflow-model/nextTasks。
2)跳转条件:跳转到对应节点的条件。其值为EL表达式: a p p r o v e R e s u l t = = ′ t r u e ′ 。其中「 a p p r o v e R e s u l t 」为变量名,不能改变;「 t r u e 」为变量值,用于控制不能的跳转,可以自行制定。例如单一网关的「通过」、「不通过」可分别配置为「 {approveResult=='true'}。其中「approveResult」为变量名,不能改变;「true」为变量值,用于控制不能的跳转,可以自行制定。例如单一网关的「通过」、「不通过」可分别配置为「 approveResult==′true′。其中「approveResult」为变量名,不能改变;「true」为变量值,用于控制不能的跳转,可以自行制定。例如单一网关的「通过」、「不通过」可分别配置为「{approveResult==‘true’}」、${approveResult==‘false’}。在后端代码中对应的控制变量为WorkflowSubmitReq.approveResult。需要注意的是每条顺序流都需要指定跳转条件(「开始事件外」),否则程序无法识别。
数据库集成
MySQL视图
工作流模块有自己独立的数据库表,用于支持其自身的功能。例如:在流程图编辑的过程中,可以指定业务系统中的用户或者角色,这些数据是维护在工作流模块自身的表中的。因此需要在业务模块和工作流模块之间进行数据同步,这里提供了MySQL视图实现其数据同步功能。
这里提供了对业务模块用户/角色/用户角色关系表的视图脚本,其脚本文件为:/doc/sql/workflow/view.sql。重点关注以下几张表:
1)ACT_ID_USER:工作流用户表
2)ACT_ID_GROUP:工作流组/角色表
3)ACT_ID_MEMBERSHIP:工作流用户组/角色关系表
工作流用户视图如下:
CREATE
ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `ACT_ID_USER` AS
SELECT
`newfiber_standard`.`sys_user`.`user_id` AS `ID_`,
1 AS `REV_`,
`newfiber_standard`.`sys_user`.`nick_name` AS `FIRST_`,
`newfiber_standard`.`sys_user`.`nick_name` AS `LAST_`,
`newfiber_standard`.`sys_user`.`user_name` AS `DISPLAY_NAME_`,
`newfiber_standard`.`sys_user`.`email` AS `EMAIL_`,
'123456' AS `PWD_`,
NULL AS `PICTURE_ID_`,
NULL AS `TENANT_ID_`
FROM
`newfiber_standard`.`sys_user`
WHERE
(`newfiber_standard`.`sys_user`.`del_flag` = 0)
业务表结构设计
为支持业务模块的数据流转,业务表需要加入以下两个字段:
1)workflow_instance_id:工作流实例编号,用于保持业务模块与工作流模块的关联关系。
2)status:用户完成业务数据的状态流转
数据库分库
Flowable官方默认提供了47张表,如果都和业务表放在一起,那数据库就太杂乱了,之前的版本就想做分库,但是没有找到合适的方法。
Flowable框架暴露了接口用来自定义SpringProcessEngineConfiguration,修改里面的dataSource属性可以实现数据库分库,修改自定义flowable属性即可实现分库。
flowable:
database-schema: newfiber_standard_workflow
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1/newfiber_standard_workflow?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
username: root
password: 123456
后端集成
后端采用SpringBoot框架,Maven管理项目依赖,对接方式如下:
1)工作流在通用工具包中newfiber-common-workflow,在pom文件中引入工作流模块依赖:
<!-- Newfiber Common Workflow -->
<dependency>
<groupId>com.newfiber</groupId>
<artifactId>newfiber-common-workflow</artifactId>
</dependency>
2)在ApplicationStart类中引入工作流模块路径,完成依赖注入:
// 加入工作流模块路径:"com.newfiber.workflow"
@SpringBootApplication(
scanBasePackages = {"com.newfiber.business", "com.newfiber.workflow"})
public class WorkflowParentApplication {
public static void main(String[] args) {
SpringApplication.run(WorkflowParentApplication.class, args);
}
}
3)开始工作流编码,主要包括以下步骤:
● 创建枚举类实现IWorkflowDefinition接口,完成工作流基本信息定义。
● Server层实现IWorkflowCallback接口并重写方法,完成业务回调方法实现。
● Server层调用ActivitiProcessService类中的方法,完成工作流的启动,执行等操作。
● 可通过WorkflowPageHelper工具类来辅助完成业务数据的分页查询操作。
IWorkflowDefinition
不同的工作流有不同的定义,工作流模块提供了IWorkflowDefinition接口规范工作流的定义,接口方法签名下:
public interface IWorkflowDefinition {
/**
* 工作流编号
* @return 工作流编号
*/
String getWorkflowKey();
/**
* 工作流名称
* @return 工作流名称
*/
String getWorkflowName();
/**
* 业务实体表名,用于分页查询业务数据
* @return 业务实体表名
*/
default String getTableName() {
return "t";
}
/**
* 业务实体表主键类型,用于分页查询业务数据
* @return 业务实体表主键类型
*/
default Class<?> getTableIdType(){
return String.class;
}
}
在业务系统中需要实现该接口,例如定义一个巡查申请的工作流,其代码如下:
public enum EWorkflowDefinition implements IWorkflowDefinition {
/** */
PatrolApply("PatrolApply", "巡查申请", "t", Integer.class);
EWorkflowDefinition(
String workflowKey, String workflowName, String tableName, Class<?> tableIdType) {
this.workflowKey = workflowKey;
this.workflowName = workflowName;
this.tableName = tableName;
this.tableIdType = tableIdType;
}
private final String workflowKey;
private final String workflowName;
private final String tableName;
private final Class<?> tableIdType;
@Override
public String getWorkflowKey() {
return workflowKey;
}
@Override
public String getWorkflowName() {
return workflowName;
}
@Override
public String getTableName() {
return tableName;
}
@Override
public Class<?> getTableIdType() {
return tableIdType;
}
}
IWorkflowCallback
在业务模块开始、提交工作流后,需要执行更新关联id、状态等操作,这里提供了IWorkflowCallback回调接口用来实现该功能。其方法签名如下:
public interface IWorkflowCallback<T> {
/**
* 业务实体类型
* @return 业务实体类型
*/
default Class<?> getEntityClass(){
return ReflectionKit.getInterfaceGeneric(this);
}
/**
* 工作流定义
* @return 工作流定义
*/
IWorkflowDefinition getWorkflowDefinition();
/**
* 更新业务实体的工作流实体编号
* @param businessKey 业务实体编号
* @param workflowInstanceId 工作流实体编号
*/
void refreshWorkflowInstanceId(Object businessKey, String workflowInstanceId);
/**
* 更新业务数据状态
* @param businessKey 业务实体编号
* @param status 状态
*/
void refreshStatus(Object businessKey, String status);
}
业务模块的Server需要实现该接口,例如巡查申请的Server,其核心代码如下:
@Service
public class PurchaseApplyServiceImpl implements IWorkflowCallback<PurchaseApply> {
@Override
public IWorkflowDefinition getWorkflowDefinition() {
return EWorkflowDefinition.PurchaseApply;
}
@Override
public void refreshWorkflowInstanceId(Object businessKey, String workflowInstanceId) {
PurchaseApply condition = new PurchaseApply();
condition.setId(Integer.parseInt(businessKey.toString()));
condition.setWorkflowInstanceId(workflowInstanceId);
updateById(condition);
}
@Override
public void refreshStatus(Object businessKey, String status) {
PurchaseApply condition = new PurchaseApply();
condition.setId(Integer.parseInt(businessKey.toString()));
condition.setStatus(status);
updateById(condition);
}
}
ActivitiProcessService
ActivitiProcessService提供了工作流程中涉及到的核心方法,包括工作流开始、提交等方法,其核心方法签名如下:
/**
* 启动工作流
* @param workflowCallback 回调接口
* @param businessKey 业务编号
* @param startReq 启动参数
* @return 工作流实体编号
*/
String startWorkflow(IWorkflowCallback<?> workflowCallback, Object businessKey, WorkflowStartReq startReq);
/**
* 提交工作流
* @param callback 回调接口
* @param businessKey 业务编号
* @param submitReq 提交结果
* @return 业务编号
*/
String submitWorkflow(IWorkflowCallback<?> callback, Object businessKey, WorkflowSubmitReq submitReq);
业务模块的Server需要注入该接口,并调用其启动和提交方法完成工作流的数据流转,例如巡查申请的Server,其核心代码如下:
@Service
public class PatrolApplyServiceImpl implements PatrolApplyService, IWorkflowCallback<PatrolApply> {
@Resource
private ActivitiProcessService activitiProcessService;
@Override
@Transactional(rollbackFor = Exception.class)
public void create(PatrolApplyCreateReq req) {
PatrolApply patrolApply = new PatrolApply();
BeanUtils.copyProperties(req, patrolApply);
this.save(patrolApply);
// 启动工作流
activitiProcessService.startWorkflow(this, patrolApply.getId(), req);
}
@Override
public void approve(PatrolApplyApproveReq req) {
// 提交工作流
activitiProcessService.submitWorkflow(this, req.getId(), req);
}
}
WorkflowPageHelper
在业务数据查询过程中,会涉及到数据查询权限的问题,其中的权限包括两个部分:
1)数据状态权限:用户只能查看到处于某一状态下的数据。
2)数据执行权限:用户只能查看属于自己的可执行的数据。
对于第一种数据权限,可以通过业务表的status字段控制,业务系统实现起来较为简单。而第二种权限实现起来则较为复杂,这是因为业务模块和工作流模块都只相互存放了对方的数据ID,导致业务模块必须先调用工作流模块查询用户可执行的数据ID,然后才能执行业务查询操作,其调用时序图如下所示:
为解决业务模块调用方法繁琐的问题,这里提供了WorkflowPageHelper工具类。可以实现在分页查询业务数据时,自动加入用户权限数据查询的功能。其核心方法签名如下:
/**
* 开始分页
* @param pageNum 开始页数
* @param pageSize 每页数量
* @param orderBy 排序字段
* @param userId 用户编号
* @param taskKey 任务编号,对应相应的状态节点
* @param workflowCallback 回调接口
* @return 分页参数实体
*/
public static <E> Page<E> startPage(int pageNum, int pageSize, String orderBy, Object userId, String taskKey, IWorkflowCallback<?> workflowCallback)
/**
* 开始分页
* @param workflowPageReq 分页接口参数
* @param workflowCallback 回调接口
* @return 分页参数实体
*/
public static <E> Page<E> startPage(WorkflowPageReq workflowPageReq, IWorkflowCallback<?> workflowCallback)
其也是基于开源工具PageHelper,用法类似。例如巡查申请分页查询,其核心代码如下:
@Override
public PageInfo<PatrolApply> page(PatrolApplyPageReq req) {
PatrolApply condition = new PatrolApply();
BeanUtils.copyProperties(req, condition);
// 执行分页操作
WorkflowPageHelper.startPage(req, this);
List<PatrolApply> list = patrolApplyDao.selectByCondition(condition);
return new PageInfo<>(list);
}
功能特性
会签
在流程业务管理中,任务通常都是由一个人去处理的。而多个人同时处理一个任务,根据多个人的审核结果来确定流程的走向,这种任务我们称之为会签任务。
在绘制BPMN流程图时,添加将【人工任务】配置为会签节点,重点关注:
1)多实例类型:选择「Parallel」。
2)集合(多实例):配置为approveUserIdList。会签节点的审核人列表,对应代码中的WorkflowStartReq.nextTaskApproveUserIdList和WorkflowSubmitReq.nextTaskApproveUserIdList。
3)元素的变量(多实例):集合的变量,会签节点的审核人,需要和「任务派遣」定义的审核人对应。
4)任务派遣:会签节点的审核人。
配置完成后,当执行到该节点时,会为每个会签人员分配一个任务,所有人通过才会通过。
一票否决
会签实现多个人同时审批,任意一个人不同意时,会签任务结束。
1)在完成条件(多实例)中添加以下配置,当满足表达式的条件时,会签节点结束,进入下一个节点:${(approveResult==‘false’)||(nrOfCompletedInstances/nrOfInstances==1)}
●approveResult==‘false’:「approveResult」为「false」时会签结束,即一票否决。
●nrOfCompletedInstances/nrOfInstances==1:框架中维护了两个变量「nrOfCompletedInstances」–Number Of Completed Instances(已完成实例数)和「nrOfInstances」–Number Of Instances(实例数),当两者相同时会签节点结束。
消息通知
在工作流执行到流程中的某个节点时,可以向节点的执行人发送邮件/短信通知。现支持三种方式:
1)使用工作流默认的邮件通知
2)使用自定义消息模板发送通知
3)使用代码调用的方式发送通知
在配置文件中添加以下配置:
spring:
mail:
host: smtp.exmail.qq.com
username: [email protected]
password: 123456
properties:
mail:
smtp:
auth: true
在需要发送消息的【人工任务】中配置消息通知监听器com.newfiber.workflow.support.listener.WorkflowNotificationListener
默认邮件通知
在需要进行消息通知的任务中添加监听器,当执行到该任务时,会自动向该任务的执行人发送一封邮件,邮件内容为 [%workflowKey]您有一条待办任务:[%businessKey]。如果需要自定义发送的内容,或者需要发送短信,可以参照下文的「自定义消息模板」和「方法调用」。
自定义消息模板
如果不想使用默认的消息模板,可以自定义消息模板。这里提供了接口来实现自定义消息模板:IWorkflowEmailNotification用于定义邮件模板,IWorkflowSmsNotification用于定义短信模板,其方法签名如下。其中短信模板现仅支持腾讯云短信,需要在腾讯云中申请短信签名及短信模板后再使用。(注:任务需要配置WorkflowNotificationListener监听器)
IWorkflowEmailNotification
public interface IWorkflowEmailNotification extends IWorkflowNotification{
/**
* 通知模板
* @return 通知模板
*/
String getNotificationTemplate();
}
IWorkflowSmsNotification
public interface IWorkflowSmsNotification extends IWorkflowNotification{
/**
* 短信签名
* @return 短信签名
*/
default String getSmsSign(){
return null;
}
/**
* 短信模板编号
* @return 短信模板编号
*/
default String getSmsTemplateCode(){
return null;
}
}
使用时,首先需要在业务代码中创建枚举实现以上接口。这里以同时发送邮件和短信为例:分别配置了邮件模板和短信模板。
public enum ECountersignNotification implements IWorkflowEmailNotification, IWorkflowSmsNotification {
/**
*
*/
AfterApprove("AfterApprove", "[%s]您有一条待办任务:[%s],请在[%s]前完成",
"新钜物联","10945341");
private final String notificationTask;
private final String notificationTemplate;
private final String smsSign;
private final String smsTemplateCode;
ECountersignNotification(String notificationTask, String notificationTemplate, String smsSign, String smsTemplateCode) {
this.notificationTask = notificationTask;
this.notificationTemplate = notificationTemplate;
this.smsSign = smsSign;
this.smsTemplateCode = smsTemplateCode;
}
@Override
public String getNotificationTask() {
return notificationTask;
}
@Override
public String getNotificationTemplate() {
return notificationTemplate;
}
@Override
public String getSmsSign() {
return smsSign;
}
@Override
public String getSmsTemplateCode() {
return smsTemplateCode;
}
}
然后业务模块的Server需要实现*IWorkflowCallback接口的getWorkflowNotification()*方法,并返回模板定义类。例如会签申请的Server,其核心代码如下:
public interface IWorkflowCallback<T> {
/**
* 工作流通知
* @return 工作流通知
*/
default IWorkflowNotification[] getWorkflowNotification(){
return null;
}
}
public class CountersignServiceImpl implements IWorkflowCallback<Countersign> {
@Override
public IWorkflowNotification[] getWorkflowNotification() {
return ECountersignNotification.values();
}
}
在发送通知时,可以动态指定消息模板的参数。参数内容通过WorkflowStartReq.notificationTemplateArgs或者WorkflowSubmitReq.notificationTemplateArgs传入。
方法调用
如果不想使用默认邮件通知和自定义消息模板,也可以直接通过代码调用的方式发送通知。方法签名如下:
public interface ActivitiProcessService {
/**
* 发送邮件通知
* @param email 邮箱
* @param content 内容
* @return 是否成功
*/
boolean sendEmailNotification(String email, String content);
/**
* 发送短信通知
* @param mobile 手机号
* @param smsSign 短信签名
* @param smsTemplateCode 短息模板编号
* @param templateArgs 模板参数
* @return 是否成功
*/
boolean sendSmsNotification(String mobile, String smsSign, String smsTemplateCode, List<String> templateArgs);
}
文件上传及查询
上传
工作流框架集成到通用产品框架后,节点附件上传走的通用文件上传,文件数据通过「refType」+「refField」和业务数据关联。
● refType:关联类型/业务类型;例如巡查任务:patrolTask
●refField:关联字段;可同时区分节点和文件类型,例如开始图片:start.picture,结束视频:finish.video
查询
在「列表查询历史活动记录」接口*/workflow-process/list-history-activity
*中,需要返回文件数据,由于涉及到依赖关系问题,工作流模块无法直接调用系统文件模块查询。所以现在的方案是通过AOP切面实现文件查询功能,涉及到的关键类:
1、com.newfiber.common.core.annotation.WorkflowFileWrapper:文件包装注解
2、com.newfiber.common.security.aspect.WorkflowFileAspect:文件包装AOP
对于每个节点的文件,默认会以refField.startsWith(节点状态)
过滤(即每个节点只能查询自身上传的文件)。同时查询条件中暴露了字段fileRefFieldPattern 查询关联文件匹配符(例查询refField like '%picture%', 则传picture)
,用于定制化查询。
集成国产数据库
修改newfiber-common-datasource
包的POM依赖,切换数据库组件。
<dependencies>
<!-- Mysql Connector -->
<!-- <dependency>-->
<!-- <groupId>mysql</groupId>-->
<!-- <artifactId>mysql-connector-java</artifactId>-->
<!-- </dependency>-->
<!-- Druid -->
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>druid-spring-boot-starter</artifactId>-->
<!-- </dependency>-->
<!-- Kingbase Connector -->
<dependency>
<groupId>cn.com.kingbase</groupId>
<artifactId>kingbase8</artifactId>
</dependency>
<!-- DM Connector -->
<!-- <dependency>-->
<!-- <groupId>com.dameng</groupId>-->
<!-- <artifactId>DmJdbcDriver18</artifactId>-->
<!-- </dependency>-->
</dependencies>
人大金仓
链接配置
spring:
datasource:
driver-class-name: com.kingbase8.Driver
url: jdbc:kingbase8://192.168.30.91:54321/newfiber_standard
username: system
password: system2023
达梦
链接配置
datasource:
driver-class-name: dm.jdbc.driver.DmDriver
url: jdbc:dm://192.168.30.93:5236/newfiber_standard?schema=newfiber_standard
username: SYSDBA
password: SYSDBA
工作流框架适配
由于达梦不同于人大金仓,数据库不支持原生的隐式数据类型转换,会导致工作流在启动初始化时报错,所以对工作流源码进行了改造用以适配。
项目模块如果引用了工作流,需要用/resources/liquibase-core-4.9.1.jar
包替换掉本地maven仓库对应的包才可正常启动。
总结
总的来看,对接工作流模块需要完成以下步骤:
1)画符合规范的BPMN流程图。
2)编写符合业务模块的MySQL视图并执行。
3)设计业务表。
4)完成后端代码编码。
问题及补充
BPMN绘制规范
在工作流开发过程中,BPMN绘制占很重要的部分,可以由懂业务的项目经理或者开发人员来绘制。在绘制过程中要特别注意上文「BPMN流程图」中提到的「重点关注」事项,其中的绘制规范涉及到和后端代码的交互,如果不遵守会导致后端代码无法识别。重点关注:
● BPMN流程图要指定「流程唯一标识」和「名称」
● 各个节点要指定「ID」和「名称」
● 人工任务节点可指定用户/角色权限,对于变量名的指定要符合文档规范
● 结束事件的ID固定为「end」,并且要指定「执行监听器」
● 每条顺序流都要指定「跳转条件」(「开始事件外」)
相关SQL
已办
SELECT RES.* from ACT_HI_TASKINST RES WHERE RES.END_TIME_ is not null and
( EXISTS(select LINK.ID_ from ACT_HI_IDENTITYLINK LINK where LINK.USER_ID_ = 1 and LINK.TASK_ID_ = RES.ID_)
or RES.ASSIGNEE_ = 1 or RES.OWNER_ = 1 )
and exists ( select 1 from ACT_RE_PROCDEF D
WHERE RES.PROC_DEF_ID_ = D.ID_ and D.KEY_ like 'PatrolCase' ) order by RES.ID_ asc;
SELECT RES.* from ACT_HI_TASKINST RES
left join ACT_RE_PROCDEF D on RES.PROC_DEF_ID_ = D.ID_
where D.KEY_ = 'PatrolCase'
代办
SELECT RES.* from ACT_RU_TASK RES WHERE exists ( select 1 from ACT_RE_PROCDEF D
WHERE RES.PROC_DEF_ID_ = D.ID_ and D.KEY_ = 'projectQualityReformRecord' )
and (RES.ASSIGNEE_ = 1 or ( RES.ASSIGNEE_ is null and
exists(select LINK.ID_ from ACT_RU_IDENTITYLINK LINK
where LINK.TASK_ID_ = RES.ID_ and LINK.TYPE_ = 'candidate'
and (LINK.USER_ID_ = 1 or ( LINK.GROUP_ID_ IN ( 89010001 ) ) )))) order by RES.ID_ asc