Activiti作为老牌工作流引擎,目前在国内市面上存在广泛的使用。JeecgBoot是国内普及率最高的低代码业务开发平台。 本文将以保姆级喂养式带您从0到1完成集成,开发的工作。
访问JeecgFlow在线体验集成后效果
环境介绍
项目 | 版本 |
---|---|
JeecgBoot | 3.6.1 |
Activiti | 7.0.0.Beat2 |
jdk | 1.8 |
JeecgBoot新增activiti模块
首先,我们要新建一个模块,命名成:jeecg-module-activiti, 与现有的命名方式保持一致。在这个模块去做activiti工作流相关的业务。
在项目中,点击右键,选择module
直接进入创建模块,不要选择任何依赖。
完成模块命令,写入jeecg-module-activiti
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-base-core</artifactId>
</dependency>
并在该模块下加入上述依赖。 这个是jeecg的核心模块,也是一个公共模块。
在jeecg-boot-module-system的start模块的pom.xml引入新增的activit模块
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-module-activiti</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
swagger配置
在swagger配置新模块的swagger扫描bean.找到Swagger2Config。追加如下Bean配置。
@Bean
public Docket loanApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo()).groupName("会员模块")
.select()
//此包路径下的类,才生成接口文档
.apis(RequestHandlerSelectors.basePackage("org.jeecg.crm"))
//加了ApiOperation注解的类,才生成接口文档
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
.securitySchemes(Collections.singletonList(securityScheme()));
//.globalOperationParameters(setHeaderToken());
}
mybatis-plus配置
在application.yml中的配置.
mybatis-plus:
mapper-locations: classpath*:classpath*:org/jeecg/activiti/**/xml/*Mapper.
mybatis-plus配置类的配置.MybatisPlusSaasConfig配置mapper文件扫描路径
@Configuration
@MapperScan(value={"org.jeecg.modules.**.mapper*","org.jeecg.loan.**.mapper*","org.jeecg.camunda.**.mapper*","org.jeecg.story.**.mapper*"})
public class MybatisPlusSaasConfig {
}
完成以上配置, 你可以往这个模块建表。到这也就完成第一步。模块建立。 接下来说明下如何完善activiti模块的依赖和配置。
Activiti模块的安装和配置
项目父pom.xml中新增activiti的依赖
<dependencyManagement>
<dependencies>
<!--以下是所需的依赖文件-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.0.0.Beta2</version>
<exclusions>
<exclusion>
<!-- 重点坑,不排除mybatis的话,在启动项目时会报错mybatisplus缺少类 -->
<artifactId>mybatis</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>
jeecg-module-activit模块的依赖
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<!-- 重点坑,不排除mybatis的话,在启动项目时会报错mybatisplus缺少类 -->
<artifactId>mybatis</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
application.yml配置
spring:
activiti:
#1.flase:默认值。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
#2.true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
#3.create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)
#4.drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
database-schema-update: true
# 检测历史信息表是否存在,activiti7默认不生成历史信息表,开启历史表
db-history-used: true
# 历史记录存储等级
history-level: full
check-process-definitions: true
mysql配置->nullCatalogMeansCurrent=true
url: jdbc:mysql://rxxx/jeecg-camunda?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
//需要在尾部追加nullCatalogMeansCurrent=true
//如果不追加, 可能会出现cause: java.sql.SQLSyntaxErrorException:
Table 'jeecg-camunda.act_ge_property' doesn't exist
Activiti7 中自带的 Security 安全框架排除掉(因为我这里使用的是 Shiro 安全框架,Security 就没什么用处了)
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, SecurityAutoConfiguration.class,
SecurityAutoConfiguration.class,
ManagementWebSecurityAutoConfiguration.class})
public class OAApplication
{
public static void main(String[] args)
{
SpringApplication.run(OAApplication.class, args);
System.out.println("启动成功~");
}
}
上面排除 Security 安全框架的操作对于 Activiti 7.1.0.M6 这个版本是没用的,
因为这个版本的代码强引用了 SpringSecurity 里的内容,
比如在 Activiti 的 SpringBoot 配置类中,强引用 UserDetailsService,
没有这个就会报错,所以我们还需要把版本降到7.1.0.M4及以下。
异常情况解决
caused by: java.lang.illegalargumentexception: property ‘sqlsessionfactory’ or ‘sqlsessiontemplate’ are required
the bean ‘methodsecurityinterceptor’, defined in class path resource
[org/activiti/spring/boot/methodsecurityconfig.class],
could not be registered. a bean with that name has already been defined in class path resource
[org/springframework/security/config/annotation/method/configuration/globalmethodsecurityconfiguration.class]
and overriding is disabled.
- 解决办法在application.yml中,添加如下配置文件.
spring:
main:
allow-bean-definition-overriding: true
入门需求
完成上述配置后, 接下来,我们来完成一个简单的审批入门需求。 帮助大家快速了解如何使用工作流。
需求描述
我们以三国为背景, 假设系统中拥有将军,军师,主公三种角色。
现在作为将军的关羽需要发动一个出征流程。在填写好出兵人数和攻占城市后。
流程流转到军事诸葛亮进行评审, 此时军事可以给出锦囊妙计。
军事同意后,
由主公角色刘备进行最后裁断,并赐予战甲等物资。
这个就是系统中多角色协同的经典业务场景。
这个就是一个简单的审批的流程,可以类比公司的职员-直属领导-部门经理
完成上述流程建模, 可以在模型建设的菜单下进行操作。
模块新建的页面,提供在线建模的功能
流程模型分析
- 1.设置将军角色的用户任务节点的属性配置
将军角色用户任务节点属性配置
- 2.设置军师角色的用户任务节点的属性配置
军师角色用户任务节点属性配置
3.设置主公角色的用户任务节点的属性配置
主公角色用户任务节点属性配置
接下来,开始角色扮演的过程
- 将军角色-关羽登录系统,并且点击发起流程。
这两个参数值与用户任务节点设置的字段是对应关系
@ApiOperation(value = "启动流程", notes = "启动流程")
@PostMapping("/startDemo")
public Result<?> startDemo(@RequestBody ProcessStartReq processStartReq) {
//设置用户任务节点assignee流程变量的值
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
Map<String, Object> variables = new HashMap<>(4);
variables.put("general", sysUser.getUsername());
variables.put("guide", "诸葛亮");
variables.put("leader", "刘备");
//启动流程&&并设置启动人
Authentication.setAuthenticatedUserId(sysUser.getUsername());
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processStartReq.getModelKey(), variables);
//查询出征申请用户任务节点,并且设置参数
Map<String, Object> params = new HashMap<>(12);
params.put("number", processStartReq.getNumber());
params.put("city", processStartReq.getCity());
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).taskAssignee(sysUser.getUsername()).singleResult();
if (task != null) {
taskService.complete(task.getId(), params);
}
return Result.OK("操作成功", processInstance.getId());
}
- 军师角色登录系统-完成审批动作
待办任务点击任务详情,重点关注同意,驳回,终止三个按钮的操作
同意操作,流程就会流转到下一个节点。
Map<String, Object> map = new HashMap<>(12);
map.put("comment", userTaskApproveReq.getComment());
map.put("imageUrl", userTaskApproveReq.getImageUrl());
//创建评论
CommentHistoryRes.CommentDTO commentDTO = new CommentHistoryRes.CommentDTO();
commentDTO.setComment(userTaskApproveReq.getComment());
commentDTO.setImageUrl(userTaskApproveReq.getImageUrl());
commentDTO.setTaskStatus("<font color='#67C23A'>通过</font>");
taskService.addComment(userTaskApproveReq.getTaskId(), userTaskApproveReq.getProcessInstanceId(), JSON.toJSONString(commentDTO));
taskService.complete(userTaskApproveReq.getTaskId(), map);
驳回操作,可以指定驳回的节点,当前系统默认驳回到发起任务的用户任务节点
public void jump(UserTaskJumpReq userTaskRejectReq) {
Task t = taskService.createTaskQuery().taskId(userTaskRejectReq.getTaskId()).singleResult();
String processDefinitionId = runtimeService.createProcessInstanceQuery().processInstanceId(t.getProcessInstanceId()).singleResult().getProcessDefinitionId();
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
// 寻找流程实例当前任务的activeId
Execution execution = runtimeService.createExecutionQuery().executionId(t.getExecutionId()).singleResult();
String activityId = execution.getActivityId();
FlowNode currentNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityId);
FlowNode targetNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(userTaskRejectReq.getTargetActivityId());
// 创建连接线
List<SequenceFlow> newSequenceFlowList = new ArrayList<SequenceFlow>();
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newFlow");
newSequenceFlow.setSourceFlowElement(currentNode);
newSequenceFlow.setTargetFlowElement(targetNode);
newSequenceFlowList.add(newSequenceFlow);
// 备份原有方向
List<SequenceFlow> dataFlows = currentNode.getOutgoingFlows();
List<SequenceFlow> oriSequenceFlows = new ArrayList<SequenceFlow>();
oriSequenceFlows.addAll(dataFlows);
// 清空原有方向
currentNode.getOutgoingFlows().clear();
// 设置新方向
currentNode.setOutgoingFlows(newSequenceFlowList);
//创建评论
CommentHistoryRes.CommentDTO commentDTO = new CommentHistoryRes.CommentDTO();
commentDTO.setComment(userTaskRejectReq.getComment());
commentDTO.setImageUrl(userTaskRejectReq.getImageUrl());
commentDTO.setTaskStatus("<font color='#E6A23C'>驳回</font>");
taskService.addComment(userTaskRejectReq.getTaskId(), userTaskRejectReq.getProcessInstanceId(), JSON.toJSONString(commentDTO));
taskService.complete(userTaskRejectReq.getTaskId());
// 恢复原有方向
currentNode.setOutgoingFlows(oriSequenceFlows);
//会签任务不可以驳回 TODO
}
- 终止操作,是强行将流程结束。
public void forceEnd(UserTaskForceEndReq userTaskForceEndReq) {
Task t = taskService.createTaskQuery().taskId(userTaskForceEndReq.getTaskId()).singleResult();
String processDefinitionId = runtimeService.createProcessInstanceQuery().processInstanceId(t.getProcessInstanceId()).singleResult().getProcessDefinitionId();
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
// 寻找流程实例当前任务的activeId
Execution execution = runtimeService.createExecutionQuery().executionId(t.getExecutionId()).singleResult();
String activityId = execution.getActivityId();
FlowNode currentNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityId);
// 创建结束节点和连接线
EndEvent end = new EndEvent();
end.setName("强制结束");
end.setId("forceEnd");
List<SequenceFlow> newSequenceFlowList = new ArrayList<SequenceFlow>();
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newFlow");
newSequenceFlow.setSourceFlowElement(currentNode);
newSequenceFlow.setTargetFlowElement(end);
newSequenceFlowList.add(newSequenceFlow);
// 备份原有方向
List<SequenceFlow> dataFlows = currentNode.getOutgoingFlows();
List<SequenceFlow> oriSequenceFlows = new ArrayList<SequenceFlow>();
oriSequenceFlows.addAll(dataFlows);
// 清空原有方向
currentNode.getOutgoingFlows().clear();
// 设置新方向
currentNode.setOutgoingFlows(newSequenceFlowList);
// 完成当前任务
CommentHistoryRes.CommentDTO commentDTO = new CommentHistoryRes.CommentDTO();
commentDTO.setComment(userTaskForceEndReq.getComment());
commentDTO.setImageUrl(userTaskForceEndReq.getImageUrl());
commentDTO.setTaskStatus("<font color='#F56C6C'>终止</font>");
taskService.addComment(userTaskForceEndReq.getTaskId(), t.getProcessInstanceId(), "comment", JSON.toJSONString(commentDTO));
taskService.complete(userTaskForceEndReq.getTaskId());
// 恢复原有方向
currentNode.setOutgoingFlows(oriSequenceFlows);
}
具体的如何操作,可以登录www.jeecgflow.com系统去体验。
主公角色也是同样的操作。 就不再描述。