1. 概述
Spring Boot与Activiti的整合可以大大简化工作流应用的开发。Spring Boot提供了自动配置和依赖管理,而Activiti则提供了强大的工作流功能。通过整合,我们可以快速构建基于工作流的业务系统。
本文将详细介绍Spring Boot与Activiti的整合方法,并通过一个请假流程的例子来演示整合的效果。
2. 环境准备
2.1 开发环境
- JDK 1.8+
- Maven 3.5+
- Spring Boot 2.3.x (兼容Activiti 7.x)
- Activiti 7.x
- MySQL 5.7+
- IDE(IntelliJ IDEA或Eclipse)
2.2 Maven依赖
创建一个Spring Boot项目,在pom.xml
中添加以下依赖:
<!-- Activiti -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
</dependency>
2.3 配置文件
在src/main/resources
目录下创建application.yml
文件:
# Activiti配置(与Swagger冲突,两者只能开启一个)
activiti:
# 这里使用create_drop确保表被创建
database-schema-update: create_drop
db-history-used: true
history-level: full
check-process-definitions: true
# 明确指定流程定义位置
process-definition-location-prefix: classpath:/processes/
2.4 安全配置
由于Activiti 7与Spring Security集成,需要创建一个安全配置类:
package com.example.testlogin.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
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.core.authority.SimpleGrantedAuthority;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().configurationSource(corsConfigurationSource()) // 使用配置的CORS策略
.and()
.csrf().disable() // 禁用CSRF保护
.authorizeRequests()
.antMatchers("/api/login").permitAll()
.antMatchers("/api/excel/upload").permitAll()
.antMatchers("/api/excel/export").permitAll()
.antMatchers("/api/test/select").permitAll()
.antMatchers("/api/test/delete").permitAll()
.antMatchers("/api/test/upload").permitAll()
.antMatchers("/api/test/download").permitAll()
.antMatchers("/api/test/exportFile").permitAll()
.antMatchers("/api/test/importFile").permitAll()
.antMatchers("/api/test/importSheetFile").permitAll()
.antMatchers("/api/test/exportSheetFile").permitAll()
.antMatchers("/api/test/fillFile").permitAll()
.antMatchers("/api/test/fillFileList").permitAll()
.antMatchers("/api/test/fillFileSheetList").permitAll()
.antMatchers("/api/test/downloadFile").permitAll()
.antMatchers("/api/message/send").permitAll()
.antMatchers("/api/modbus/holdingRegisters").permitAll()
.antMatchers("/time/count").permitAll()
.antMatchers("/pic/**").permitAll()
.antMatchers("/files/**").permitAll()
.antMatchers("/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**", "/v2/api-docs/**", "/webjars/**")
.permitAll()// 允许访问swagger
.antMatchers("/webSocket/image").permitAll()// 允许未认证用户访问
.antMatchers("/chat").permitAll() // 允许访问WebSocket的chat端点
.antMatchers("/chat/*").permitAll() // 允许访问WebSocket的chat端点
// 重要修改:保护Activiti相关资源,要求认证
.antMatchers("/activiti/**", "/leave/**", "/process/**", "/task/**").authenticated()
// 静态资源可以公开访问
.antMatchers("/css/**", "/js/**", "/images/**").permitAll()
// 主页和其他公共页面
.antMatchers("/", "/index", "/home").permitAll()
// 其他请求需要认证
.anyRequest().authenticated()
.and()
// 使用默认登录页面 - 移除loginPage配置
.formLogin()
.defaultSuccessUrl("/index")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll();
// 允许HTTP基本认证(Activiti Rest API可能需要)
http.httpBasic();
// 允许iframe嵌入,用于Activiti表单和流程设计器
http.headers().frameOptions().sameOrigin();
}
@Bean
public UserDetailsService userDetailsService() {
// 创建用户
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
// 添加用户:员工
String[][] usersGroupsAndRoles = {
{"jack", "password", "ROLE_ACTIVITI_USER", "GROUP_employees"},
{"rose", "password", "ROLE_ACTIVITI_USER", "GROUP_employees"},
{"tom", "password", "ROLE_ACTIVITI_USER", "GROUP_employees"},
{"jerry", "password", "ROLE_ACTIVITI_USER", "GROUP_employees"},
{"manager", "password", "ROLE_ACTIVITI_USER", "GROUP_managers"},
{"hr", "password", "ROLE_ACTIVITI_USER", "GROUP_hr"},
{"admin", "password", "ROLE_ACTIVITI_ADMIN", "ROLE_ACTIVITI_USER"},
};
for (String[] user : usersGroupsAndRoles) {
List<String> authoritiesStrings = Arrays.asList(Arrays.copyOfRange(user, 2, user.length));
inMemoryUserDetailsManager.createUser(new User(
user[0],
passwordEncoder().encode(user[1]),
authoritiesStrings.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())
));
}
// 添加匿名用户
inMemoryUserDetailsManager.createUser(User.withUsername("anonymousUser")
.password("") // 空密码
.authorities("ROLE_ANONYMOUS")
.build());
return inMemoryUserDetailsManager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
2.5 启动类配置
创建Spring Boot启动类:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ActivitiSpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ActivitiSpringBootDemoApplication.class, args);
}
}
3. 业务模型设计
3.1 请假实体类
创建一个请假实体类:
package com.example.testlogin.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class LeaveRequest implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String userId;
private String userName;
private Date startDate;
private Date endDate;
private Integer days;
private String reason;
private String leaveType;// 请假类型:年假、病假、事假等
private String status; // 状态:草稿、提交、审批中、已批准、已拒绝
private String processInstanceId; // 流程实例ID
}
3.2 数据访问层
创建请假数据访问接口:
package com.example.testlogin.repository;
import com.example.testlogin.entity.LeaveRequest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Repository
public class LeaveRequestRepository {
private final JdbcTemplate jdbcTemplate;
public LeaveRequestRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// 创建请假表
public void createTable() {
jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS leave_request (" +
"id BIGINT AUTO_INCREMENT PRIMARY KEY," +
"user_id VARCHAR(100) NOT NULL," +
"user_name VARCHAR(100) NOT NULL," +
"start_date DATE NOT NULL," +
"end_date DATE NOT NULL," +
"days INT NOT NULL," +
"leave_type DATE NOT NULL," +
"reason VARCHAR(500)," +
"status VARCHAR(50) NOT NULL," +
"process_instance_id VARCHAR(100)" +
")");
}
// 保存请假申请
public void save(LeaveRequest leaveRequest) {
try {
System.out.println("保存请假申请: ID=" + leaveRequest.getId() +
", 申请人=" + leaveRequest.getUserName() +
", 状态=" + leaveRequest.getStatus());
if (leaveRequest.getId() == null) {
String sql = "INSERT INTO leave_request (user_id, user_name, start_date, end_date,leave_type, days, reason, status, process_instance_id) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
System.out.println("执行SQL: " + sql);
System.out.println("参数: [" +
leaveRequest.getUserId() + ", " +
leaveRequest.getUserName() + ", " +
leaveRequest.getStartDate() + ", " +
leaveRequest.getEndDate() + ", " +
leaveRequest.getLeaveType() + ", " +
leaveRequest.getDays() + ", " +
leaveRequest.getReason() + ", " +
leaveRequest.getStatus() + ", " +
leaveRequest.getProcessInstanceId() + "]");
jdbcTemplate.update(
sql,
leaveRequest.getUserId(),
leaveRequest.getUserName(),
leaveRequest.getStartDate(),
leaveRequest.getEndDate(),
leaveRequest.getLeaveType(),
leaveRequest.getDays(),
leaveRequest.getReason(),
leaveRequest.getStatus(),
leaveRequest.getProcessInstanceId()
);
System.out.println("插入请假申请成功");
} else {
String sql = "UPDATE leave_request SET user_id=?, user_name=?, start_date=?, end_date=?, leave_type=?, days=?, reason=?, status=?, process_instance_id=? " +
"WHERE id=?";
System.out.println("执行SQL: " + sql);
System.out.println("参数: [" +
leaveRequest.getUserId() + ", " +
leaveRequest.getUserName() + ", " +
leaveRequest.getStartDate() + ", " +
leaveRequest.getEndDate() + ", " +
leaveRequest.getLeaveType() + ", " +
leaveRequest.getDays() + ", " +
leaveRequest.getReason() + ", " +
leaveRequest.getStatus() + ", " +
leaveRequest.getProcessInstanceId() + ", " +
leaveRequest.getId() + "]");
int rowsAffected = jdbcTemplate.update(
sql,
leaveRequest.getUserId(),
leaveRequest.getUserName(),