盘点 Flow Activiti7 Task 入门

这是我参与更文挑战的第10天,活动详情查看: 更文挑战

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜

文章合集 : 🎁 juejin.cn/post/694164…

Github : 👉 github.com/black-ant

一 . 前言

此篇文档将开启 Activiti 的系列文档 , 所以这篇文章的内容主要以流程使用为主 , 为了更清晰 , 我们由 Task 往外层分析 >>>

后续再来看看 Process 实例的创建以及配置的处理

二 . 整体使用

2.1 Maven 依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
xml复制代码<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.0.0.Beta2</version>
</dependency>

<!-- 由于需要使用数据库 , 所以需要加入如下 DAO 框架 -->

<!-- DAO -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- PS : 该包主要是为了构建一个 DataSource-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

2.2 application.yml

PS : 无需创建数据库 , Activiti 中会默认创建表 , 创建流程我们以后来看 >>>

1
2
3
4
5
6
7
8
9
10
java复制代码spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/activiti007?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT&nullCatalogMeansCurrent=true
username : root
password : 123456
driver-class-name: com.mysql.jdbc.Driver
activiti:
database-schema-update: true
server:
port: 8086

默认会建如下表 :

image.png

2.3 前置准备

Activiti 默认是需要和用户绑定的 , 此处需要进行必要的配置 :

在缓存中添加2个用户

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
java复制代码@Configuration
public class SecurityConfiguration {

private Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);

@Bean
public UserDetailsService myUserDetailsService() {

InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
logger.info("> Registering new user: " + "root" + " with the following Authorities[ 'ACTIVE' , 'ADMIN' ]");

// 构建 Group 组信息
List<SimpleGrantedAuthority> groupList = new ArrayList<>();
// 注意 , 该权限是必须的
groupList.add(new SimpleGrantedAuthority("ROLE_ACTIVITI_USER"));
groupList.add(new SimpleGrantedAuthority("ADMIN"));

// 准备2个用户 : Root , Admin
inMemoryUserDetailsManager.createUser(new User("root", passwordEncoder().encode("123456"), groupList));
inMemoryUserDetailsManager.createUser(new User("admin", passwordEncoder().encode("123456"), groupList));

return inMemoryUserDetailsManager;
}


@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

}

模拟登录工具类

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
java复制代码@Component
public class SecurityUtil {

private Logger logger = LoggerFactory.getLogger(SecurityUtil.class);

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private PasswordEncoder passwordEncoder;

public void logInAs(String username) {

UserDetails user = userDetailsService.loadUserByUsername(username);

logger.info("> 用户安全配置 (1) : 简单校验用户是否存在 [{}]", username);
if (user == null) {
throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user");
}

logger.info("------> 用户安全配置 (2) , Security 中模拟登录对象 :{} <-------", username);
SecurityContextHolder.setContext(new SecurityContextImpl(new UsernamePasswordAuthenticationToken(user.getUsername(), "123456")));

logger.info("------> 用户安全配置 (3) , Activiti 中设置对象 :{} <-------", username);
org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
}
}

