1. 项目背景与核心需求
在企业管理系统中,OA请假流程是最基础也是最常用的功能之一。基于Jeecgboot3.9.0和flowable7.2.0实现这样一个功能,实际上是在验证一个完整的工作流集成方案。我最近在重构公司内部管理系统时,就遇到了这样的需求场景。
传统的请假审批流程通常包含以下几个关键节点:申请人填写表单→直属领导审批→人事备案→结果通知。在Jeecgboot框架下,我们需要将这些业务逻辑与flowable的工作流引擎无缝衔接。这个过程中最大的挑战在于如何将表单数据与流程实例绑定,以及如何处理各种审批路径的分支逻辑。
2. 环境准备与基础配置
2.1 Jeecgboot3.9.0项目初始化
首先需要确保Jeecgboot的基础环境配置正确。我推荐使用以下Maven依赖配置:
xml复制<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-base-starter</artifactId>
<version>3.9.0</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>7.2.0</version>
</dependency>
数据库方面,需要同时准备业务数据库和flowable的流程引擎数据库。在实际部署时,我建议将这两个数据库分开,这样既方便管理也利于性能优化。在application.yml中需要配置双数据源:
yaml复制spring:
datasource:
dynamic:
primary: master
datasource:
master:
url: jdbc:mysql://localhost:3306/jeecg-boot?useSSL=false
username: root
password: 123456
flowable:
url: jdbc:mysql://localhost:3306/flowable7?useSSL=false
username: root
password: 123456
2.2 Flowable7.2.0引擎配置
Flowable的配置需要特别注意事务管理器的设置。在项目中我采用了如下的Java配置类:
java复制@Configuration
public class FlowableConfig {
@Bean
public SpringProcessEngineConfiguration processEngineConfiguration(
@Qualifier("flowableDataSource") DataSource dataSource,
PlatformTransactionManager transactionManager) {
SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration();
config.setDataSource(dataSource);
config.setTransactionManager(transactionManager);
config.setDatabaseSchemaUpdate(FlowableProperties.DATABASE_SCHEMA_UPDATE_TRUE);
config.setAsyncExecutorActivate(true);
config.setHistoryLevel(HistoryLevel.FULL);
return config;
}
}
3. 请假流程设计与实现
3.1 流程定义建模
使用Flowable Modeler设计请假流程时,我通常会先绘制流程图。一个标准的请假审批流程应该包含以下节点:
- 开始事件
- 申请人填写表单(用户任务)
- 部门领导审批(用户任务)
- 人事备案(服务任务)
- 结束事件
在bpmn20.xml文件中,关键的用户任务配置如下:
xml复制<userTask id="leaderAudit" name="部门领导审批"
flowable:assignee="${applyUserId}_leader">
<extensionElements>
<flowable:formProperty id="auditResult"
name="审批结果"
type="enum"
required="true">
<flowable:value id="agree" name="同意"/>
<flowable:value id="reject" name="拒绝"/>
</flowable:formProperty>
</extensionElements>
</userTask>
3.2 动态表单设计与数据绑定
Jeecgboot的表单设计器可以很好地与Flowable集成。在实现请假单功能时,我创建了以下核心字段:
- 请假类型(病假/年假/事假)
- 开始时间
- 结束时间
- 请假天数
- 请假事由
在Vue3前端,表单提交的关键代码如下:
javascript复制const submitForm = async () => {
const formData = {
...formState,
processDefinitionKey: 'leave_process'
};
const { data } = await startProcessInstance(formData);
if (data.success) {
message.success('请假申请已提交');
}
};
后端Controller需要处理流程实例的启动:
java复制@PostMapping("/startLeaveProcess")
public Result<?> startLeaveProcess(@RequestBody LeaveApplyDTO dto) {
// 1. 保存业务数据
LeaveApply entity = new LeaveApply();
BeanUtils.copyProperties(dto, entity);
leaveApplyService.save(entity);
// 2. 启动流程实例
Map<String, Object> variables = new HashMap<>();
variables.put("applyUserId", dto.getUserId());
variables.put("leaveId", entity.getId());
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
dto.getProcessDefinitionKey(),
variables);
// 3. 关联业务与流程
entity.setProcessInstanceId(instance.getProcessInstanceId());
leaveApplyService.updateById(entity);
return Result.OK("流程启动成功");
}
4. 审批功能实现细节
4.1 任务列表获取
审批人需要看到待处理的请假申请列表。在Service层,我实现了如下查询逻辑:
java复制public List<TaskDTO> getTodoTasks(String userId) {
// 查询个人任务
List<Task> tasks = taskService.createTaskQuery()
.taskCandidateOrAssigned(userId)
.orderByTaskCreateTime().desc()
.list();
// 查询组任务(部门领导角色)
List<Task> groupTasks = taskService.createTaskQuery()
.taskCandidateGroup("dept_leader")
.taskUnassigned()
.list();
// 合并结果
List<Task> allTasks = new ArrayList<>();
allTasks.addAll(tasks);
allTasks.addAll(groupTasks);
return allTasks.stream().map(task -> {
TaskDTO dto = new TaskDTO();
// 填充任务属性
// 查询关联的业务数据
return dto;
}).collect(Collectors.toList());
}
4.2 审批操作处理
当领导进行审批时,需要完成以下操作:
- 记录审批意见
- 更新业务数据状态
- 完成任务并流转流程
关键代码如下:
java复制@Transactional
public Result<?> completeTask(TaskCompleteDTO dto) {
// 1. 查询任务
Task task = taskService.createTaskQuery()
.taskId(dto.getTaskId())
.singleResult();
// 2. 设置流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("auditResult", dto.getAuditResult());
variables.put("comment", dto.getComment());
// 3. 完成任务
taskService.complete(task.getId(), variables);
// 4. 更新业务状态
LeaveApply apply = leaveApplyService.getById(dto.getBusinessKey());
apply.setStatus("audited");
leaveApplyService.updateById(apply);
return Result.OK("审批完成");
}
5. 流程监控与异常处理
5.1 流程状态追踪
在请假单详情页,我们需要展示当前流程的进展。这可以通过Flowable的历史服务实现:
java复制public ProcessStatusVO getProcessStatus(String processInstanceId) {
ProcessStatusVO vo = new ProcessStatusVO();
// 获取历史活动实例
List<HistoricActivityInstance> activities = historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceStartTime().asc()
.list();
// 获取当前活动任务
List<Task> currentTasks = taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.list();
// 构建响应数据
vo.setHistoryActivities(activities);
vo.setCurrentTasks(currentTasks);
return vo;
}
5.2 常见问题排查
在实际项目中,我遇到过几个典型问题:
-
流程实例无法启动:检查流程定义是否部署成功,确认processDefinitionKey是否正确。可以通过repositoryService.createProcessDefinitionQuery()验证。
-
任务分配失败:确保流程变量中的assignee或candidateUsers/candidateGroups设置正确。我通常会添加日志打印变量值。
-
表单数据不显示:检查formProperty定义是否与前端表单字段匹配,特别是type和required属性。
-
事务回滚问题:业务操作和流程操作要在同一个事务中,确保@Transactional注解配置正确。
6. 前端界面实现技巧
6.1 使用Vue3组合式API
在Jeecgboot3.9.0中,前端已经升级到Vue3。对于请假单页面,我采用了如下结构:
javascript复制// 请假申请组件
<script setup>
import { ref, reactive } from 'vue';
import { useRouter } from 'vue-router';
const formState = reactive({
leaveType: '',
startTime: '',
endTime: '',
days: 0,
reason: ''
});
const rules = {
leaveType: [{ required: true, message: '请选择请假类型' }],
startTime: [{ required: true, message: '请选择开始时间' }]
};
const submitForm = async () => {
// 提交逻辑
};
</script>
6.2 流程可视化展示
对于流程进展,我使用了Flowable提供的流程图SVG渲染:
vue复制<template>
<div class="process-diagram">
<img :src="`/api/flowable/process-instance/${instanceId}/diagram`"
@click="handleDiagramClick">
<div v-for="(node, index) in activeNodes"
:key="index"
class="active-node"
:style="getNodeStyle(node)">
{{ node.name }}
</div>
</div>
</template>
7. 性能优化实践
7.1 流程引擎调优
在高并发场景下,我做了以下优化:
- 启用异步执行器:
yaml复制flowable:
async-executor-activate: true
async-executor-thread-pool-size: 10
- 配置二级缓存:
java复制config.setProcessDefinitionCache(new DefaultProcessDefinitionCache(100));
config.setProcessModelCache(new DefaultProcessModelCache(100));
- 批量操作优化:对于批量审批场景,使用CommandContext优化:
java复制commandExecutor.execute(new Command<Void>() {
public Void execute(CommandContext commandContext) {
// 批量操作逻辑
}
});
7.2 数据库优化建议
- 为ACT_RU_TASK表的PROC_INST_ID_字段添加索引
- 定期归档历史数据:
sql复制INSERT INTO ACT_HI_TASK_ARCHIVED
SELECT * FROM ACT_HI_TASKINST
WHERE END_TIME_ < DATE_SUB(NOW(), INTERVAL 3 MONTH);
- 调整Flowable的history级别,对于不需要的审计数据可以设置为AUDIT或ACTIVITY级别。
8. 扩展功能实现
8.1 请假额度控制
在实际业务中,通常需要控制各类假期的额度。我在服务层添加了如下校验逻辑:
java复制public void validateLeaveQuota(LeaveApply apply) {
// 查询已使用的额度
Integer usedDays = leaveApplyService.getUsedDays(
apply.getUserId(),
apply.getLeaveType(),
apply.getYear());
// 查询总配额
Integer quota = leaveQuotaService.getQuota(
apply.getUserId(),
apply.getLeaveType(),
apply.getYear());
if (usedDays + apply.getDays() > quota) {
throw new BusinessException("请假天数超过剩余额度");
}
}
8.2 多级审批实现
对于需要多级审批的场景,可以在流程定义中使用条件序列流:
xml复制<sequenceFlow id="flow1" sourceRef="leaderAudit" targetRef="hrAudit">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${days > 3}]]>
</conditionExpression>
</sequenceFlow>
对应的服务代码:
java复制// 在完成任务时设置天数变量
variables.put("days", leaveApply.getDays());
taskService.complete(taskId, variables);
9. 安全与权限控制
9.1 流程数据权限
在查询流程实例时,必须确保用户只能访问自己有权限的数据:
java复制public List<ProcessInstance> getMyProcessInstances(String userId) {
return runtimeService.createProcessInstanceQuery()
.involvedUser(userId)
.list();
}
9.2 操作权限验证
在执行关键操作前进行权限校验:
java复制public void validateTaskPermission(String taskId, String userId) {
long count = taskService.createTaskQuery()
.taskId(taskId)
.taskCandidateOrAssigned(userId)
.count();
if (count == 0) {
throw new BusinessException("无权操作该任务");
}
}
10. 测试与部署建议
10.1 单元测试策略
对于流程相关的测试,我推荐使用Flowable的测试工具类:
java复制@SpringBootTest
public class LeaveProcessTest {
@Autowired
private RuntimeService runtimeService;
@Test
public void testLeaveProcess() {
// 部署流程定义
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("processes/leave.bpmn20.xml")
.deploy();
// 启动流程实例
Map<String, Object> variables = new HashMap<>();
variables.put("applyUserId", "user1");
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
"leave_process", variables);
// 验证任务
Task task = taskService.createTaskQuery()
.processInstanceId(instance.getId())
.singleResult();
assertNotNull(task);
assertEquals("部门领导审批", task.getName());
}
}
10.2 生产环境部署
在生产环境部署时,我建议:
- 使用Nginx做静态资源缓存
- 配置Redis作为流程引擎缓存
- 启用数据库连接池监控
- 设置流程引擎的JMX监控
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,flowable
在实现这个OA请假单功能的过程中,最大的收获是理解了业务数据与流程数据的关联方式。特别是在处理多级审批和额度控制时,需要仔细设计变量传递和条件分支。建议在正式上线前,充分测试各种边界情况,比如超长请假、跨年请假等特殊场景。
