什么是工作流

首先还是要把专业术语给搬出来:工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。说的通俗一点就是通过计算机应用帮助我们“走流程”,各种证件的办理、资格的审批在生活中无处不在,工作流就在我们身边。

工作流这个名词概念在计算机领域中,起源于生产组织和办公自动化领域,是针对日常工作中具有固定程序活动而提出的一个概念,目的是通过将工作分解成定义良好的任务或角色,按照一定的规则和过程来执行这些任务并对其进行监控,达到提高工作效率、更好的控制过程、增强对客户的服务、有效管理业务流程等目的。

要走“走流程”的人是痛苦的,慢、麻烦相信是很多人对走流程的印象,而处理“走流程”的人也并不开心,复杂的过程令人心力憔悴。而在这个计算机互联网时代,流程也都逐渐开始“上网”,构成所谓的信息化的一个重要组成部分。这就催生了后台工作流框架,比较著名的有jBPM和Activiti,本文的主角就是Activiti。

Activiti简介

本文以Activiti 5.22作为示例和分析的基础。还是先上官话:Activiti5是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理、工作流、服务协作等领域的一个开源的、灵活的、易扩展的可执行流程语言框架。

Activiti实现了BPMN 2.0规范,可以发布设计好的流程定义,并通过api进行流程调度。Activiti流程引擎重点关注在系统开发的易用性和轻量性上。每一项 BPM 业务功能 Activiti 流程引擎都以服务的形式提供给开发人员。通过使用这些服务,开发人员能够构建出功能丰富、轻便且高效的 BPM 应用程序。

本文目标

记录上手过程,快速理解、入门Actitviti,并不深挖以及使用骚操作。

上手过程

准备工作

  1. 首先Activiti的数据基于数据库,所以首先需要一个数据库,基本主流的数据库Activiti都支持,选用mysql就ok。
  2. 去官网下载Activiti,里面不仅包含了需要的jar包,还有文档、sql文件、war包等。
  3. 配置IDE,虽然说eclipse插件是Activiti的一个优点,但是目前IDEA的流行程度更高一些,还是选用IDEA作为开发测试工具,IDEA也有Activiti插件名为actiBPM,但是个人觉得不太好用,截止目前这个插件最后更新时间是2014年,看起来好像不在维护了,比较尴尬。
  4. 框架集成,Activiti绝大多数情况还是作为一个组件集成到后台框架中,为了方便,减少其他因素干扰,选用SpringBoot。不过很可惜在练习的时候想当然地选了SpringBoot2.0,集成起来发生了问题浪费了很多时间。
  5. 简单了解下BPMN,业务流程建模与标注(Business Process Model and Notation,BPMN),定义了流程图的内容和表达,最新的是BPMN 2.0规范。流程内容一般采用XML格式来承载表达,通过图形化工具来快速、直观地设计流程。

流程图图解

很多教程喜欢上来就说API、说数据库表,但我认为还是该从最直观的流程图来作为切入点,一步一步引出各个概念。下图是一个最简单的流程图,流程的目的是员工加入公司的审批。

