OA 办公自动化 / 工作流引擎
OA 系统通用模块
组织架构管理
OA 系统以组织架构为基石,通常采用树形结构管理企业组织关系。
公司(根节点)
├── 事业部 / 子公司
│ ├── 部门
│ │ ├── 小组
│ │ │ ├── 岗位(职位)
│ │ │ │ └── 人员数据库设计示例:
-- 组织架构表(公司 / 部门 / 小组)
CREATE TABLE org_unit (
id BIGINT PRIMARY KEY,
parent_id BIGINT, -- 父节点 ID
unit_type VARCHAR(20), -- COMPANY / DEPT / GROUP
unit_name VARCHAR(100),
sort_order INT,
status TINYINT DEFAULT 1,
path VARCHAR(500), -- 全路径,如 /1/10/100
FOREIGN KEY (parent_id) REFERENCES org_unit(id)
);
-- 岗位表
CREATE TABLE org_position (
id BIGINT PRIMARY KEY,
unit_id BIGINT,
position_name VARCHAR(100),
level INT, -- 岗位层级
FOREIGN KEY (unit_id) REFERENCES org_unit(id)
);
-- 人员-岗位关联
CREATE TABLE org_staff_position (
staff_id BIGINT,
position_id BIGINT,
is_primary TINYINT DEFAULT 0, -- 是否主岗
PRIMARY KEY (staff_id, position_id)
);Java 实体结构:
public class OrgUnit {
private Long id;
private Long parentId;
private String unitType; // COMPANY / DEPT / GROUP
private String unitName;
private Integer sortOrder;
private Integer status;
private String path;
// 子节点列表(树形结构)
private List<OrgUnit> children;
}
public class Staff {
private Long id;
private String staffNo;
private String name;
private String email;
private String phone;
private Long orgUnitId; // 所属部门
private List<Position> positions;
}角色权限
OA 系统权限体系分为功能权限和数据权限两个维度。
功能权限(RBAC):
-- 角色表
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY,
role_code VARCHAR(50), -- ROLE_ADMIN, ROLE_DEPT_MGR
role_name VARCHAR(100),
data_scope VARCHAR(20) -- ALL / DEPT / SELF / CUSTOM
);
-- 菜单/功能表
CREATE TABLE sys_menu (
id BIGINT PRIMARY KEY,
parent_id BIGINT,
menu_name VARCHAR(100),
permission_code VARCHAR(100), -- oa:leave:approve
menu_type VARCHAR(20), -- DIR / MENU / BUTTON
sort_order INT
);
-- 角色-菜单关联
CREATE TABLE sys_role_menu (
role_id BIGINT,
menu_id BIGINT,
PRIMARY KEY (role_id, menu_id)
);数据权限实现策略:
| 数据范围 | 说明 | SQL 过滤条件 |
|---|---|---|
| ALL | 查看全部数据 | 无条件 |
| DEPT | 本部门及子部门 | dept_id IN (子部门列表) |
| DEPT_SELF | 仅本部门 | dept_id = ? |
| SELF | 仅自己 | create_by = ? |
| CUSTOM | 自定义范围 | dept_id IN (指定列表) |
数据权限通过 MyBatis 拦截器或 AOP 在 SQL 层面注入过滤条件实现:
@Aspect
@Component
public class DataScopeAspect {
@Around("@annotation(dataScope)")
public Object handleDataScope(ProceedingJoinPoint joinPoint, DataScope dataScope) {
// 获取当前用户的数据权限范围
String scope = SecurityUtils.getCurrentUser().getDataScope();
// 注入 SQL 过滤条件
DataFilter filter = buildDataFilter(scope);
DataScopeContext.set(filter);
try {
return joinPoint.proceed();
} finally {
DataScopeContext.clear();
}
}
}通知公告
通知公告模块包含发布、审批、阅读记录、置顶、过期等功能。
CREATE TABLE oa_notice (
id BIGINT PRIMARY KEY,
title VARCHAR(200),
content TEXT,
type VARCHAR(20), -- NOTICE / BULLETIN / MESSAGE
priority VARCHAR(10), -- HIGH / MEDIUM / LOW
status VARCHAR(20), -- DRAFT / PUBLISHED / CANCELLED
is_top TINYINT DEFAULT 0,
receive_scope VARCHAR(20), -- ALL / DEPT / CUSTOM
publish_time DATETIME,
expire_time DATETIME,
create_by BIGINT,
create_time DATETIME
);
CREATE TABLE oa_notice_read (
id BIGINT PRIMARY KEY,
notice_id BIGINT,
staff_id BIGINT,
read_time DATETIME,
UNIQUE KEY uk_notice_staff (notice_id, staff_id)
);文档管理
文档管理支持文件夹组织、版本控制、权限控制、在线预览等功能。
CREATE TABLE oa_document (
id BIGINT PRIMARY KEY,
parent_id BIGINT,
doc_name VARCHAR(200),
doc_type VARCHAR(20), -- FILE / FOLDER
file_type VARCHAR(50), -- pdf / docx / xlsx
file_size BIGINT,
storage_path VARCHAR(500),
version INT DEFAULT 1,
status TINYINT DEFAULT 1,
create_by BIGINT,
create_time DATETIME
);
CREATE TABLE oa_document_version (
id BIGINT PRIMARY KEY,
document_id BIGINT,
version INT,
storage_path VARCHAR(500),
file_size BIGINT,
change_note VARCHAR(500),
create_by BIGINT,
create_time DATETIME
);考勤排班
考勤排班模块包括班次定义、排班计划、打卡记录、考勤统计等功能。
-- 班次定义
CREATE TABLE oa_shift (
id BIGINT PRIMARY KEY,
shift_name VARCHAR(50), -- 早班 / 晚班 / 正常班
start_time TIME,
end_time TIME,
late_minutes INT DEFAULT 0, -- 允许迟到分钟数
work_hours DECIMAL(4,1)
);
-- 排班计划
CREATE TABLE oa_schedule (
id BIGINT PRIMARY KEY,
staff_id BIGINT,
shift_id BIGINT,
schedule_date DATE,
schedule_type VARCHAR(20), -- NORMAL / OVERTIME / HOLIDAY
create_time DATETIME
);
-- 打卡记录
CREATE TABLE oa_attendance (
id BIGINT PRIMARY KEY,
staff_id BIGINT,
attendance_date DATE,
clock_in TIME,
clock_out TIME,
status VARCHAR(20), -- NORMAL / LATE / EARLY / ABSENT
source VARCHAR(20) -- DEVICE / APP / MANUAL
);流程引擎选型
Activiti vs Flowable vs Camunda 对比
| 特性 | Activiti | Flowable | Camunda |
|---|---|---|---|
| 当前维护方 | Alfresco / 社区 | Flowable 开源团队 | Camunda 公司 |
| 最新版本 | 7.x (2022) | 6.x (Flowable 7 开发中) | 7.x (Camunda 8 独立架构) |
| BPMN 2.0 支持 | 完整 | 完整 | 完整 |
| DMN 支持 | 有限 | 完整 | 完整 |
| CMMN 支持 | 有限 | 完整 | 完整 |
| 云原生支持 | 一般 | 一般 | Camunda 8 (Zeebe) |
| Spring Boot 集成 | Activiti Spring Boot Starter | Flowable Spring Boot Starter | Camunda Spring Boot Starter |
| 社区活跃度 | 较低 | 中等 | 活跃 |
| 商业版本 | 无 | Flowable Enterprise | Camunda Platform |
| 轻量级程度 | 中等 | 中等 | 较高 (Zeebe) |
| REST API | 内置 | 内置 | 内置 |
| 历史版本 | Activiti 5 -> 6 -> 7 | 从 Activiti 5 分支 (2016) | 从 Activiti 5 分支 (2013) |
各引擎版本历史
Activiti 版本演进:
| 版本 | 发布时间 | 主要变化 |
|---|---|---|
| 5.x | 2010-2015 | 初期版本,基于 BPMN 2.0 标准 |
| 6.x | 2017 | 改进 API、增强 JSON 支持、移除部分遗留组件 |
| 7.x | 2020+ | 重构为 Cloud Native 架构、模块化设计 |
Flowable 版本演进:
| 版本 | 发布时间 | 主要变化 |
|---|---|---|
| 6.0 | 2016 | 从 Activiti 5.x 分支独立 |
| 6.1-6.3 | 2017-2018 | 增强 DMN/CMMN 支持、改进性能 |
| 6.4-6.6 | 2019-2021 | 改进 REST API、增强 Spring Boot 支持 |
| 6.7-6.8 | 2022-2024 | 改进云原生支持、性能优化 |
Camunda 版本演进:
| 版本 | 发布时间 | 主要变化 |
|---|---|---|
| 7.0-7.3 | 2013-2015 | 基于 Activiti 5 分支,改进 BPMN 2.0 支持 |
| 7.4-7.9 | 2016-2018 | 增强 Cockpit、DMN 支持 |
| 7.10-7.15 | 2019-2021 | 改进云部署、增强身份管理 |
| 7.16-7.20 | 2022-2024 | 持续维护,Camunda 8 (Zeebe) 并行发展 |
社区活跃度评估
- GitHub Stars(截至 2025 年): Camunda (12k+) > Flowable (8k+) > Activiti (4k+)
- 发布频率: Camunda 月度发布 > Flowable 季度发布 > Activiti 半年发布
- 文档质量: Camunda 文档最为完善,Flowable 次之,Activiti 文档较少
- Spring Boot 兼容性: 三者均适配 Spring Boot 2.x/3.x
选型建议:
- 需要 DMN/CMMN 完整支持 -> Camunda 或 Flowable
- 追求轻量级、快速集成 -> Activiti 7 或 Flowable
- 云原生需求、高吞吐量 -> Camunda 8 (Zeebe)
- 国内企业级应用 -> Flowable(社区文档中文资料较多)
Flowable 核心概念
Flowable 的核心围绕六大 Service 接口构建,它们统一由 ProcessEngine 管理。
架构总览
ProcessEngine(流程引擎核心)
├── RepositoryService —— 流程定义与部署管理
├── RuntimeService —— 流程实例运行时控制
├── TaskService —— 任务管理(查询 / 签收 / 办理)
├── HistoryService —— 历史数据查询
├── IdentityService —— 用户与组管理
├── ManagementService —— 引擎管理与元数据
├── DynamicBpmnService —— 动态修改 BPMN 定义
└── FormService —— 表单管理(已废弃,建议自定义表单)ProcessEngine
ProcessEngine 是所有 Service 的入口,通过配置构建:
// 方式一:编程式构建
ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
.setJdbcUrl("jdbc:mysql://localhost:3306/flowable?useUnicode=true&characterEncoding=utf-8")
.setJdbcUsername("root")
.setJdbcPassword("root")
.setJdbcDriver("com.mysql.cj.jdbc.Driver")
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
ProcessEngine processEngine = cfg.buildProcessEngine();
// 方式二:Spring Boot 自动配置
// 引入 flowable-spring-boot-starter 后自动注入
@Autowired
private ProcessEngine processEngine;RepositoryService
管理流程定义与部署包:
@Service
public class DeploymentService {
@Autowired
private RepositoryService repositoryService;
// 部署 BPMN 文件
public Deployment deployBpmn(String bpmnPath) {
return repositoryService.createDeployment()
.addClasspathResource(bpmnPath)
.name("请假流程")
.category("leave")
.deploy();
}
// 查询流程定义
public List<ProcessDefinition> getProcessDefinitions() {
return repositoryService.createProcessDefinitionQuery()
.latestVersion()
.active()
.list();
}
// 挂起/激活流程定义
public void suspendProcessDefinition(String processDefId) {
repositoryService.suspendProcessDefinitionById(processDefId);
}
// 获取 BPMN 模型
public BpmnModel getBpmnModel(String processDefId) {
return repositoryService.getBpmnModel(processDefId);
}
}RuntimeService
控制流程实例的启动、推进、触发等运行时操作:
@Service
public class RuntimeProcessService {
@Autowired
private RuntimeService runtimeService;
// 启动流程实例(携带变量)
public ProcessInstance startProcess(String processKey, Map<String, Object> variables) {
return runtimeService.startProcessInstanceByKey(processKey, variables);
}
// 启动流程实例(带业务 Key)
public ProcessInstance startProcessWithBusinessKey(String processKey,
String businessKey,
Map<String, Object> variables) {
return runtimeService.startProcessInstanceByKey(processKey, businessKey, variables);
}
// 触发信号事件
public void signalEvent(String signalName, Map<String, Object> variables) {
runtimeService.signalEventReceived(signalName, variables);
}
// 发送消息事件
public void messageEvent(String messageName, String businessKey) {
runtimeService.startProcessInstanceByMessage(messageName, businessKey);
}
// 查询运行中的流程实例
public List<ProcessInstance> getRunningInstances(String processKey) {
return runtimeService.createProcessInstanceQuery()
.processDefinitionKey(processKey)
.active()
.list();
}
// 删除/终止流程实例
public void deleteProcessInstance(String processInstanceId, String reason) {
runtimeService.deleteProcessInstance(processInstanceId, reason);
}
}TaskService
任务查询、签收、办理、委派等操作:
@Service
public class TaskOperationService {
@Autowired
private TaskService taskService;
// 查询待办任务(指定用户)
public List<Task> getTodoTasks(String assignee) {
return taskService.createTaskQuery()
.taskAssignee(assignee)
.active()
.orderByTaskCreateTime().desc()
.list();
}
// 查询候选组任务
public List<Task> getCandidateGroupTasks(String groupId) {
return taskService.createTaskQuery()
.taskCandidateGroup(groupId)
.active()
.list();
}
// 签收任务
public void claimTask(String taskId, String userId) {
taskService.claim(taskId, userId);
}
// 办理任务
public void completeTask(String taskId, Map<String, Object> variables) {
taskService.complete(taskId, variables);
}
// 委派任务
public void delegateTask(String taskId, String delegater) {
taskService.delegateTask(taskId, delegater);
}
// 转办任务
public void setAssignee(String taskId, String assignee) {
taskService.setAssignee(taskId, assignee);
}
// 添加批注
public void addComment(String taskId, String processInstanceId, String message) {
taskService.addComment(taskId, processInstanceId, message);
}
}HistoryService
查询已完成的历史数据和流程审计信息:
@Service
public class HistoryQueryService {
@Autowired
private HistoryService historyService;
// 查询已完成的流程实例
public List<HistoricProcessInstance> getCompletedInstances(String processKey) {
return historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey(processKey)
.finished()
.orderByProcessInstanceEndTime().desc()
.list();
}
// 查询流程历史节点
public List<HistoricActivityInstance> getActivityHistory(String processInstanceId) {
return historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceStartTime().asc()
.list();
}
// 查询历史任务
public List<HistoricTaskInstance> getTaskHistory(String processInstanceId) {
return historyService.createHistoricTaskInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricTaskInstanceEndTime().asc()
.list();
}
// 查询流程变量历史
public List<HistoricVariableInstance> getVariableHistory(String processInstanceId) {
return historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstanceId)
.list();
}
// 查询某个用户的历史任务
public List<HistoricTaskInstance> getUserHistoryTasks(String userId) {
return historyService.createHistoricTaskInstanceQuery()
.taskAssignee(userId)
.finished()
.orderByHistoricTaskInstanceEndTime().desc()
.listPage(0, 20);
}
}IdentityService
管理用户和用户组:
@Service
public class IdentityManageService {
@Autowired
private IdentityService identityService;
// 创建用户组
public Group createGroup(String groupId, String groupName, String type) {
Group group = identityService.newGroup(groupId);
group.setName(groupName);
group.setType(type);
identityService.saveGroup(group);
return group;
}
// 创建用户
public User createUser(String userId, String displayName) {
User user = identityService.newUser(userId);
user.setDisplayName(displayName);
identityService.saveUser(user);
return user;
}
// 将用户加入组
public void addUserToGroup(String userId, String groupId) {
identityService.createMembership(userId, groupId);
}
}实际项目中身份管理通常与业务系统对接: 使用自定义 UserEntityManager 替代 Flowable 内置的身份管理,通过查询业务系统的组织架构表来实现用户和组的映射。
BPMN 2.0 建模
流程定义核心元素
process / sequenceFlow
<bpmn2:process id="leaveProcess" name="请假流程" isExecutable="true">
<!-- 流程变量定义 -->
<bpmn2:property id="days" name="请假天数" type="integer"/>
<bpmn2:property id="reason" name="请假原因" type="string"/>
<!-- 开始事件 -->
<bpmn2:startEvent id="startEvent" name="开始"/>
<!-- 顺序流 -->
<bpmn2:sequenceFlow id="flow1" sourceRef="startEvent" targetRef="applyTask">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">
<![CDATA[${days > 0}]]>
</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
</bpmn2:process>userTask
用户任务,需要人工参与办理:
<bpmn2:userTask id="managerApprove" name="直属领导审批">
<bpmn2:extensionElements>
<flowable:taskListener event="create"
class="com.example.listener.ManagerTaskListener"/>
</bpmn2:extensionElements>
<!-- 候选人组 -->
<bpmn2:potentialOwner>
<bpmn2:resourceAssignmentExpression>
<bpmn2:formalExpression>group(manager)</bpmn2:formalExpression>
</bpmn2:resourceAssignmentExpression>
</bpmn2:potentialOwner>
<!-- 或指定审批人 -->
<bpmn2:humanPerformer>
<bpmn2:resourceAssignmentExpression>
<bpmn2:formalExpression>${approver}</bpmn2:formalExpression>
</bpmn2:resourceAssignmentExpression>
</bpmn2:humanPerformer>
</bpmn2:userTask>serviceTask
自动执行的服务任务,支持 Java 类、表达式、Delegate 三种方式:
<!-- 方式一:Java 类 -->
<bpmn2:serviceTask id="hrRecord" name="HR备案记录"
flowable:class="com.example.task.HrRecordTask"/>
<!-- 方式二:表达式 -->
<bpmn2:serviceTask id="calcDays" name="计算天数"
flowable:expression="${dayCalculator.calc(execution)}"/>
<!-- 方式三:Delegate 表达式 -->
<bpmn2:serviceTask id="sendNotify" name="发送通知"
flowable:delegateExpression="${noticeDelegate}"/>对应的 Java 实现:
// 方式一:实现 JavaDelegate
public class HrRecordTask implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
String applicant = (String) execution.getVariable("applicant");
Integer days = (Integer) execution.getVariable("days");
// 执行备案逻辑
System.out.println("HR备案: " + applicant + ", 天数: " + days);
}
}
// 方式二:Spring Bean + 方法调用
@Component("dayCalculator")
public class DayCalculator {
public void calc(DelegateExecution execution) {
// 计算逻辑
}
}
// 方式三:Spring Bean 实现 JavaDelegate
@Component("noticeDelegate")
public class NoticeDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
// 通知逻辑
}
}exclusiveGateway
排他网关,根据条件选择唯一分支:
<bpmn2:exclusiveGateway id="decisionGateway" name="审批决策" default="rejectEnd"/>
<bpmn2:sequenceFlow id="approveFlow" sourceRef="decisionGateway" targetRef="hrTask">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">
<![CDATA[${approved == true}]]>
</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="rejectFlow" sourceRef="decisionGateway" targetRef="rejectEnd">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">
<![CDATA[${approved == false}]]>
</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>parallelGateway
并行网关,拆分和汇合多分支:
<!-- 并行拆分 -->
<bpmn2:parallelGateway id="parallelSplit" name="并行拆分"/>
<bpmn2:sequenceFlow id="toManager" sourceRef="parallelSplit" targetRef="managerApprove"/>
<bpmn2:sequenceFlow id="toFinance" sourceRef="parallelSplit" targetRef="financeCheck"/>
<!-- 并行汇合 -->
<bpmn2:parallelGateway id="parallelJoin" name="并行汇合"/>
<bpmn2:sequenceFlow id="fromManager" sourceRef="managerApprove" targetRef="parallelJoin"/>
<bpmn2:sequenceFlow id="fromFinance" sourceRef="financeCheck" targetRef="parallelJoin"/>事件
startEvent / endEvent
<!-- 空开始事件 -->
<bpmn2:startEvent id="startEvent" name="申请提交"/>
<!-- 定时开始事件 -->
<bpmn2:startEvent id="timerStart" name="每日定时触发">
<bpmn2:timerEventDefinition>
<bpmn2:timeCycle>0 0 9 * * ?</bpmn2:timeCycle>
</bpmn2:timerEventDefinition>
</bpmn2:startEvent>
<!-- 消息开始事件 -->
<bpmn2:startEvent id="messageStart" name="消息触发">
<bpmn2:messageEventDefinition messageRef="leaveRequestMsg"/>
</bpmn2:startEvent>
<!-- 空结束事件 -->
<bpmn2:endEvent id="approveEnd" name="审批通过"/>
<!-- 终止结束事件(终止整个流程) -->
<bpmn2:endEvent id="terminateEnd" name="终止">
<bpmn2:terminateEventDefinition/>
</bpmn2:endEvent>timerEvent
定时中间事件,用于超时提醒和自动推进:
<!-- 定时边界事件(任务超时) -->
<bpmn2:boundaryEvent id="timeoutBoundary" attachedToRef="managerApprove"
cancelActivity="true">
<bpmn2:timerEventDefinition>
<!-- 超时时间为 2 天 -->
<bpmn2:timeDuration>PT48H</bpmn2:timeDuration>
</bpmn2:timerEventDefinition>
</bpmn2:boundaryEvent>
<!-- 定时捕获中间事件 -->
<bpmn2:intermediateCatchEvent id="waitTimer">
<bpmn2:timerEventDefinition>
<bpmn2:timeDate>2025-12-31T23:59:59</bpmn2:timeDate>
</bpmn2:timerEventDefinition>
</bpmn2:intermediateCatchEvent>messageEvent
消息事件用于跨流程通信或外部系统触发:
<!-- 消息定义 -->
<bpmn2:message id="leaveRequestMsg" name="LeaveRequestMessage"/>
<!-- 消息捕获中间事件 -->
<bpmn2:intermediateCatchEvent id="catchMessage">
<bpmn2:messageEventDefinition messageRef="leaveRequestMsg"/>
</bpmn2:intermediateCatchEvent>
<!-- 消息抛出中间事件 -->
<bpmn2:intermediateThrowEvent id="throwMessage">
<bpmn2:messageEventDefinition messageRef="leaveRequestMsg"/>
</bpmn2:intermediateThrowEvent>// 触发消息事件
runtimeService.messageEventReceived("LeaveRequestMessage", executionId);监听器
ExecutionListener (执行监听器)
监听流程节点和流转事件:
@Component("globalExecutionListener")
public class GlobalExecutionListener implements ExecutionListener {
@Override
public void notify(DelegateExecution execution, EventType eventType) {
switch (eventType) {
case START:
// 流程实例启动
log.info("流程启动: {}", execution.getProcessInstanceId());
break;
case END:
// 流程实例结束
log.info("流程结束: {}", execution.getProcessInstanceId());
break;
case TAKE:
// 顺序流被选中
String flowId = execution.getCurrentFlowElement().getId();
log.info("流转: {}", flowId);
break;
}
}
}<!-- 在流程级别配置执行监听器 -->
<bpmn2:process id="leaveProcess" name="请假流程">
<bpmn2:extensionElements>
<flowable:executionListener event="start"
delegateExpression="${globalExecutionListener}"/>
<flowable:executionListener event="end"
delegateExpression="${globalExecutionListener}"/>
</bpmn2:extensionElements>
</bpmn2:process>TaskListener (任务监听器)
监听用户任务的创建、分配、完成事件:
@Component("managerTaskListener")
public class ManagerTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
switch (delegateTask.getEventName()) {
case EVENTNAME_CREATE:
// 任务创建时:动态设置审批人
String deptId = (String) delegateTask.getVariable("deptId");
String manager = findDeptManager(deptId);
delegateTask.setAssignee(manager);
break;
case EVENTNAME_ASSIGNMENT:
// 任务分配时
log.info("任务已分配给: {}", delegateTask.getAssignee());
break;
case EVENTNAME_COMPLETE:
// 任务完成时
Map<String, Object> vars = delegateTask.getVariables();
delegateTask.setVariable("approveTime", new Date());
break;
case EVENTNAME_DELETE:
// 任务删除时
break;
}
}
private String findDeptManager(String deptId) {
// 查询部门负责人
return "manager_" + deptId;
}
}<bpmn2:userTask id="managerApprove" name="直属领导审批">
<bpmn2:extensionElements>
<flowable:taskListener event="create"
delegateExpression="${managerTaskListener}"/>
<flowable:taskListener event="complete"
class="com.example.listener.CompleteTaskListener"/>
</bpmn2:extensionElements>
</bpmn2:userTask>流程部署与启动
BPMN 文件部署
方式一:自动部署(Spring Boot)
在 classpath:processes/ 目录下放置 .bpmn 文件,Flowable 启动时自动扫描部署。
方式二:编程式部署:
@Autowired
private RepositoryService repositoryService;
// 部署单个文件
public Deployment deployByClasspath(String path) {
return repositoryService.createDeployment()
.addClasspathResource("processes/" + path)
.name("请假流程v1")
.category("OA")
.tenantId("default")
.deploy();
}
// 部署字节流
public Deployment deployByBytes(String processName, byte[] bpmnBytes) {
return repositoryService.createDeployment()
.addBytes(processName + ".bpmn", bpmnBytes)
.name(processName)
.deploy();
}
// 部署 ZIP 包(批量)
public Deployment deployByZip(String zipPath) {
ZipInputStream zis = new ZipInputStream(
this.getClass().getClassLoader().getResourceAsStream(zipPath));
return repositoryService.createDeployment()
.addZipInputStream(zis)
.name("批量部署")
.deploy();
}流程实例启动
@Autowired
private RuntimeService runtimeService;
public ProcessInstance startLeaveProcess(LeaveApplyDto dto) {
// 构建流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("applicant", dto.getApplicant());
variables.put("days", dto.getDays());
variables.put("reason", dto.getReason());
variables.put("deptId", dto.getDeptId());
variables.put("approver", null); // 由监听器动态填充
// 启动流程(业务 Key 关联业务表单 ID)
return runtimeService.startProcessInstanceByKey(
"leaveProcess", // 流程定义 Key
dto.getBizId(), // 业务 Key
variables // 流程变量
);
}任务查询
@Autowired
private TaskService taskService;
// 1. 查询个人待办
public List<Task> getPersonalTasks(String userId) {
return taskService.createTaskQuery()
.taskAssignee(userId)
.active()
.orderByTaskCreateTime().desc()
.list();
}
// 2. 查询候选组任务(未签收)
public List<Task> getGroupTasks(String groupId) {
return taskService.createTaskQuery()
.taskCandidateGroup(groupId)
.active()
.list();
}
// 3. 查询某流程实例的所有任务
public List<Task> getProcessTasks(String processInstanceId) {
return taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.list();
}签收 / 办理
// 签收(领取任务)
public void claim(String taskId, String userId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
throw new RuntimeException("任务不存在");
}
if (task.getAssignee() != null) {
throw new RuntimeException("任务已被 " + task.getAssignee() + " 签收");
}
taskService.claim(taskId, userId);
}
// 办理(完成任务)
public void complete(TaskCompleteDto dto) {
Map<String, Object> variables = new HashMap<>();
variables.put("approved", dto.getApproved());
variables.put("comment", dto.getComment());
variables.put("approveUserId", dto.getUserId());
// 添加审批意见
taskService.addComment(
dto.getTaskId(),
dto.getProcessInstanceId(),
dto.getComment()
);
// 完成当前任务,推进流程
taskService.complete(dto.getTaskId(), variables);
}流程推进
// 1. 普通推进:complete 完成当前任务即可
taskService.complete(taskId, variables);
// 2. 信号事件推进
runtimeService.signalEventReceived("signalName");
// 3. 消息事件推进
runtimeService.messageEventReceived("messageName", executionId);
// 4. 终止流程实例
runtimeService.deleteProcessInstance(processInstanceId, "强制终止");
// 5. 触发流程(针对等待状态)
runtimeService.trigger(executionId);请假流程完整示例
BPMN 模型
请假流程设计:员工提交 -> 直属领导审批 -> HR 备案 -> 结束
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:flowable="http://flowable.org/bpmn"
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
targetNamespace="http://flowable.org/bpmn">
<process id="leaveProcess" name="请假流程" isExecutable="true">
<!-- 流程变量 -->
<bpmn2:property id="applicant" name="申请人" type="string"/>
<bpmn2:property id="days" name="请假天数" type="integer"/>
<bpmn2:property id="reason" name="请假原因" type="string"/>
<bpmn2:property id="approved" name="是否通过" type="boolean"/>
<bpmn2:property id="deptId" name="部门ID" type="string"/>
<!-- 开始:员工提交申请 -->
<bpmn2:startEvent id="startEvent" name="提交申请">
<bpmn2:extensionElements>
<flowable:executionListener event="start"
delegateExpression="${leaveStartListener}"/>
</bpmn2:extensionElements>
</bpmn2:startEvent>
<!-- 员工填写申请(用户任务) -->
<bpmn2:userTask id="applyTask" name="填写申请"
flowable:assignee="${applicant}"
flowable:formKey="leaveApplyForm">
<bpmn2:extensionElements>
<flowable:taskListener event="complete"
class="com.example.leave.listener.ApplyTaskListener"/>
</bpmn2:extensionElements>
</bpmn2:userTask>
<!-- 部门领导审批 -->
<bpmn2:userTask id="managerApprove" name="直属领导审批">
<bpmn2:extensionElements>
<flowable:taskListener event="create"
delegateExpression="${deptManagerAssigner}"/>
<flowable:taskListener event="complete"
delegateExpression="${managerApproveListener}"/>
</bpmn2:extensionElements>
</bpmn2:userTask>
<!-- 审批决策网关 -->
<bpmn2:exclusiveGateway id="approveGateway" name="审批决策" default="rejectEnd"/>
<!-- 通过分支 -->
<bpmn2:sequenceFlow id="approveFlow" sourceRef="approveGateway" targetRef="hrTask">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">
<![CDATA[${approved == true}]]>
</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<!-- 驳回分支 -->
<bpmn2:sequenceFlow id="rejectFlow" sourceRef="approveGateway" targetRef="rejectEnd">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">
<![CDATA[${approved == false}]]>
</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<!-- HR 备案 -->
<bpmn2:userTask id="hrTask" name="HR备案">
<bpmn2:extensionElements>
<flowable:taskListener event="create"
delegateExpression="${hrTaskAssigner}"/>
</bpmn2:extensionElements>
</bpmn2:userTask>
<!-- 备案完成 -->
<bpmn2:serviceTask id="recordService" name="更新考勤记录"
flowable:delegateExpression="${attendanceDelegate}"/>
<!-- 结束 -->
<bpmn2:endEvent id="normalEnd" name="流程结束"/>
<!-- 驳回结束 -->
<bpmn2:endEvent id="rejectEnd" name="申请驳回"/>
<!-- 顺序流连接 -->
<bpmn2:sequenceFlow id="flow1" sourceRef="startEvent" targetRef="applyTask"/>
<bpmn2:sequenceFlow id="flow2" sourceRef="applyTask" targetRef="managerApprove"/>
<bpmn2:sequenceFlow id="flow3" sourceRef="managerApprove" targetRef="approveGateway"/>
<bpmn2:sequenceFlow id="flow4" sourceRef="hrTask" targetRef="recordService"/>
<bpmn2:sequenceFlow id="flow5" sourceRef="recordService" targetRef="normalEnd"/>
</process>
</definitions>前端表单设计
请假表单页面(Vue 示例):
<template>
<div class="leave-form">
<h3>请假申请</h3>
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
<el-form-item label="请假天数" prop="days">
<el-input-number v-model="form.days" :min="0.5" :max="30" :step="0.5"/>
</el-form-item>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker v-model="form.startTime" type="datetime" placeholder="选择开始时间"/>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker v-model="form.endTime" type="datetime" placeholder="选择结束时间"/>
</el-form-item>
<el-form-item label="请假类型" prop="leaveType">
<el-select v-model="form.leaveType" placeholder="请选择">
<el-option label="年假" value="ANNUAL"/>
<el-option label="事假" value="PERSONAL"/>
<el-option label="病假" value="SICK"/>
<el-option label="婚假" value="MARRIAGE"/>
<el-option label="产假" value="MATERNITY"/>
</el-select>
</el-form-item>
<el-form-item label="请假原因" prop="reason">
<el-input type="textarea" v-model="form.reason" :rows="4"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">提交申请</el-button>
<el-button @click="cancel">取消</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
form: {
days: 1,
startTime: null,
endTime: null,
leaveType: 'ANNUAL',
reason: ''
},
rules: {
days: [{ required: true, message: '请输入请假天数' }],
reason: [{ required: true, message: '请输入请假原因' }]
}
}
},
methods: {
async submit() {
await this.$refs.formRef.validate();
const res = await axios.post('/api/leave/apply', this.form);
if (res.data.success) {
this.$message.success('提交成功');
this.$emit('submitted', res.data.data);
}
}
}
}
</script>审批表单(查看待办):
<template>
<div class="approve-panel">
<h3>审批处理</h3>
<el-descriptions :column="1" border>
<el-descriptions-item label="申请人">{{ taskInfo.applicant }}</el-descriptions-item>
<el-descriptions-item label="请假天数">{{ taskInfo.days }} 天</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ taskInfo.startTime }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ taskInfo.endTime }}</el-descriptions-item>
<el-descriptions-item label="原因">{{ taskInfo.reason }}</el-descriptions-item>
</el-descriptions>
<el-form ref="approveForm" :model="approveData" class="approve-form">
<el-form-item label="审批意见">
<el-input type="textarea" v-model="approveData.comment" :rows="3"/>
</el-form-item>
<el-form-item>
<el-button type="success" @click="approve(true)">通过</el-button>
<el-button type="danger" @click="approve(false)">驳回</el-button>
</el-form-item>
</el-form>
</div>
</template>Spring Boot 代码集成
Maven 依赖:
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.8.1</version>
</dependency>
<!-- 使用 MySQL 需要引入 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>application.yml 配置:
spring:
datasource:
url: jdbc:mysql://localhost:3306/flowable?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
flowable:
async-executor-activate: true # 启用异步执行器
database-schema-update: true # 自动创建/更新表结构
process-definition-location-prefix: classpath:/processes/ # BPMN 文件路径
history-level: full # 历史级别(none/activity/audit/full)
idm:
enabled: false # 禁用内置身份管理Controller 代码:
@RestController
@RequestMapping("/api/leave")
public class LeaveController {
@Autowired
private LeaveService leaveService;
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
@PostMapping("/apply")
public Result startLeave(@RequestBody LeaveApplyDto dto) {
// 保存业务表单数据
LeaveForm form = leaveService.saveForm(dto);
// 启动流程
Map<String, Object> vars = new HashMap<>();
vars.put("applicant", dto.getApplicant());
vars.put("days", dto.getDays());
vars.put("reason", dto.getReason());
vars.put("deptId", dto.getDeptId());
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
"leaveProcess", String.valueOf(form.getId()), vars);
form.setProcessInstanceId(instance.getId());
leaveService.updateById(form);
return Result.success(instance.getId());
}
@GetMapping("/todo/{userId}")
public Result getTodoList(@PathVariable String userId) {
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee(userId)
.active()
.orderByTaskCreateTime().desc()
.list();
List<TaskVo> result = tasks.stream().map(task -> {
// 关联业务数据
String bizKey = taskService.getVariable(task.getId(), "bizKey", String.class);
LeaveForm form = leaveService.getByBizKey(bizKey);
return TaskVo.from(task, form);
}).collect(Collectors.toList());
return Result.success(result);
}
@PostMapping("/approve")
public Result approve(@RequestBody ApproveDto dto) {
taskService.addComment(dto.getTaskId(), dto.getProcessInstanceId(), dto.getComment());
Map<String, Object> vars = new HashMap<>();
vars.put("approved", dto.getApproved());
vars.put("comment", dto.getComment());
taskService.complete(dto.getTaskId(), vars);
return Result.success();
}
@GetMapping("/history/{processInstanceId}")
public Result getHistory(@PathVariable String processInstanceId) {
List<HistoricActivityInstance> activities = historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceStartTime().asc()
.list();
return Result.success(activities);
}
}Service 代码:
@Service
public class LeaveService {
@Autowired
private LeaveFormMapper leaveFormMapper;
@Transactional
public LeaveForm saveForm(LeaveApplyDto dto) {
LeaveForm form = new LeaveForm();
BeanUtils.copyProperties(dto, form);
form.setStatus("PROCESSING");
leaveFormMapper.insert(form);
return form;
}
public void updateById(LeaveForm form) {
leaveFormMapper.updateById(form);
}
public LeaveForm getByBizKey(String bizKey) {
return leaveFormMapper.selectById(Long.valueOf(bizKey));
}
}审批节点动态指派
通过表达式配置
方式一:流程变量指定审批人
<bpmn2:userTask id="managerApprove" name="审批" flowable:assignee="${approver}"/>启动流程时传入审批人:
Map<String, Object> vars = new HashMap<>();
vars.put("approver", "user123");
runtimeService.startProcessInstanceByKey("leaveProcess", vars);方式二:候选人组配置
<bpmn2:userTask id="managerApprove" name="审批">
<bpmn2:potentialOwner>
<bpmn2:resourceAssignmentExpression>
<bpmn2:formalExpression>group(dept_${deptId}_manager)</bpmn2:formalExpression>
</bpmn2:resourceAssignmentExpression>
</bpmn2:potentialOwner>
</bpmn2:userTask>组名支持动态表达式,${deptId} 在运行时替换为实际变量值。
通过自定义监听器实现
TaskListener 动态设置审批人:
@Component("deptManagerAssigner")
public class DeptManagerAssigner implements TaskListener {
@Autowired
private StaffService staffService;
@Override
public void notify(DelegateTask delegateTask) {
// 获取部门ID
String deptId = (String) delegateTask.getVariable("deptId");
// 查询部门负责人
Staff manager = staffService.getDeptManager(deptId);
if (manager != null) {
// 直接指定审批人
delegateTask.setAssignee(manager.getUserId());
} else {
// 部门无负责人,设置为部门候选组
delegateTask.addCandidateGroup("dept_" + deptId);
}
// 设置任务过期时间(48小时超时)
delegateTask.setDueDate(new Date(System.currentTimeMillis() + 48 * 60 * 60 * 1000));
}
}多级审批逐级上查:
@Component("levelApproveAssigner")
public class LevelApproveAssigner implements TaskListener {
@Autowired
private OrgUnitService orgUnitService;
@Override
public void notify(DelegateTask delegateTask) {
// 获取当前审批层级(从流程变量读取,默认第一级)
Integer currentLevel = (Integer) delegateTask.getVariable("currentLevel");
if (currentLevel == null) currentLevel = 1;
String applicant = (String) delegateTask.getVariable("applicant");
String applicantDeptId = (String) delegateTask.getVariable("deptId");
// 根据层级查找对应级别的审批人
Staff approver = orgUnitService.findApproverByLevel(applicantDeptId, currentLevel);
if (approver != null) {
delegateTask.setAssignee(approver.getUserId());
} else {
// 无更上级审批人,自动通过
delegateTask.setVariable("autoPass", true);
}
}
}会签 / 或签
会签(多实例任务)允许指定多个审批人并行或串行审批。
会签(全部通过)
所有审批人都通过才算通过:
<bpmn2:userTask id="multiApprove" name="会签审批">
<bpmn2:extensionElements>
<flowable:taskListener event="create"
delegateExpression="${multiApproveListener}"/>
</bpmn2:extensionElements>
<bpmn2:multiInstanceLoopCharacteristics
isSequential="false" <!-- false=并行会签, true=串行会签 -->
flowable:collection="${approverList}"
flowable:elementVariable="approver">
<bpmn2:loopCardinality>
<bpmn2:inputExpression>
<bpmn2:simpleExpression>${approverList.size()}</bpmn2:simpleExpression>
</bpmn2:inputExpression>
</bpmn2:loopCardinality>
<bpmn2:completionCondition>
<!-- 会签通过率条件 -->
<bpmn2:inputExpression>
<bpmn2:simpleExpression>
${nrOfCompletedInstances >= nrOfInstances}
</bpmn2:simpleExpression>
</bpmn2:inputExpression>
</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>或签(一人通过即可)
任意一人审批即可通过:
<bpmn2:multiInstanceLoopCharacteristics
isSequential="false"
flowable:collection="${approverList}"
flowable:elementVariable="approver">
<bpmn2:completionCondition>
<bpmn2:inputExpression>
<bpmn2:simpleExpression>
${nrOfCompletedInstances >= 1 || nrOfActiveInstances == 0}
</bpmn2:simpleExpression>
</bpmn2:inputExpression>
</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>会签 Java 代码
@Component("multiApproveListener")
public class MultiApproveListener implements TaskListener {
@Autowired
private StaffService staffService;
@Override
public void notify(DelegateTask delegateTask) {
if (EVENTNAME_CREATE.equals(delegateTask.getEventName())) {
// 获取审批人列表(动态计算)
String deptId = (String) delegateTask.getVariable("deptId");
Integer days = (Integer) delegateTask.getVariable("days");
List<String> approvers;
if (days > 10) {
// 超过10天需要全员审批
approvers = staffService.getDeptAllManagers(deptId);
// 还要上级部门总监
approvers.addAll(staffService.getUpperDeptLeaders(deptId));
} else if (days > 3) {
// 3-10天需要所有副总监审批
approvers = staffService.getDeptDeputyManagers(deptId);
} else {
// 3天以内只需直属上级审批
approvers = Collections.singletonList(
staffService.getDeptManager(deptId).getUserId());
}
// 设置多实例集合变量
delegateTask.setVariable("approverList", approvers);
}
}
}会签变量说明:
| 变量名 | 说明 |
|---|---|
nrOfInstances | 总实例数 |
nrOfActiveInstances | 当前活动实例数 |
nrOfCompletedInstances | 已完成实例数 |
loopCounter | 循环计数器(从1开始) |
approver | 当前实例的元素变量值 |
会签结果处理(通过比例控制):
<!-- 超过半数通过即通过 -->
<bpmn2:completionCondition>
<bpmn2:inputExpression>
<bpmn2:simpleExpression>
${nrOfCompletedInstances >= (nrOfInstances + 1) / 2}
</bpmn2:simpleExpression>
</bpmn2:inputExpression>
</bpmn2:completionCondition>
<!-- 拒绝数超过1个即驳回 -->
<bpmn2:completionCondition>
<bpmn2:inputExpression>
<bpmn2:simpleExpression>
${nrOfCompletedInstances >= nrOfInstances || rejectCount > 1}
</bpmn2:simpleExpression>
</bpmn2:inputExpression>
</bpmn2:completionCondition>流程监控与管理
流程实例管理
挂起 / 激活流程实例
@RestController
@RequestMapping("/api/process")
public class ProcessAdminController {
@Autowired
private RuntimeService runtimeService;
@Autowired
private RepositoryService repositoryService;
// 挂起流程实例
@PostMapping("/suspend/{instanceId}")
public Result suspendInstance(@PathVariable String instanceId) {
runtimeService.suspendProcessInstanceById(instanceId);
return Result.success();
}
// 激活流程实例
@PostMapping("/activate/{instanceId}")
public Result activateInstance(@PathVariable String instanceId) {
runtimeService.activateProcessInstanceById(instanceId);
return Result.success();
}
// 挂起流程定义
@PostMapping("/definition/suspend/{defId}")
public Result suspendDefinition(@PathVariable String defId) {
repositoryService.suspendProcessDefinitionById(defId);
return Result.success();
}
// 终止流程实例
@PostMapping("/terminate/{instanceId}")
public Result terminateInstance(@PathVariable String instanceId) {
runtimeService.deleteProcessInstance(instanceId, "管理员终止");
return Result.success();
}
// 获取运行中的流程实例列表
@GetMapping("/running")
public Result getRunningInstances(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int size) {
List<ProcessInstance> list = runtimeService.createProcessInstanceQuery()
.active()
.orderByProcessInstanceStartTime().desc()
.listPage((page - 1) * size, size);
long count = runtimeService.createProcessInstanceQuery()
.active()
.count();
return Result.success(new PageResult<>(list, count));
}
}流程实例管理后台页面
@RestController
@RequestMapping("/api/admin/process")
public class ProcessAdminApiController {
@Autowired
private RuntimeService runtimeService;
@Autowired
private HistoryService historyService;
@Autowired
private TaskService taskService;
// 获取流程实例详情
@GetMapping("/detail/{processInstanceId}")
public Result getProcessDetail(@PathVariable String processInstanceId) {
ProcessInstance instance = runtimeService
.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
if (instance == null) {
// 已结束,查询历史
HistoricProcessInstance historic = historyService
.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
return Result.success(ProcessDetailVo.fromHistoric(historic));
}
// 当前运行状态
List<Task> tasks = taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.list();
List<HistoricActivityInstance> doneActivities = historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.finished()
.orderByHistoricActivityInstanceEndTime().asc()
.list();
return Result.success(ProcessDetailVo.builder()
.instance(instance)
.currentTasks(tasks)
.doneActivities(doneActivities)
.build());
}
// 获取流程实例变量
@GetMapping("/variables/{processInstanceId}")
public Result getProcessVariables(@PathVariable String processInstanceId) {
Map<String, Object> variables = runtimeService.getVariables(processInstanceId);
if (variables.isEmpty()) {
// 已结束流程从历史查询
List<HistoricVariableInstance> historicVars = historyService
.createHistoricVariableInstanceQuery()
.processInstanceId(processInstanceId)
.list();
variables = historicVars.stream()
.collect(Collectors.toMap(
HistoricVariableInstance::getVariableName,
HistoricVariableInstance::getValue));
}
return Result.success(variables);
}
}任务催办与超时提醒
定时任务催办
@Component
public class TaskRemindJob {
@Autowired
private TaskService taskService;
@Autowired
private NotificationService notificationService;
// 每天上午9点和下午3点执行催办
@Scheduled(cron = "0 0 9,15 * * ?")
public void remindOverdueTasks() {
// 查询所有未完成任务
List<Task> tasks = taskService.createTaskQuery()
.active()
.list();
for (Task task : tasks) {
// 检查是否超时
if (isOverdue(task)) {
String assignee = task.getAssignee();
if (assignee != null) {
// 发送催办通知
notificationService.sendRemindMessage(
assignee,
task.getId(),
"您有一个待办任务已超时:" + task.getName()
);
}
// 如果超时超过3天,抄送上级
if (isOverdueDays(task, 3)) {
String managerId = findAssigneeManager(assignee);
notificationService.sendRemindMessage(
managerId,
task.getId(),
"下属任务超时3天,请关注"
);
}
}
}
}
private boolean isOverdue(Task task) {
if (task.getDueDate() == null) return false;
return new Date().after(task.getDueDate());
}
private boolean isOverdueDays(Task task, int days) {
if (task.getDueDate() == null) return false;
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_YEAR, -days);
return task.getDueDate().before(cal.getTime());
}
}超时边界事件自动推进
利用 BPMN 定时边界事件实现超时自动处理:
<bpmn2:userTask id="managerApprove" name="审批">
<bpmn2:extensionElements>
<flowable:taskListener event="create"
delegateExpression="${deptManagerAssigner}"/>
</bpmn2:extensionElements>
</bpmn2:userTask>
<!-- 超时边界事件:48小时未审批默认通过 -->
<bpmn2:boundaryEvent id="timeoutBoundary" attachedToRef="managerApprove"
cancelActivity="true">
<bpmn2:timerEventDefinition>
<bpmn2:timeDuration>PT48H</bpmn2:timeDuration>
</bpmn2:timerEventDefinition>
</bpmn2:boundaryEvent>
<!-- 超时后执行 -->
<bpmn2:serviceTask id="autoApprove" name="超时自动通过"
flowable:delegateExpression="${autoApproveDelegate}"/>@Component("autoApproveDelegate")
public class AutoApproveDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
// 自动设置通过状态
execution.setVariable("approved", true);
execution.setVariable("comment", "超时未审批,系统默认通过");
// 记录超时日志
log.warn("流程实例 {} 审批超时, 已自动通过",
execution.getProcessInstanceId());
}
}流程历史查询
@RestController
@RequestMapping("/api/history")
public class HistoryController {
@Autowired
private HistoryService historyService;
@Autowired
private RuntimeService runtimeService;
// 查询个人历史流程
@GetMapping("/my-processes/{userId}")
public Result getMyHistory(@PathVariable String userId,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int size) {
List<HistoricProcessInstance> list = historyService
.createHistoricProcessInstanceQuery()
.startedBy(userId)
.orderByProcessInstanceStartTime().desc()
.listPage((page - 1) * size, size);
long count = historyService.createHistoricProcessInstanceQuery()
.startedBy(userId)
.count();
return Result.success(new PageResult<>(list, count));
}
// 查询流程追踪图数据
@GetMapping("/trace/{processInstanceId}")
public Result getTraceData(@PathVariable String processInstanceId) {
// 已完成的节点
List<HistoricActivityInstance> finished = historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.finished()
.orderByHistoricActivityInstanceStartTime().asc()
.list();
// 当前活动节点
List<HistoricActivityInstance> running = historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.unfinished()
.list();
// 构建流程图追踪数据
TraceVo trace = new TraceVo();
trace.setFinishedActivities(finished.stream()
.map(ActivityVo::from)
.collect(Collectors.toList()));
trace.setCurrentActivities(running.stream()
.map(ActivityVo::from)
.collect(Collectors.toList()));
return Result.success(trace);
}
// 按条件搜索历史流程
@PostMapping("/search")
public Result searchHistory(@RequestBody HistorySearchDto dto) {
HistoricProcessInstanceQuery query = historyService
.createHistoricProcessInstanceQuery()
.orderByProcessInstanceStartTime().desc();
if (StringUtils.hasText(dto.getProcessKey())) {
query.processDefinitionKey(dto.getProcessKey());
}
if (StringUtils.hasText(dto.getStartUser())) {
query.startedBy(dto.getStartUser());
}
if (dto.getStartTime() != null) {
query.startedAfter(dto.getStartTime());
}
if (dto.getEndTime() != null) {
query.startedBefore(dto.getEndTime());
}
if (dto.getFinished() != null && dto.getFinished()) {
query.finished();
}
if (dto.getFinished() != null && !dto.getFinished()) {
query.unfinished();
}
List<HistoricProcessInstance> list = query.listPage(
(dto.getPage() - 1) * dto.getSize(), dto.getSize());
long count = query.count();
return Result.success(new PageResult<>(list, count));
}
}流程实例管理数据库表
Flowable 引擎的主要运行时和历史表:
| 表名 | 说明 |
|---|---|
ACT_RU_EXECUTION | 运行时流程实例 |
ACT_RU_TASK | 运行时任务 |
ACT_RU_VARIABLE | 运行时变量 |
ACT_RU_IDENTITYLINK | 运行时身份关联 |
ACT_HI_PROCINST | 历史流程实例 |
ACT_HI_TASKINST | 历史任务 |
ACT_HI_ACTINST | 历史活动节点 |
ACT_HI_VARINST | 历史变量 |
ACT_HI_COMMENT | 历史批注 |
ACT_RE_PROCDEF | 流程定义 |
ACT_RE_DEPLOYMENT | 部署信息 |
ACT_GE_BYTEARRAY | 资源文件(BPMN/图片) |
-- 统计各流程定义的数量
SELECT
pd.KEY_ AS process_key,
pd.NAME_ AS process_name,
pd.VERSION_ AS version,
COUNT(pi.ID_) AS instance_count
FROM ACT_RE_PROCDEF pd
LEFT JOIN ACT_HI_PROCINST pi ON pd.ID_ = pi.PROC_DEF_ID_
GROUP BY pd.KEY_, pd.NAME_, pd.VERSION_
ORDER BY process_key, version DESC;
-- 查询某用户的待办任务汇总
SELECT
t.ID_ AS task_id,
t.NAME_ AS task_name,
t.CREATE_TIME_ AS create_time,
t.DUE_DATE_ AS due_date,
pi.BUSINESS_KEY_ AS business_key
FROM ACT_RU_TASK t
JOIN ACT_RU_EXECUTION pi ON t.PROC_INST_ID_ = pi.PROC_INST_ID_
WHERE t.ASSIGNEE_ = 'user123'
AND t.SUSPENSION_STATE_ = 1
ORDER BY t.CREATE_TIME_ DESC;
-- 查询流程超时任务
SELECT
t.ID_ AS task_id,
t.NAME_ AS task_name,
t.ASSIGNEE_,
t.CREATE_TIME_,
t.DUE_DATE_,
timestampdiff(HOUR, t.CREATE_TIME_, NOW()) AS overdue_hours
FROM ACT_RU_TASK t
WHERE t.DUE_DATE_ IS NOT NULL
AND t.DUE_DATE_ < NOW()
AND t.SUSPENSION_STATE_ = 1
ORDER BY t.DUE_DATE_ ASC;