2.4 一个简单的 flow 流程

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
java复制代码@RestController
public class StartController {

private Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private ProcessRuntime processRuntime; //实现流程定义相关操作

@Autowired
private TaskRuntime taskRuntime; //实现任务相关操作

@Autowired
private SecurityUtil securityUtil;//SpringSecurity相关的工具类

@RequestMapping("/test")
public String test() {
logger.info("------> [成功进入 StartController] <-------");
return "Success !";
}

@GetMapping("/info")
public String getInfd() {
Page<ProcessDefinition> processDefinitionPage = processRuntime
.processDefinitions(Pageable.of(0, 10));
logger.info("------> 可用的流程定义数量:[{}] <-------", processDefinitionPage.getTotalItems());
for (ProcessDefinition pd : processDefinitionPage.getContent()) {
logger.info("------> 流程定义:[{}] <-------", pd);
}

return "success";
}

@GetMapping("/startFlow")
public String startFlow() {
securityUtil.logInAs("root");
ProcessInstance pi = processRuntime.start(ProcessPayloadBuilder
.start()
// processers 中定义的 .bpm 文件
.withProcessDefinitionKey("SimpleProcess")
.build());//启动流程实例

logger.info("------>流程实例ID: + [{}] <-------", pi.getId());
return "开启流程";
}

@GetMapping("/selectFlow")
public String selectFlow() {
securityUtil.logInAs("root");
Page<Task> taskPage = taskRuntime.tasks(Pageable.of(0, 10));
if (taskPage.getTotalItems() > 0) {
taskPage.getContent().forEach(item -> {
logger.info("------> 剩余任务 :[{}] <-------", JSONObject.toJSONString(item));
});
} else {
logger.info("------> 任务全部执行完成 <-------", taskPage.getContent());
}


return "查询 Flow : " + taskPage.getTotalItems();
}

@GetMapping("/doFlow")
public String doFlowBusiness() {

logger.info("------> [进入 doFlowBusiness 处理流程] <-------");
securityUtil.logInAs("root");
Page<Task> taskPage = taskRuntime.tasks(Pageable.of(0, 10));

logger.info("------> Task 启动完成 <-------");

if (taskPage.getTotalItems() > 0) {
for (Task task : taskPage.getContent()) {

logger.info("------> 循环处理任务 [{}] <-------", task.getName());

//拾取任务
taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build());

//执行任务
taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(task.getId()).build());
}
}

logger.info("------> 查询任务的结果 <-------");

Page<Task> taskPage2 = taskRuntime.tasks(Pageable.of(0, 10));
if (taskPage2.getTotalItems() > 0) {
logger.info("------> 剩余任务 :[{}] <-------", taskPage2.getContent());
} else {
logger.info("------> 任务全部执行完成 <-------", taskPage2.getContent());
}

return "Success : Do Flow Business 处理完成";
}

@GetMapping("deleteFlow")
public String deleteFlow() {
// PS : 此处如果是多个用户 , 需要切换用户
// securityUtil.logInAs("admin");
Page<Task> temTaskList = taskRuntime.tasks((Pageable.of(0, 10)));
temTaskList.getContent().forEach(item -> {
try {
logger.info("------> Step 4 item : 删除Task :{} <-------", item.getId());
taskRuntime.delete(TaskPayloadBuilder.delete().withTaskId(item.getId()).build());
} catch (Exception e) {
logger.error("E----> error :{} -- content :{}", e.getClass(), e.getMessage());
}

});
return "success";
}


}

