1. Introduce dependencies
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.0.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <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-test</artifactId> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>7.0.0.Beta2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.29</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
2. SpringBoot application.yml file configuration
Note: activiti7 does not enable database history by default and needs to be manually configured to enable it.
spring: datasource: url: jdbc:mysql:///activiti7?useUnicode=true & characterEncoding=utf8 & serverTimezone=GMT username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver Activiti: #1.flase: default value. When activiti starts, it compares the versions saved in the database tables. If there is no table or the versions do not match, an exception will be thrown. #2.true: activiti will update all tables in the database. If the table does not exist, it is automatically created #3.create_drop: Create the table when activiti starts and delete the table when it closes (the engine must be shut down manually to delete the table) #4.drop-create: Delete the original old table when activiti starts, and then create a new table (no need to shut down the engine manually) database-schema-update: true #Detect whether the history table exists. Activiti7 does not enable database history by default. Start database history. db-history-used: true #Record history level Configurable history levels include none, activity, audit, full #none: No historical data is saved, so this is the most efficient during process execution. #activity: The level is higher than none, the process instance and process behavior are saved, and other data is not saved. #audit: In addition to the data saved at the activity level, all process tasks and their attributes will also be saved. audit is the default value of history. #full: The highest level for saving historical data. In addition to saving audit-level data, it will also save all other process-related detailed data, including some process parameters, etc. history-level: full #Verify process files. By default, the process files in the processes folder under resources are verified. check-process-definitions: false
3. Startup class
@SpringBootApplication public class AppApplication {<!-- --> public static void main(String[] args) {<!-- --> SpringApplication.run(AppApplication.class, args); } }
4. Add SpringSecurity security framework integration configuration
Because after Activiti7 is integrated with SpringBoot, the SpringSecurity security framework is integrated by default, so we need to prepare the relevant user permission configuration information integrated by SpringSecurity.
SpringBoot’s dependency package has also added SpringSecurity’s dependency package to the project.
4.1, ### Add SecurityUtil class
A component added in order to quickly implement the configuration of the SpringSecurity security framework.
@Component public class SecurityUtil {<!-- --> private Logger logger = LoggerFactory.getLogger(SecurityUtil.class); @Autowired @Qualifier("myUserDetailsService") private UserDetailsService userDetailsService; public void logInAs(String username) {<!-- --> UserDetails user = userDetailsService.loadUserByUsername(username); if (user == null) {<!-- --> throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user"); } logger.info("> Logged in as: " + 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 b) throws IllegalArgumentException {<!-- --> } @Override public String getName() {<!-- --> return user.getUsername(); } } ) ); org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username); } }
4.2, ### Add DemoApplicationConfig class
Find the DemoApplicationConfig class in the officially downloaded Example of Activiti7. Its function is to implement the configuration of user permissions of the SpringSecurity framework, so that we can use user permission information in the system.
In this project, it is basically the user information defined in the file. Of course, it can also be the user permission information queried in the database.
The task leader who will be used later in the process needs to be added here.
@Configuration public class ActivitiConfiguration {<!-- --> private Logger logger = LoggerFactory.getLogger(ActivitiConfiguration.class); @Bean public UserDetailsService myUserDetailsService() {<!-- --> InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(); //Add the user here. The task leader who will be used later when processing the process needs to be added here. String[][] usersGroupsAndRoles = {<!-- --> {<!-- -->"jack", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {<!-- -->"rose", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {<!-- -->"tom", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {<!-- -->"other", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"}, {<!-- -->"system", "password", "ROLE_ACTIVITI_USER"}, {<!-- -->"admin", "password", "ROLE_ACTIVITI_ADMIN"}, }; for (String[] user : usersGroupsAndRoles) {<!-- --> List<String> authoritiesStrings = Arrays.asList(Arrays.copyOfRange(user, 2, user.length)); logger.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]"); inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]), authoritiesStrings.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList()))); } return inMemoryUserDetailsManager; } @Bean public PasswordEncoder passwordEncoder() {<!-- --> return new BCryptPasswordEncoder(); } }
5. Create Bpmn file
Activiti7 can automatically deploy the process. The premise is to create a new directory processes in the resources directory to place the bpmn file.
Create a simple Bpmn process file and set the task’s user group Candidate Groups.
The content in Candidate Groups must be consistent with the user group name appearing in the DemoApplicationConfiguration class above. You can fill in: activitiTeam or otherTeam.
The advantage of filling in this way: When you are not sure who is responsible for the current task, as long as you are a user in Groups, you can pick up the task.
<process id="myProcess" isClosed="false" isExecutable="true" processType="None"> <startEvent id="_2" name="StartEvent"/> <endEvent id="_3" name="EndEvent"/> <userTask activiti:candidateGroups="activitiTeam" activiti:exclusive="true" id="_4" name="UserTask"/> <userTask activiti:candidateGroups="activitiTeam" activiti:exclusive="true" id="_5" name="UserTask"/> <sequenceFlow id="_6" sourceRef="_2" targetRef="_4"/> <sequenceFlow id="_7" sourceRef="_4" targetRef="_5"/> <sequenceFlow id="_8" sourceRef="_5" targetRef="_3"/> </process>
6. Test
@RunWith(SpringRunner.class) @SpringBootTest public class AppApplicationTests {<!-- --> @Autowired private ProcessRuntime processRuntime; @Autowired private TaskRuntime taskRuntime; @Autowired private SecurityUtil securityUtil; @Test public void testApp() {<!-- --> System.out.println(taskRuntime); } /** * View process definition */ @Test public void contextLoads() {<!-- --> securityUtil.logInAs("system"); Page<ProcessDefinition> processDefinitionPage = processRuntime.processDefinitions(Pageable.of(0, 10)); System.out.println("Number of available process definitions:" + processDefinitionPage.getTotalItems()); for (ProcessDefinition pd : processDefinitionPage.getContent()) {<!-- --> System.out.println("Process definition:" + pd); } } @Test public void testStartProcess() {<!-- --> securityUtil.logInAs("system"); ProcessInstance pi = processRuntime.start(ProcessPayloadBuilder.start().withProcessDefinitionKey("myProcess").build()); System.out.println("Process instance ID:" + pi.getId()); } /** * Query tasks and complete your own tasks */ @Test public void testTask() {<!-- --> securityUtil.logInAs("jack"); Page<Task> tasks = taskRuntime.tasks(Pageable.of(0, 10)); if (tasks.getTotalItems() > 0) {<!-- --> for (Task task : tasks.getContent()) {<!-- --> taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build()); System.out.println("Task:" + task); taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(task.getId()).build()); } } Page<Task> taskPage = taskRuntime.tasks(Pageable.of(0, 10)); if (taskPage.getTotalItems() > 0) {<!-- --> System.out.println("Task:" + taskPage.getContent()); } } }