activities的动词搭配(Activiti7+SpringBoot)
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
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配置的是*) ,要注意将新加的这个仓库排除
https://maven.apache.org/guides/mini/guide-mirror-settings.html
举个例子:
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启动项目后 ,生成的表结构如图:
也可以自己执行SQL脚本 ,脚本在源码包activiti-engine-7.6.1.jar里面
由于Activit7集成了SpringSecurity ,它用SpringSecurity来做权限管理 ,因此它需要一个UserDetailsService ,不配做的话启动会报错 。当然网上也有一种解决办法就是排除SpringSecurity相关的某些类 ,没试过 ,应该也是可以的吧 。
按照Spring Security 5.7以后的新写法 ,我们来配置一下WebSecurity
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
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权限
因此 ,在使用这两个类之前要保证当前登录用户有这个权限 。为了模拟登录 ,我们来写个登录方法 。
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://gitee.com/chengjiansheng/activiti7-sample
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!