三 . Task 核心流程

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
java复制代码@Component
public class ActivitiTaskRuntimeService implements ApplicationRunner {

private Logger logger = LoggerFactory.getLogger(this.getClass());

private static String taskId = "MyTask001";

@Autowired
private TaskRuntime taskRuntime;
@Autowired
private SecurityUtil securityUtil;

@Override
public void run(ApplicationArguments args) throws Exception {
logger.info("------> [开启一个完整的 Activiti 流程] , 首先模拟登录一个用户 <-------");

// PS : Activiti 默认依赖 Spring Security
securityUtil.logInAs("root");

deleteTask();

// 创建流程
createTask();

// 查询对象
getTaskInfo();
selectTaskInfo();

selectTaskInfoByUserId("admin");
selectTaskInfoByUserId("root");

// 执行流程
doTask();

// 再次查询流程
getTaskInfo();
selectTaskInfo();
}


public void getTaskInfo() {
try {
// Step 2 : 查询单个 Task 信息
Task temTask = taskRuntime.task(taskId);
logger.info("------> Step 2 查询 ID : {} - 对应的 Task :{} <-------", taskId, JSONObject.toJSONString(temTask));
} catch (NotFoundException e) {
logger.error("E----> 当前 Task 已经处理完成 , 未查询到 error :{} -- content :{}", e.getClass(), e.getMessage());
}
}

/**
* 查询当前的 Task 案例
*/
public void selectTaskInfo() {
// Step 2 : 查询已知的所有的 Task 信息
Pageable pageable = Pageable.of(0, 10);
Page<Task> temTaskList = taskRuntime.tasks(pageable);
temTaskList.getContent().forEach(item -> {
logger.info("------> Step 2-1 查询系列数量 - [{}] - 对应的 Task :{} <-------", temTaskList.getTotalItems(), JSONObject.toJSONString(item));
});

}

/**
* 对应委托人查询自己的任务
*/
public void selectTaskInfoByUserId(String assignee) {
// Step 2 : 查询已知的所有的 Task 信息
Pageable pageable = Pageable.of(0, 10);
Page<Task> temTaskList = taskRuntime.tasks(pageable, TaskPayloadBuilder.tasks().withAssignee(assignee).build());
temTaskList.getContent().forEach(item -> {
logger.info("------> Step 2-2 查询 assignee :{} 系列数量 - [{}] - 对应的 Task :{} <-------", assignee, temTaskList.getTotalItems(), JSONObject.toJSONString(item));
});

}


/**
* 创建一个 Task 任务
*/
public void createTask() {
logger.info("------> Step 1 : 创建一个 Task 开始 <-------");
CreateTaskPayload taskPayloadBuilder = TaskPayloadBuilder.create()
.withName("First Team Task")
.withDescription("This is something really important")
// 设置当前 Task 的用户组
.withGroup("ADMIN")
.withPriority(10)
.build();
Task temTask = taskRuntime.create(taskPayloadBuilder);

logger.info("------> Step 1 创建第二个 Task , 注意 , 此处设置了 Assignee <-------");
CreateTaskPayload taskPayloadBuilderTo = TaskPayloadBuilder.create()
.withName("Second Team Task")
.withDescription("This is something really important hava Assignee")
// 设置当前 Task 的用户组
.withGroup("ADMIN")
.withAssignee("admin")
.withPriority(10)
.build();
taskRuntime.create(taskPayloadBuilderTo);


this.taskId = temTask.getId();
}

/**
* 执行一个 Task
*/
public void doTask() {

logger.info("------> Step 3-1 : 声明一个 Task 开始 claimed <-------");
taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(taskId).build());
logger.info("------> Step 3-3 : 完成一个 Task 开始 complete <-------");
taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(taskId).build());
}

/**
* 删除 Task : PS : 注意 , 删除也需要权限
*/
public void deleteTask() {
logger.info("------> Step 4 : 删除所有的Task <-------");

Pageable pageable = Pageable.of(0, 10);
Page<Task> temTaskList = taskRuntime.tasks(pageable);
temTaskList.getContent().forEach(item -> {
try {
logger.info("------> Step 4 item : 删除Task :{} <-------", item.getId());
taskRuntime.delete(TaskPayloadBuilder.delete().withTaskId(item.getId()).build());
} catch (Exception e) {
logger.error("E----> error :{} -- content :{}", e.getClass(), e.getMessage());
}

});

securityUtil.logInAs("admin");
Pageable pageableAdmin = Pageable.of(0, 10);
Page<Task> temTaskListAdmin = taskRuntime.tasks(pageable);
temTaskListAdmin.getContent().forEach(item -> {
try {
logger.info("------> Step 4 item : 删除Task :{} <-------", item.getId());
taskRuntime.delete(TaskPayloadBuilder.delete().withTaskId(item.getId()).build());
} catch (Exception e) {
logger.error("E----> error :{} -- content :{}", e.getClass(), e.getMessage());
}
});

securityUtil.logInAs("root");


}
}

3.1 TaskRuntime 模块

接口一览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码C- TaskRuntime
public interface TaskRuntime {
TaskRuntimeConfiguration configuration();
Task task(String taskId);
Page tasks(Pageable pageable);
Page tasks(Pageable pageable, GetTasksPayload payload);
Task create(CreateTaskPayload payload);
Task claim(ClaimTaskPayload payload);
Task release(ReleaseTaskPayload payload);
Task complete(CompleteTaskPayload payload);
Task update(UpdateTaskPayload payload);
Task delete(DeleteTaskPayload payload);
...
}

