【工作流Activiti7】7、Activiti7+SpringBoot

人生之路不会是一帆风顺的,我们会遇上顺境,也会遇上逆境,在所有成功路上折磨你的,背后都隐藏着激励你奋发向上的动机,人生没有如果,只有后果与结果,成熟,就是用微笑来面对一切小事。

导读:本篇文章讲解 【工作流Activiti7】7、Activiti7+SpringBoot,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

1. 版本问题

1.1. Activiti版本

7.1.0-M6是最后一个支持JDK1.8的版本,此后的版本都要求JDK11以上

目前,Activiti最新版本是7.6.0,它是用JDK11编译的,因此要想使用最新版7.6.0必须升级JDK版本,不能再用1.8

同时,7.6.0依赖的SpringBoot版本是2.7.5

【工作流Activiti7】7、Activiti7+SpringBoot

1.2. SpringBoot版本

最新的SpringBoot版本是3.0.0,这个版本不支持JDK1.8,对JDK的最小版本是17

目前可用比较多的Java版本是Java 17 和 Java 19

综合来看,我们采用Java 17+SpringBoot 2.7.5+Activiti 7.6.1

补充:52 = Java 8  55 = Java 11

2. Maven仓库设置

首先要添加Activiti组件的仓库,不然找不到jar包,可用配置在全局的settings.xml文件中,也可以配置在项目pom.xml中

<repositories>
    <repository>
        <id>activiti-releases</id>
        <url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases</url>
    </repository>
</repositories>

如果settings.xml中有镜像,并且所有镜像都使用一个仓库的话(即,morrorOf配置的是*),要注意将新加的这个仓库排除

Maven – Guide to Mirror Settings

【工作流Activiti7】7、Activiti7+SpringBoot

举个例子:

【工作流Activiti7】7、Activiti7+SpringBoot

3. 依赖管理

Activiti依赖管理

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-dependencies</artifactId>
            <version>7.6.1</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

Dubbo依赖管理

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-bom</artifactId>
            <version>3.1.3</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

完整的父POM如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <!--<version>3.0.0</version>-->
        <version>2.7.5</version>
        <relativePath/>
    </parent>
    <groupId>com.cjs.example</groupId>
    <artifactId>activiti7-sample</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>

    <name>activiti7-sample</name>

    <modules>
        <module>activiti7-sample-provider-api</module>
        <module>activiti7-sample-provider</module>
    </modules>

    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <!--
            <dependency>
                <groupId>org.activiti</groupId>
                <artifactId>activiti-spring-boot-starter</artifactId>
                <version>7.1.0.M6</version>
            </dependency>
            -->
            <dependency>
                <groupId>org.activiti</groupId>
                <artifactId>activiti-dependencies</artifactId>
                <version>7.6.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-bom</artifactId>
                <version>3.1.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.5.2</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>activiti-releases</id>
            <url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases</url>
        </repository>
    </repositories>
</project>

4. Activiti  API使用

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.cjs.example</groupId>
        <artifactId>activiti7-sample</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <groupId>com.cjs.example</groupId>
    <artifactId>activiti7-sample-provider</artifactId>
    <version>${parent.version}</version>

    <name>activiti7-sample-provider</name>

    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.cjs.example</groupId>
            <artifactId>activiti7-sample-provider-api</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-nacos</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

server:
  port: 8080
  servlet:
    context-path: /activiti7
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/activiti7?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&nullCatalogMeansCurrent=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
  activiti:
    check-process-definitions: false
    database-schema-update: "false" # 第一次运行时设置为true,待表生成完以后改成false,以后就不用再更新表结构了
    db-history-used: true
    history-level: full
    deployment-mode: "never-fail" # org.activiti.spring.autodeployment.AbstractAutoDeploymentStrategy
dubbo:
  application:
    name: activiti7-sample-provider
  protocol:
    name: dubbo
    port: -1
  registry:
    address: nacos://127.0.0.1:8848
  config-center:
    address: nacos://127.0.0.1:8848
  metadata-report:
    address: nacos://127.0.0.1:8848

启动项目后,生成的表结构如图:

【工作流Activiti7】7、Activiti7+SpringBoot

也可以自己执行SQL脚本,脚本在源码包activiti-engine-7.6.1.jar里面

【工作流Activiti7】7、Activiti7+SpringBoot