对应的XML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
xmlns:tns="http://www.activiti.org/test"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
expressionLanguage="http://www.w3.org/1999/XPath"
id="m1534942345607" name=""
targetNamespace="http://www.activiti.org/test"
typeLanguage="http://www.w3.org/2001/XMLSchema">
<process id="joinProcess" isClosed="false" isExecutable="true" processType="None">
<startEvent id="_2" name="Join process">
<extensionElements>
<activiti:formProperty id="personId" name="person Id" required="true" type="long"/>
<activiti:formProperty id="compId" name="company Id" required="true" type="long"/>
</extensionElements>
</startEvent>
<userTask activiti:exclusive="true" id="_3" name="主管审批"
activiti:candidateUsers="${joinService.findUsers(execution)}" isForCompensation="true">
<extensionElements>
<activiti:formProperty id="joinApproved" name="Join Approved" type="enum">
<activiti:value id="true" name="Approve" />
<activiti:value id="false" name="Reject" />
</activiti:formProperty>
</extensionElements>
</userTask>
<endEvent id="_4" name="EndEvent"/>
<serviceTask activiti:exclusive="true" id="_5" name="审批处理" activiti:expression="${joinService.joinGroup(execution)}" />
<sequenceFlow id="_6" sourceRef="_2" targetRef="_3"/>
<sequenceFlow id="_7" sourceRef="_3" targetRef="_5"/>
<sequenceFlow id="_8" sourceRef="_5" targetRef="_4"/>
</process>
<bpmndi:BPMNDiagram documentation="background=#FFFFFF;count=1;horizontalcount=1;orientation=0;
width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
<bpmndi:BPMNPlane bpmnElement="myProcess_1">
<bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
<omgdc:Bounds height="32.0" width="32.0" x="70.0" y="181.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
<omgdc:Bounds height="55.0" width="85.0" x="160.0" y="170.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
<omgdc:Bounds height="32.0" width="32.0" x="455.0" y="181.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
<omgdc:Bounds height="55.0" width="85.0" x="310.0" y="170.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="_6" id="BPMNEdge__6" sourceElement="_2" targetElement="_3">
<omgdi:waypoint x="102.0" y="197.5"/>
<omgdi:waypoint x="160.0" y="197.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_7" id="BPMNEdge__7" sourceElement="_3" targetElement="_5">
<omgdi:waypoint x="245.0" y="197.5"/>
<omgdi:waypoint x="310.0" y="197.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="_5" targetElement="_4">
<omgdi:waypoint x="395.0" y="197.5"/>
<omgdi:waypoint x="455.0" y="197.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

图中内容相当简单,两个开始和结束的圆圈,两个流程的步骤。

先用直白的文字描述这个流程:人事提交员工加入公司的审批 -> 主管审批 -> 结束,通过后要生成一条入职数据。

而流程图中表现的是:开始流程 -> 主管审批 -> 审批处理 -> 结束。

可见还是有一定的差别,根据我个人的感觉,BPMN流程图的表达和直白的思维是有一定的区别的,这也是我一开始上手时感到困惑的点。其一是Activiti流程开始后并不会在开始节点停留,所以提交审批这个流程在图中没有直接的体现,其二是对于一个审批任务来说通俗思维会认为审批和处理属于同一步操作,而对于Activiti每一步操作是一个任务,任务区分明确,其中尤以人为的操作(UserTask)和程序处理(ServiceTask)这两个为主,因此主管审批是一步,而审批处理生成数据是另外一步。

图中bpmndi:BPMNDiagram节点下的内容代表的是流程图各个节点的位置信息,以图的左上角为原点,用xy坐标值表示流程节点在图中的位置。

主要概念

任务

很自然地引申出任务这个点,更准确地说应该是执行计划(excution)。Task用来描述业务过程中所有可能发生工时的行为,它主要包括UserTask、ServiceTask、ScriptTask、ReceiveTask、MailTask等等。详细说明一下其中最基础的两种:

  1. UserTask:人工任务,用来描述认为参与的操作,流程执行到此节点时须人工响应后才能继续向下流转。人工任务最终要的属性就是办理任务的执行者,有assignee(单一执行人)、candidateUsers(多个候选执行人)、candidateGroups(候选执行组)这几种属性可选。
  2. ServiceTask:Java服务任务,允许指定一个实现了指定接口的java类,或者执行一个表达式。与User Task不同,流程引擎流经此节点会自动调用Java类中定义的方法,方法执行完毕自动向下一流程节点流转。

顺序流

有了任务节点就自然需要把他们连接起来,图中的箭头连线称为顺序流(sequenceFlow),用于描述节点的流转方向和顺序。主要属性有sourceRef(起始节点Id)、targetRef(指向节点Id)、conditionExpression(条件限制的表达)。

网关

