下载示例代码
https://community.alfresco.com/external-link.jspa?url=https%3A%2F%2Fgithub.com%2Fgravitonian%2Factiviti7-api-basic-process
maven 依赖
示例项目使用 7.0.0.Beta3,我们需要将它换成最新的 7.1.7。
首先是 spring-boot 版本,需要更新至 2.1.2 :
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
添加一个属性:
<activiti-dependencies.version>7.1.7</activiti-dependencies.version>
修改 activiti 的版本:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.activiti.cloud.dependencies</groupId>
<artifactId>activiti-cloud-dependencies</artifactId>
<version>${activiti-dependencies.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
7.1.7 还没有正式发布,目前放在 alfresco 的私有库上,我们还需要添加一个私有库:
<repositories>
<repository>
<id>alfresco</id>
<name>Activiti Releases</name>
<url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases/</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
修改代码
有几个地方报红了,需要修改一下。7.1 和 7.0 相比较,主要是命名或包结构的改变。
- 修改 MyProcessEventListener.java 46 行,将 SequenceFlowTakenEvent 修改为 SequenceFlowEvent。
- 修改 ProcessStartController.java 47 行,将 withProcessInstanceName 修改为 withName。
- 在 ServiceTask1Connector.java 中,实现抽象方法:
@Override
public IntegrationContext apply(IntegrationContext integrationContext) {
return integrationContext;
}
OK,可以运行了。
流程图
代码中使用的流程图文件是 resources/processes/sample-process.bpmn20.xml。这是一个非常简单的流程图:
总共只有两个任务:一个人工任务,一个服务任务。
注意,流程定义文件必须放在 resources/processes 目录下。
spring security
activiti 7 集成了 spring security。也就是说,哪怕是你的项目本身使用的并不是 spring security,但只要你使用了 activiti 7,那么默认就已经启用了 spring security。
为了使用 Process 运行时,我们必须登录某个用户(这个用户必须具有 ROLE_ACTIVITI_USER
角色)。如果是在 Java 代码中调用 Process 运行时,我们可以在调用之前模拟登录。你可以使用 securityUtil 类的 logInAs 方法来模拟登录:securityUtil.logInAs(“system”);
因为我们的 app 是前后端分离的,所以我们的登录只能是通过浏览器进行(使用 Basic Auth)。具体的做法是在 Activiti7ApplicationConfiguration 中实现的:
@Configuration
@EnableWebSecurity
public class Activiti7ApplicationConfiguration extends WebSecurityConfigurerAdapter {
...
String[][] usersGroupsAndRoles = {
{"mbergljung", "1234", "ROLE_ACTIVITI_USER", "GROUP_activitiTraining"},
{"testuser", "1234", "ROLE_ACTIVITI_USER", "GROUP_activitiTraining"},
{"system", "1234", "ROLE_ACTIVITI_USER"},
{"admin", "1234", "ROLE_ACTIVITI_ADMIN"},
};
注意 @ EnableWebSecurity 注解。在这里添加了几个用户,前 3 个属于 ROLE_ACTIVITI_USER
角色,可用于 Basic Auth 登录。
同时也禁用了 crsf 防护:
http
.csrf().disable()
列出所有流程定义
这个是在控制器 ProcessDefinitionsController 中实现的:
@GetMapping("/process-definitions")
public List<ProcessDefinition> getProcessDefinitions() {
Page<ProcessDefinition> processDefinitionPage = processRuntime.processDefinitions(Pageable.of(0, 10));
logger.info("> Available Process definitions: " + processDefinitionPage.getTotalItems());
for (ProcessDefinition pd : processDefinitionPage.getContent()) {
logger.info("\t > Process definition: " + pd);
}
return processDefinitionPage.getContent();
}
用浏览器访问 http://localhost:8080/process-definitions,返回:
[{“id”:“5be497e7-4b82-11e9-be91-62c7d1691b1b”,“name”:“Sample Process”,“version”:1,“key”:“sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d”}]
如果你不小心使用了 admin 进行了登录,那么所有的 Process 引擎 API 调用都会返回 403 错误(因为它不属于
ROLE_ACTIVITI_USER
角色)。这时你可以先修改代码中 admin 的用户名为别的(或者删除 admin 用户),然后重新运行 app,此时刷新页面,浏览器又会弹出登录框,你就可以用其它 3 个用户登录,服务器调用就能正常返回。
启动流程
启动流程由 ProcessStartController 类实现:
@RequestMapping("/start-process")
public ProcessInstance startProcess(
@RequestParam(value="processDefinitionKey", defaultValue="SampleProcess") String processDefinitionKey) {
ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder
.start()
.withProcessDefinitionKey(processDefinitionKey)
.withProcessDefinitionKey("Sample Process: " + new Date())//.withProcessInstanceName("Sample Process: " + new Date())
.withVariable("someProcessVar", "someProcVarValue")
.build());
logger.info(">>> Created Process Instance: " + processInstance);
return processInstance;
}
现在,使用 ProcessPayloadBuilder 来构建一个流程实例。流程实例变量(全局变量)用 withVariable() 方法赋值。
用浏览器访问 http://localhost:8080/start-process?processDefinitionKey=sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d,启动流程。其中 sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d 是流程定义的 key。返回:
{“id”:“48519e31-4b86-11e9-8c1b-62c7d1691b1b”,“name”:“Sample Process: Thu Mar 21 11:06:12 CST 2019”,“processDefinitionId”:“2beace0f-4b86-11e9-8c1b-62c7d1691b1b”,“processDefinitionKey”:“sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d”,“startDate”:“2019-03-21T03:06:12.252+0000”,“status”:“RUNNING”,“processDefinitionVersion”:1}
记住这个流程实例的 id,后面会用到。
如何部署新的流程定义?
只需要你将 .bpmn/.bpmn20.xml 文件放到 resources/processes 目录下,重启 app,就可以自动部署新的流程定义。
列出流程实例
列出流程实例在 ProcessInstanceController 中实现:
@GetMapping("/process-instances")
public List<ProcessInstance> getProcessInstances() {
List<ProcessInstance> processInstances =
processRuntime.processInstances(Pageable.of(0, 10)).getContent();
return processInstances;
}
浏览器访问 http://localhost:8080/process-instances,返回:
[{“id”:“48519e31-4b86-11e9-8c1b-62c7d1691b1b”,“name”:“Sample Process: Thu Mar 21 11:06:12 CST 2019”,“processDefinitionId”:“2beace0f-4b86-11e9-8c1b-62c7d1691b1b”,“processDefinitionKey”:“sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d”,“startDate”:“2019-03-21T03:06:12.252+0000”,“status”:“RUNNING”,“processDefinitionVersion”:1}]
返回结果和创建流程时返回的一模一样,因为目前我们就只有这个流程实例。
列出流程实例的其它信息
列出流程实例在 ProcessInstanceController 中实现:
@GetMapping("/process-instance-meta")
public ProcessInstanceMeta getProcessInstanceMeta(@RequestParam(value="processInstanceId") String processInstanceId) {
ProcessInstanceMeta processInstanceMeta = processRuntime.processInstanceMeta(processInstanceId);
return processInstanceMeta;
}
访问:http://localhost:8080/process-instance-meta?processInstanceId=48519e31-4b86-11e9-8c1b-62c7d1691b1b(注意修改 processInstanceId 参数),返回:
{“processInstanceId”:“48519e31-4b86-11e9-8c1b-62c7d1691b1b”,“activeActivitiesIds”:[“UserTask_0b6cp1l”]}
activeActivitiesIds 返回的是当前流程正在执行的活动。
列出用户待办
在 TaskManagementController 中:
@GetMapping("/my-tasks")
public List<Task> getMyTasks() {
Page<Task> tasks = taskRuntime.tasks(Pageable.of(0, 10));
logger.info("> My Available Tasks: " + tasks.getTotalItems());
for (Task task : tasks.getContent()) {
logger.info("\t > My User Task: " + task);
}
return tasks.getContent();
}
访问 http://localhost:8080/my-tasks,返回:
[{“id”:“48543644-4b86-11e9-8c1b-62c7d1691b1b”,“name”:“User Task 1”,“status”:“ASSIGNED”,“assignee”:“testuser”,“createdDate”:“2019-03-21T03:06:12.262+0000”,“priority”:50,“processDefinitionId”:“2beace0f-4b86-11e9-8c1b-62c7d1691b1b”,“processInstanceId”:“48519e31-4b86-11e9-8c1b-62c7d1691b1b”}]
注意,只有 testuser 用户有待办。请登录 testuser。
如何切换 Basic Auth 用户
如果是 IE,请执行 Javascript
document.execCommand("ClearAuthenticationCache");
。
如果是 Chrome,请用shift + command + n
打开无痕模式,在这种模式下,总是需要输入密码。
列出所有待办
访问 http://localhost:8080/all-tasks。需要 admin 用户。
办理任务
访问 localhost:8080/complete-task?taskId=48543644-4b86-11e9-8c1b-62c7d1691b1b(注意检查 taskId 是否和你的匹配),需要 testuser 用户。返回:
Completed Task: 48543644-4b86-11e9-8c1b-62c7d1691b1b
监听器
传统的流程监听和任务监听是通过继承的方式实现的。这意味着这些代码和流程的执行是以同步的方式执行。在云部署的情况下,这通常不是一种好办法。Activiti 7 流程引擎采用了事件机制,这样我们就可以订阅事件了,这是异步的。
要实现流程监听需要实现 ProcessRuntimeEventListener 接口。这是由 MyProcessEventListener 完成的:
@Override
public void onEvent(RuntimeEvent runtimeEvent) {
if (runtimeEvent instanceof ProcessStartedEvent)
logger.info("Do something, process is started: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessCompletedEvent)
logger.info("Do something, process is completed: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessCancelledEvent)
logger.info("Do something, process is cancelled: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessSuspendedEvent)
logger.info("Do something, process is suspended: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessResumedEvent)
logger.info("Do something, process is resumed: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessCreatedEvent)
logger.info("Do something, process is created: " + runtimeEvent.toString());
else if (runtimeEvent instanceof SequenceFlowEvent)
logger.info("Do something, sequence flow is taken: " + runtimeEvent.toString());
else if (runtimeEvent instanceof VariableCreatedEvent)
logger.info("Do something, variable was created: " + runtimeEvent.toString());
else
logger.info("Unknown event: " + runtimeEvent.toString());
}
通过 spring 的自动装配机制,实现了 ProcessRuntimeEventListener 的监听器会自动装配成 spring bean,然后注入到 ProcessRuntime 中。
类似的,任务事件监听必须实现 TaskRuntimeEventListener 接口的 onEvent 方法,即 MyTaskEventListener 类:
public void onEvent(RuntimeEvent runtimeEvent) {
if (runtimeEvent instanceof TaskActivatedEvent)
logger.info("Do something, task is activated: " + runtimeEvent.toString());
else if (runtimeEvent instanceof TaskAssignedEvent) {
TaskAssignedEvent taskEvent = (TaskAssignedEvent)runtimeEvent;
Task task = taskEvent.getEntity();
logger.info("Do something, task is assigned: " + task.toString());
} else if (runtimeEvent instanceof TaskCancelledEvent)
logger.info("Do something, task is cancelled: " + runtimeEvent.toString());
else if (runtimeEvent instanceof TaskCompletedEvent)
logger.info("Do something, task is completed: " + runtimeEvent.toString());
else if (runtimeEvent instanceof TaskCreatedEvent)
logger.info("Do something, task is created: " + runtimeEvent.toString());
else if (runtimeEvent instanceof TaskSuspendedEvent)
logger.info("Do something, task is suspended: " + runtimeEvent.toString());
else
logger.info("Unknown event: " + runtimeEvent.toString());
}