由于Activit7集成了SpringSecurity,它用SpringSecurity来做权限管理,因此它需要一个UserDetailsService,不配做的话启动会报错。当然网上也有一种解决办法就是排除SpringSecurity相关的某些类,没试过,应该也是可以的吧。

按照Spring Security 5.7以后的新写法,我们来配置一下WebSecurity

Java Configuration :: Spring Security

https://github.com/spring-projects/spring-security-samples/tree/6.0.x/servlet/spring-boot/java/oauth2

package com.cjs.example.provider.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

/**
 * https://docs.spring.io/spring-security/reference/servlet/configuration/java.html
 * https://github.com/spring-projects/spring-security-samples/tree/6.0.x/servlet/spring-boot/java/oauth2
 *
 * @Author: ChengJianSheng
 * @Date:   2022/12/5
 */
@Configuration
@EnableWebSecurity
public class MyWebSecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests(authorize -> authorize.anyRequest().authenticated())
                .formLogin(Customizer.withDefaults());
        return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring()
                // Spring Security should completely ignore URLs starting with /resources/
                .antMatchers("/resources/**");
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails admin = User.withUsername("tom")
                .password("$2a$10$tk29HhXGXCZDOSvn.VZlFeBsLmtJrKE2Uv6zLrRpTyvZMu3ipQLgC")
                .roles("ACTIVITI_USER", "ACTIVITI_ADMIN", "APPLICATION_MANAGER")
                .build();
        UserDetails user = User.withUsername("jerry")
                .password("$2a$10$tk29HhXGXCZDOSvn.VZlFeBsLmtJrKE2Uv6zLrRpTyvZMu3ipQLgC")
                .roles("ACTIVITI_USER", "GROUP_BUSINESS_MANAGER")
                .build();
        UserDetails zhangsan = User.withUsername("zhangsan")
                .password("$2a$10$tk29HhXGXCZDOSvn.VZlFeBsLmtJrKE2Uv6zLrRpTyvZMu3ipQLgC")
                .roles("ACTIVITI_USER")
                .build();
        UserDetails lisi = User.withUsername("lisi")
                .password("$2a$10$tk29HhXGXCZDOSvn.VZlFeBsLmtJrKE2Uv6zLrRpTyvZMu3ipQLgC")
                .roles("ACTIVITI_USER")
                .build();
        return new InMemoryUserDetailsManager(admin, user, zhangsan, lisi);
    }

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

    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123456"));
    }
}

在activiti-spring-boot-starter中新提供了ProcessRuntime 和 TaskRuntime用于流程和任务处理的API,调用它们需要当前操作的用户具有ACTIVITI_USER权限

【工作流Activiti7】7、Activiti7+SpringBoot

【工作流Activiti7】7、Activiti7+SpringBoot

【工作流Activiti7】7、Activiti7+SpringBoot

因此,在使用这两个类之前要保证当前登录用户有这个权限。为了模拟登录,我们来写个登录方法。

package com.cjs.example.provider.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;

import java.util.Collection;

/**
 * @Author: ChengJianSheng
 * @Date: 2022/12/6
 */
@Component
public class SecurityUtil {

    @Autowired
    private UserDetailsService userDetailsService;

    public void logInAs(String username) {
        UserDetails user = userDetailsService.loadUserByUsername(username);
        if (null == user) {
            throw new IllegalStateException(String.format("用户【%s】不存在", username));
        }
        SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() {
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                return user.getAuthorities();
            }

            @Override
            public Object getCredentials() {
                return user.getPassword();
            }

            @Override
            public Object getDetails() {
                return user;
            }

            @Override
            public Object getPrincipal() {
                return user;
            }

            @Override
            public boolean isAuthenticated() {
                return true;
            }

            @Override
            public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {

            }

            @Override
            public String getName() {
                return user.getUsername();
            }
        }));
        org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
    }
}

现在可以开始调用API操作流程了

package com.cjs.example.provider.service;

import com.cjs.example.provider.api.WorkflowService;
import com.cjs.example.provider.util.SecurityUtil;
import org.activiti.api.process.model.ProcessInstance;
import org.activiti.api.process.model.builders.ProcessPayloadBuilder;
import org.activiti.api.process.model.payloads.GetProcessInstancesPayload;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.api.runtime.shared.query.Order;
import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable;
import org.activiti.api.task.model.Task;
import org.activiti.api.task.model.builders.TaskPayloadBuilder;
import org.activiti.api.task.runtime.TaskRuntime;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.repository.Deployment;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Map;