网关(gateways)在示例的图中没有表现,但是它是流程定义重要的要素之一。真实的流程并不会如此简单,很多时候流程需要分叉、并行等等,而网关就是用来决定流程流转指向的,可能会被用作条件分支或聚合,也可以被用作并行执行或基于事件的排它性条件判断。常用的网关有互斥关口(exclusiveGateway,流程经过时只能走其中一个顺序流)和并行关口(parallelGateway,经过关口后会同时经过所有顺序流,所有流程完成后会一起通过指向的并行关口)。

事件

说完上述的还有两个圆圈没有讲到,在BPMN规范下圆圈一般表示事件。启动、结束、边界条件以及每个活动的创建、开始、流转等都是流程事件,利用事件机制,可以通过事件控制器为系统增加辅助功能。开始和结束是每个流程都必须包含的事件节点。

任务和顺序流以及未介绍的子流程可以同称为活动(Activities),加上网关和事件就构成了BPMN2.0对流程执行语义定义的三类基本要素。

流程变量

流程变量在整个工作流中用于传递业务变量,流程变量的作用域范围是只对应一个流程实例,在开启流程和完成任务时都可以设置变量,直接设置变量时,分setVariable和setVariableLocal,local表示绑定当前任务,流程继续执行时下个任务获取不到这个流程变量,涉及的数据库表为act_ru_variable、act_hi_varinst(变量历史记录)。

注意,流程变量需要实现Serializable接口且要求过程中属性不能变化(反序列化),需要添加序列化ID,如果是JavaBean作为流程变量,序列化的bean会存放到act_ge_bytearray这张表中。

流程可以定义需要哪些变量,可以查看xml中activiti:formProperty相关的信息。

核心API

了解了流程图之后,介绍Activiti的核心API,快速形成对于Activiti的基本概念。Activiti最核心的类是流程引擎(ProcessEngine),其他所有类都通过引擎来获取。引擎可以获取各种各种各样的服务(Service)对应处理各种需求,所有流程的具体操作都对应执行某一项服务提供的方法。

服务

  1. RepositoryService:仓库服务。业务流程的定义都需要使用一些定义文件,定义了流程需要将其部署,部署之后可能需要查询部署的信息和相关的文件,这些需求就由RepositoryService提供。
  2. RuntimeService:流程执行服务。在Activiti中,每当一个流程定义被启动一次之后,都会生成一个相应的流程对象实例。RuntimeService提供了启动流程、查询流程实例、设置获取流程实例变量等功能。
  3. TaskService:任务服务。它提供了运行时任务查询、领取、完成、删除以及变量设置等功能。
  4. HistoryService:历史服务。流程、任务执行完成之后可能需要追溯、查询历史,这就需要用到HistoryService。
  5. IdentityService:身份服务。Activiti中内置了用户以及组管理的功能,必须使用这些用户和组的信息才能获取到相应的Task。IdentityService提供了对Activiti 系统中的用户和组的管理功能。当然用户和组也可以自定义提供,但是这些内容不在本文讨论范围之内
  6. ManagementService: 管理服务。它提供了对Activiti流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于Activiti系统的日常维护。

对象

除了服务,还有以接口形式表达的几个关键对象:

  1. Deployment:流程部署对象,是对一个部署的抽象化表达。
  2. ProcessDefinition:流程定义,部署成功后自动创建。
  3. ProcessInstance:代表流程实例,启动流程时创建。
  4. Execution:执行计划,流程实例和流程执行中的所有节点都是Execution。当流程单线执行时ProcessInstance与Execution内容保持一致,并发流程中,总线路为ProcessInstance,分线路中每个活动由Execution表达。
  5. Task:任务,在Activiti中的Task仅指有角色参与的任务,包括上述的UserTask以及MannualTask。

入门示例

说完了概念性的东西,接下来就需要完整地走一遍流程。示例的同时介绍相关的数据表,Activiti所有的内容信息都以数据库作为基础,各种服务提供的查询本质上都是对数据库的查询。

还是以上述的简单流程作为示例来进行演示。

工程搭建

