Это 10-й день моего участия в Gengwen Challenge.Подробности о мероприятии:Обновить вызов
Прежде всего, поделитесь всеми предыдущими статьями, ставьте лайки, добавляйте в избранное и пересылайте три раза подряд. >>>>😜😜😜
Сборник статей:🎁nuggets.capable/post/694164…
Github :👉github.com/black-ant
Введение
Этот документ откроет серию документов Activiti, поэтому содержание этой статьи в основном основано на использовании процессов.Для ясности мы проанализируем его от задачи до внешнего слоя >>>
Давайте рассмотрим создание экземпляров Process и обработку конфигурации позже.
2. Общее использование
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 Простой потоковый процесс
@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";
}
}
3. Основной процесс задачи
@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
- Задачи страницы (Pageable pageable); Получить все задачи текущего аутентифицированного пользователя
- Задачи страницы (Pageable pageable, GetTasksPayload, getTasksPayload): получить все задачи в Payload с применением фильтра.
- Создание задачи (CreateTaskPayload createTaskPayload): Создать задачу
- Заявление о задаче (ClaimTaskPayload ClaimTaskPayload): запрос на задание
- Выбрасывает IllegalStateException, если нет аутентифицированного пользователя
- Выдает исключение IllegalStateException, если текущий аутентифицированный пользователь не является пользователем-кандидатом
- Текущий метод не поддерживает олицетворение, он всегда будет принимать текущего аутентифицированного пользователя.
- После объявления задача должна находиться в назначенном состоянии
- Выпуск задачи (ReleaseTaskPayload releaseTaskPayload): выпустить ранее объявленную задачу.
- Задача завершена (CompleteTaskPayload completeTaskPayload): установите переменные в полезной нагрузке для завершения выбранной задачи.
- Этот метод также проверяет, назначена ли задача текущему аутентифицированному пользователю перед завершением.
- Метод возвращает неглубокий объект Task, содержащий основную информацию, необходимую для проверки выполнения задачи.
- Обновление задачи (UpdateTaskPayload updateTaskPayload): обновить сведения о задаче.
- Удалить задачу (DeleteTaskPayload deleteTaskPayload): удалить задачу
3.2 Объекты задачи
Задача — это основной объект потока в процессе, давайте посмотрим на параметры этого объекта:
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. Во время обработки Задачи конкретные бизнес-операции реализуются через этот объект. Давайте возьмем в качестве примера операцию удаления, чтобы увидеть весь процесс:
Шаг 1: Триггер удаления
В бизнесе удаление Задачи запускается вызовом метода
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");
Это связано с тем, что операции задач разделены разрешениями, и соответствующий персонал может выполнять только свои собственные задачи.
Шаг 2: Вызовите метод удаления TaskRuntimeImpl #
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: сначала.execute(config, command); на этом этапе фактически вызывается цепочка перехватчиков
Шаг 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: Подготовить DbSqlSession в CommandInvoker, выполнить поток обработки через executeOperation # runnable.run()
- Step 5: NeedsActiveTaskCmd выполнить TaskCmd
- Step 6: CompleteTaskCmd инициирует выполнение выполнения (PS : тип операции — Command,Типичный шаблон команды)
- Step End: выполнение определенных операций с БД в TaskEntityManagerImpl.
Суммировать
В этой статье в основном представлен основной процесс, а затем мы углубимся в следующие операции:
- Действия SpringConfiguration
- Общий процесс
- Пользовательская обработка действий