/**
 * @Author: ChengJianSheng
 * @Date: 2022/12/6
 */
@DubboService
public class WorkflowServiceImpl implements WorkflowService {

    @Autowired
    private ProcessRuntime processRuntime;

    @Autowired
    private TaskRuntime taskRuntime;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private SecurityUtil securityUtil;


    @Override
    public void deploy() {
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("processes/leave.bpmn20.xml")
                .name("请假")
                .key("leave")
                .category("AAA")
                .tenantId("QingJia")
                .deploy();
    }

    @Override
    public void start() {
        securityUtil.logInAs("tom");
        ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder.start()
                .withProcessDefinitionKey("leave")
                .withVariable("hello", "world")
                .withVariable("apple", "orange")
                .withBusinessKey("1")
                .build());
    }

    @Override
    public void startProcessInstance(String processDefinitionKey, String businessKey, Map<String, Object> variables, String tenantId) {
        runtimeService.startProcessInstanceByKeyAndTenantId(processDefinitionKey, businessKey, variables, tenantId);
    }

    @Override
    public void processInstancePage(int pageNo, int pageSize) {
        securityUtil.logInAs("tom");
        GetProcessInstancesPayload payload = new GetProcessInstancesPayload();
        payload.setActiveOnly(true);
        Page<ProcessInstance> page = processRuntime.processInstances(Pageable.of(pageNo-1, pageSize, Order.by("start_time_", Order.Direction.DESC)), payload);
        for (ProcessInstance ps : page.getContent()) {
            System.out.println(ps);
        }
    }

    @Override
    public void taskList() {
        securityUtil.logInAs("zhangsan");
        Page<Task> taskPage = taskRuntime.tasks(Pageable.of(0, 10));
        System.out.println(taskPage.getTotalItems());

        securityUtil.logInAs("lisi");
        taskPage = taskRuntime.tasks(Pageable.of(0, 10));
        System.out.println(taskPage.getTotalItems());
    }

    @Override
    public void claimTask() {
        securityUtil.logInAs("zhangsan");
        String taskId = "429ad159-754c-11ed-aaf8-84a9386654d8";
        Task task = taskRuntime.task(taskId);
        taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(taskId).build());
    }

    @Override
    public void completeTask() {
        securityUtil.logInAs("zhangsan");
        String taskId = "429ad159-754c-11ed-aaf8-84a9386654d8";
        taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(taskId).build());
    }
}

注意:可以把tenantId看成是某个业务,businessKey当做业务ID,比如:放款流程001。或者建一张中间表用于关联流程ID和业务ID。

最后,配置流程监听器

package com.cjs.example.provider.config;

import lombok.extern.slf4j.Slf4j;
import org.activiti.api.process.runtime.events.ProcessCompletedEvent;
import org.activiti.api.process.runtime.events.listener.ProcessRuntimeEventListener;
import org.activiti.api.task.runtime.events.TaskAssignedEvent;
import org.activiti.api.task.runtime.events.TaskCompletedEvent;
import org.activiti.api.task.runtime.events.listener.TaskRuntimeEventListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author: ChengJianSheng
 * @Date: 2022/12/6
 */
@Slf4j
@Configuration
public class ListenerConfig {

    @Bean
    public ProcessRuntimeEventListener<ProcessCompletedEvent> processCompletedListener() {
        return processCompleted -> log.info(">>> Process Completed: '"
                + processCompleted.getEntity().getName() +
                "' We can send a notification to the initiator: " + processCompleted.getEntity().getInitiator());
    }

    @Bean
    public TaskRuntimeEventListener<TaskAssignedEvent> taskAssignedListener() {
        return taskAssigned -> log.info(">>> Task Assigned: '"
                + taskAssigned.getEntity().getName() +
                "' We can send a notification to the assginee: " + taskAssigned.getEntity().getAssignee());
    }

    @Bean
    public TaskRuntimeEventListener<TaskCompletedEvent> taskCompletedListener() {
        return taskCompleted -> log.info(">>> Task Completed: '"
                + taskCompleted.getEntity().getName() +
                "' We can send a notification to the owner: " + taskCompleted.getEntity().getOwner());
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/124459.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!