打开IDEA创建一个SpringBoot项目(可以选2.0,有坑但是会加以说明解决),勾选组件:Web、JPA(想用其他的ORM也可以)、MySQL即可,项目构建采用Maven。工程创建成功后,在POM文件中增加Activiti的依赖。

1
2
3
4
5
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>5.22.0</version>
</dependency>

这里有个整合的坑点,Activiti目前的版本落后于SpringBoot和SpringSecurity,如果直接就这么运行工程会产生缺少SpringSecurity依赖的错误,但是可以在POM中可以看到SpringSecurity的依赖的Optional属性为True,而且安全相关的自动配置添加了@AutoConfigureBefore,确保在SpringSecurity自动配置完成后执行。尝试加入SpringSecurity的依赖依然报错,可以发现Activiti里依赖的一个类在SpringSecurity5.0版本之后已经移动了位置。因此必须强制排除产生错误的Activiti的自动配置类,修改主类Application如下:

1
2
3
4
5
6
7
8
9
@EnableAutoConfiguration(exclude = {
org.activiti.spring.boot.SecurityAutoConfiguration.class
})
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

数据准备与流程部署

创建Person和Comp两个实体类以及相关的JpaRepository,用于表示员工和公司以及表现生成数据的操作。然后保存几个实例以供使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Entity
public class Person {
@Id
@GeneratedValue
private Long personId;

private String personName;

@ManyToOne
private Comp comp;

public Person(String personName) {
this.personName = personName;
}

// Getter & Setter
// ...
}

@Entity
public class Comp {

@Id
@GeneratedValue
private Long compId;

private String compName;

@OneToMany(mappedBy = "comp")
private List<Person> people;

public Comp(String compName) {
this.compName = compName;
}

// Getter & Setter
// ...
}

在测试类中写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Autowired
private PersonRepository personRepository;
@Autowired
private CompRepository compRepository;
@Before
public void contextLoads() {
// 准备数据
if (personRepository.findAll().size() == 0) {
personRepository.save(new Person("wtr"));
personRepository.save(new Person("wyf"));
personRepository.save(new Person("admin"));
}
if (compRepository.findAll().size() == 0) {
Comp group = new Comp("great company");
compRepository.save(group);
Person admin = personRepository.findByPersonName("admin");
Person wtr = personRepository.findByPersonName("wtr");
admin.setComp(group); wtr.setComp(group);
personRepository.save(admin); personRepository.save(wtr);
}
}

接下来将上面的流程XML代码拷贝进入一个xml文件,在resource目录下新建一个process文件夹,将xml文件放入这个文件夹,复制文件更改后缀为bpmn,这么做是因为IDEA的插件编辑器无法编辑某些属性,而且重新打开bpmn文件会发现配置的属性看不到,所以会把xml放在同一个目录下进行参考。设计流程时可以先用插件画图,然后更改为xml后缀进行属性编辑,编辑完成后复制一份并更改为bpmn后缀。

由于和springboot集成,process目录下的流程会自动部署,省去了部署这件事,不过还是把普通的部署代码贴出来。

1
2
3
4
5
6
7
8
9
10
@Test
public void showDeploy(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
Deployment deploy = processEngine.getRepositoryService()
.createDeployment()
.name("testDeploy")
.addClasspathResource("processes/example.bpmn")
.deploy();
System.out.println(deploy.getId() + " " + deploy.getName());
}

与部署相关的数据表以ACT_RE开头,在执行以上步骤或者自动部署后,可以在相关表中查看到新生成的部署信息以及流程定义的信息。另外流程文件(包括bpmn、png、bpmn20.xml等)会直接以二进制形式存入act_ge_bytearray表,ACT_GE开头的表存放通用数据。