方法作用

  • Task task(String taskId) : 通过id获取任务
  • Page tasks(Pageable pageable); 获取当前认证用户所有的 Task
  • Page tasks(Pageable pageable,GetTasksPayload getTasksPayload) : 获取在Payload中应用筛选器的所有任务
  • Task create(CreateTaskPayload createTaskPayload) : 创建 Task
  • Task claim(ClaimTaskPayload claimTaskPayload) : 声明一个 Task
    • 如果没有经过验证的用户抛出IllegalStateException
    • 如果当前认证用户不是一个候选用户抛出IllegalStateException
    • 当前方法不支持模拟,它将始终接受当前已验证的用户
    • 在声明之后,任务应该处于分配状态
  • Task release(ReleaseTaskPayload releaseTaskPayload) : 发布一个先前声明的任务
  • Task complete(CompleteTaskPayload completeTaskPayload) : 在有效负载中设置变量来完成选定的任务
    • 此方法还检查任务是否在完成之前分配给当前已验证的用户
    • 该方法返回一个浅层Task对象,其中包含验证任务已完成所需的基本信息
  • Task update(UpdateTaskPayload updateTaskPayload) : 更新任务详细信息
  • Task delete(DeleteTaskPayload deleteTaskPayload) : 删除任务

3.2 Task 对象

Task 是流程中的核心流转对象 , 来看一下该对象的参数有哪些 :

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
java复制代码public interface Task extends TaskInfo {

/**
* 创建新任务时优先级的默认值
*/
int DEFAULT_PRIORITY = 50;

/** 任务的名称或标题. */
void setName(String name);

/** 为任务设置可选的本地化名称. */
void setLocalizedName(String name);

/** 修改任务描述 */
void setDescription(String description);

/** 为任务设置可选的本地化描述. */
void setLocalizedDescription(String description);

/** 设定任务的重要性/紧迫性*/
void setPriority(int priority);

/**
* 负责此任务的人员的userId.
*/
void setOwner(String owner);

/**
* 被委派此任务的人的userId
*/
void setAssignee(String assignee);

/** 此任务的当前委派状态. */
DelegationState getDelegationState();

/** 此任务的当前委派状态。 */
void setDelegationState(DelegationState delegationState);

/**更改任务的到期日期 */
void setDueDate(Date dueDate);

/**
* 更改任务的类别。这是一个可选的字段,允许将任务标记为属于某个类别。
*/
void setCategory(String category);

/** 父任务 ID */
void setParentTaskId(String parentTaskId);

/** 修改任务的tenantId */
void setTenantId(String tenantId);

/** 更改任务的表单键 */
void setFormKey(String formKey);

/** 指示此任务是否挂起. */
boolean isSuspended();

}

3.3 TaskRuntimeImpl 模块

TaskRuntimeImpl 是 TaskRuntime 的实现类 , Task 处理中 ,通过该对象实现业务的具体操作 , 我们以删除操作为例 , 看一下整体的流程 :

Step 1 : Delete 的触发

在业务中 ,通过调用 方法触发对 Task 的删除操作

1
2
3
4
5
6
7
8
9
10
11
java复制代码    public void deleteTask() {
logger.info("------> Step 4 : 删除所有的Task <-------");
// 设置当前用户
securityUtil.logInAs("admin");
Pageable pageableAdmin = Pageable.of(0, 10);
Page<Task> temTaskListAdmin = taskRuntime.tasks(pageable);
temTaskListAdmin.getContent().forEach(item -> {
taskRuntime.delete(TaskPayloadBuilder.delete().withTaskId(item.getId()).build());
});

}

PS : 这里我们做了一个特殊操作 ->securityUtil.logInAs("admin");

这是因为 Task 的操作是由权限划分的 ,对应的人员只能操作自己的 Task

