这是我参与更文挑战的第10天,活动详情查看: 更文挑战
首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
一 . 前言
此篇文档将开启 Activiti 的系列文档 , 所以这篇文章的内容主要以流程使用为主 , 为了更清晰 , 我们由 Task 往外层分析 >>>
后续再来看看 Process 实例的创建以及配置的处理
二 . 整体使用
2.1 Maven 依赖
<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 中会默认创建表 , 创建流程我们以后来看 >>>
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
复制代码
默认会建如下表 :
2.3 前置准备
Activiti 默认是需要和用户绑定的 , 此处需要进行必要的配置 :
在缓存中添加2个用户
@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();
}
}
复制代码
模拟登录工具类
@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 流程
@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 核心流程
@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 模块
接口一览
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 是流程中的核心流转对象 , 来看一下该对象的参数有哪些 :
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 的删除操作
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 方法
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 对象
来看一下对象是干嘛的 :
// 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 : 拦截器链的调用
拦截链的生成后面展示 , 这里只说一说执行了什么
// 从前面我们分析 Delete 流程的时候就可以看到 , 其走了一个拦截链 ,如下图所示 :
- CommandContextInterceptor
- CommandInvoker
- DebugCommandInvoker
- CommandInvoker
- JtaRetryInterceptor
- JtaTransactionInterceptor
- LoggingCommandInvoker
- SpringTransactionInterceptor
- TotalExecutionTimeCommandInterceptor
- TransactionContextInterceptor
- RetryInterceptor
复制代码
这些拦截链并不是全部会走 , 主要走的如下几个 :
- 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 流程
- 定制操作处理
近期评论