同时,还需要准备身份信息,也就是添加执行人以及执行组,但是这一步并不是必要的,原因同样可以从数据库中窥得一二。上文提到了Activiti提供了一个默认的身份服务,相关的数据表以ACT_ID开头,很明显act_id_user存放的就是用户的信息,不过任务相关的表并没有和用户表有外键约束,所以任务执行的时候即使用户表中没有相关信息,相关的任务表依然可以存储用户的ID标识,也可以以ID来查询。同样还是放出一段添加用户和组的代码,以供了解参考,示例工程为了方便就不添加用户和组了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void addUserAndGroup(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
IdentityService identityService = processEngine.getIdentityService();
identityService.saveGroup(new GroupEntity("主管"));//建立组
identityService.saveGroup(new GroupEntity("经理"));
identityService.saveUser(new UserEntity("小明"));// 建立用户
identityService.saveUser(new UserEntity("小红"));
UserEntity littileMount = new UserEntity("小山");
littileMount.setEmail("xxx@fake.com"); // 设置用户相关详细信息
identityService.saveUser(littileMount);
identityService.createMembership("小明", "主管");//建立组和用户关系
identityService.createMembership("小红", "主管");
identityService.createMembership("小山", "经理");
}

开启流程实例

以下相关操作为了方便都写在测试方法中,更标准地做法是写几个Controller和Service来进行请求,不过实际生效的代码是一致的。不要立马运行这些代码,因为后面还需要写一些东西,看完一遍以后再动手练习。

准备妥当之后,首先就是开启一个流程。每开启一个定义好的流程就会产生一个流程实例,相关的数据表为act_ru_execution。

1
2
3
4
5
6
7
@Autowired
private RuntimeService runtimeService;

@Test
public void startProcess() {
runtimeService.startProcessInstanceByKey("joinProcess");
}

由于和Spring整合,Activiti的引擎和服务都在Spring中以供装配。开启流程只有一句话,很简单,给出的这个参数值可以在定义流程的xml中找到,找到了自然就理解了。不过仔细想想,其实还漏了一些东西,开始流程的时候需要提供这个审批过程需要的信息,就好比去办手续总是要你提交身份证复印件一样。对于这个审批员工加入公司的示例流程来说,最基本的信息就是两个:员工id和公司id,需要在流程开启时传入,这就是流程变量的概念。所以完整的开启流程代码应该如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
@Autowired
private RuntimeService runtimeService;

@Test
public void startProcess() {
Person wyf = personRepository.findByPersonName("wyf");
Comp comp = compRepository.findAll().get(0);
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("personId", wyf.getId()); // 员工id
variables.put("compId", comp.getId()); // 公司id
runtimeService.startProcessInstanceByKey("joinProcess",variables);
}

完成人工任务

如之前所说,流程开启后不会在开始节点停留,那么很显然目前流程位于主管审批这个节点。人工任务需要响应后流程才会继续,这一步就是抽象化的主管审批动作。

查询任务

首先,Activiti需要知道由谁来执行某个任务,查看流程xml文件可以发现userTask节点有一个属性:activiti:candidateUsers,这就是在指定执行任务的候选人。它的值是一个表达式(也可以直接填入候选人的id,指定执行者有多种方法),可以理解为执行某个java方法来获取候选人的id。创建一个类JoinService,并且将其交由Spring管理。

1
2
3
4
5
6
7
@Service
public class JoinService {
//获取符合条件的审批人,演示方便这里写死,使用时应用实际代码
public List<String> findUsers(DelegateExecution execution) {
return Arrays.asList("admin", "wtr");
}
}

这就对应了表达式:"${joinService.findUsers(execution)}"。这里指定了admin和wtr可以执行这个审批。但是实际应用过程中任务不会直接摆在审批人面前,我们需要查询任务。查询任务的方式有许多种,这里就介绍最基本的按照用户id查询。

1
2
3
4
5
6
7
8
9
10
11
@Autowired
private ActivitiService activitiService;
@Test
public void query() {
List<Task> tasks = activitiService.getTasks("admin");
System.out.println("----------------- task size : "+tasks.size());
for (Task task : tasks){
System.out.println("Id : "+task.getId()+" Name : "+task.getName());
}
return ;
}
执行完成