Step 2 : 调用 TaskRuntimeImpl # delete 方法

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
java复制代码 public Task delete(DeleteTaskPayload deleteTaskPayload) {
// 获取一个 Task
Task task;
try {
task = task(deleteTaskPayload.getTaskId());
} catch (IllegalStateException ex) {
throw new IllegalStateException("T....");
}

// 获取当前认证的 Userid
String authenticatedUserId = securityManager.getAuthenticatedUserId();
// 验证您是否试图删除您是受让人或所有者的任务
if ((task.getAssignee() == null
|| task.getAssignee().isEmpty()
|| !task.getAssignee().equals(authenticatedUserId))
&& (task.getOwner() == null
|| task.getOwner().isEmpty()
|| !task.getOwner().equals(authenticatedUserId))) {
throw new IllegalStateException(".....");
}

// 这里可以理解为通过原本的数据构建了一个同样的 Task
TaskImpl deletedTaskData = new TaskImpl(task.getId(),
task.getName(),
Task.TaskStatus.DELETED);

// 设置 Reason 原因
if (!deleteTaskPayload.hasReason()) {
deleteTaskPayload.setReason("Cancelled by " + authenticatedUserId);
}

// 执行 Service 删除 -> PS:0001
taskService.deleteTask(deleteTaskPayload.getTaskId(),
deleteTaskPayload.getReason(),
true);
return deletedTaskData;
}


// PS:0001 此处调用 taskService 进行处理 -> TaskServiceImpl
public void deleteTask(String taskId, String deleteReason, boolean cancel) {
commandExecutor.execute(new DeleteTaskCmd(taskId, deleteReason, false, cancel));
}

其中有2个需要注意的地方 :

  • C- DeleteTaskCmd : 该对象是一个命令对象 , 猜测这里是使用命令模式进行处理
  • C- commandExecutor 对象

来看一下对象是干嘛的 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码// commandExecutor 是一个接口 , 其中有三个方法
C- commandExecutor :
M- CommandConfig getDefaultConfig() : 获取默认的CommandConfig,如果没有提供就使用
M- <T> T execute(CommandConfig config, Command<T> command) : 使用指定的CommandConfig执行命令
M- <T> T execute(Command<T> command) : 使用默认的 CommandConfig执行命令

C- CommandExecutorImpl
I- commandExecutor

// 我们来看一下 , 执行了什么
public <T> T execute(CommandConfig config, Command<T> command) {
// 这里的 first 是 LogInterceptor
return first.execute(config, command);
}

PS : 到了 first.execute(config, command); 这一步 ,实际上就开始调用拦截器链了

Step 3 : 拦截器链的调用

拦截链的生成后面展示 , 这里只说一说执行了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码// 从前面我们分析 Delete 流程的时候就可以看到 , 其走了一个拦截链  ,如下图所示 : 

- CommandContextInterceptor
- CommandInvoker
- DebugCommandInvoker
- CommandInvoker
- JtaRetryInterceptor
- JtaTransactionInterceptor
- LoggingCommandInvoker
- SpringTransactionInterceptor
- TotalExecutionTimeCommandInterceptor
- TransactionContextInterceptor
- RetryInterceptor

avtiviti-AbstractCommandInterceptor.png

这些拦截链并不是全部会走 , 主要走的如下几个 :

  • Step 1 : SpringTransactionInterceptor : 控制事务
  • Step 2 : CommandContextInterceptor : 准备容器
  • Step 3 : TransactionContextInterceptor : 构建 TransactionContext
  • Step 4 : CommandInvoker 中准备 DbSqlSession , 通过 executeOperation # runnable.run() 执行处理线程
  • Step 5 : NeedsActiveTaskCmd 执行 TaskCmd
  • Step 6 : CompleteTaskCmd 发起 execute 执行 (PS : 操作类型在 Command 中 ,典型的命令模式)
  • Step End : TaskEntityManagerImpl 中执行具体的 DB 操作

总结

这一篇基本上把主流程大概介绍完了 , 后面来深入一下以下操作 :

  • SpringConfiguration 操作
  • 整体 Process 流程
  • 定制操作处理

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%