查询到这个任务后就可以将其完成。代码也很少,不过别忘了输入代表审批结果的参数。执行完这个方法后,人工任务就完成了,流程将进入下一个节点。

1
2
3
4
5
6
7
8
9
@Autowired
private TaskService taskService;
@Test
public void completeTasks() {
Map<String, Object> taskVariables = new HashMap<String, Object>();
taskVariables.put("joinApproved", true); // 这里直接给予true表示通过
String taskId = "1"; // 更改成上个方法查询出的任务Id
taskService.complete(taskId, taskVariables);
}

服务任务的执行

可以在图中看到接下来的节点是一个服务任务。如上文所述,与人工任务不同,服务任务会在执行完设置的方法或者表达式之后自动结束进入下一步。那么很显然最重要的就是如何设置执行的方法,查看流程定义xml文件中的serviceTask节点,很明显就是它的activiti:expression属性指定了执行的方法。根据这个属性值,需要在之前创建的JoinService类中添加一个名为joinGroup的方法,完整的JoinService类如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class JoinService {
@Autowired
PersonRepository personRepository;
@Autowired
private CompRepository compRepository;

//加入公司操作,可从DelegateExecution获取流程中的变量
public void joinGroup(DelegateExecution execution) {
Boolean bool = execution.getVariable("joinApproved",Boolean.class);
if (bool) {
Long personId = execution.getVariable("personId", Long.class);
Long compId = execution.getVariable("compId",Long.class);
Comp comp = compRepository.findById(compId).get();
Person person = personRepository.findById(personId).get();
person.setComp(comp);
personRepository.save(person);
System.out.println("加入组织成功");
} else {
System.out.println("加入组织失败");
}
}

//获取符合条件的审批人,演示方便这里写死,使用时应用实际代码
public List<String> findUsers(DelegateExecution execution) {
return Arrays.asList("admin", "wtr");
}
}

在joinGroup方法中可以通过入参DelegateExcution来获取流程中的参数。如果审批通过则在数据库中增加Person和Comp的关联,表示员工加入了公司。

查询历史记录

到上一步其实可以发现整个示例流程已经结束。但是还有一个很重要的需求就是历史记录的查询,流程实例结束之后可以查询相关的历史记录,主要包括历史流程实例查询、历史活动实例查询、历史任务实例查询和历史流程变量查询等,这些方法都来自HistoryService。历史相关的数据库表以ACT_HI开头,一共有8张历史记录相关的表。

值得一提的是正在运行中的流程相关信息由RuntimeService提供查询,而结束的流程信息才能够通过HistoryService获取到,信息存储在不同的数据表,这是为了减少单表数据量提高效率,大部分的操作主要是对执行中流程的查询。因此在人工任务等待期间可以在act_ru_execution表中查到相关数据,而流程结束之后可以发现相关信息已经从表中删除。

总结

看完上述内容,并且实践过示例流程后,相信已经建立了对于Activiti的基本理解,对于创建、处理简单的流程应该不成问题,当然实际项目的应用需要处理的问题一定不限于此,Activiti框架本身也不止于此。本文只是入门,想进一步了解还是应该查看官方文档、源码以及高阶的教程。

另外,官网下载的Activiti文件中包含了一个演示项目的war包:activiti-explorer.war,部署后可以在线编辑流程图,作为一个官方Demo也有助于快速入门。部署也比较简单,在mysql中创建一个数据库,使用给予的sql文件创建Activiti相关的表,然后将war包解压置入tomcat,找到activiti-explorer\WEB-INF\classes下的db.properties配置文件并打开,修改数据库连接信息如下。

1
2
3
4
5
db=mysql
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/activiti #更改为创建的数据库名
jdbc.username=root #数据库用户
jdbc.password=123456 #用户密码

复制mysql连接的jar包mysql-connector-java-xxx.jar(没有的话先下载)到WEB-INF\lib目录下即可。启动tomcat,不出问题打开http://localhost:8080/activiti-explorer,使用用户kermit,密码kermit登录即可开始体验操作。