Appearance
createHistoricProcessInstanceQuery
Flowable 7.1.0 摘要:创建历史流程实例查询对象,用于查询已完成或正在运行的流程实例历史数据。
方法签名与说明
HistoricProcessInstanceQuery createHistoricProcessInstanceQuery()
创建一个历史流程实例查询构建器,用于查询所有流程实例的历史记录(包括正在运行和已完成的)。与RuntimeService的查询不同,历史查询可以查到已结束的流程实例。
Returns:
- HistoricProcessInstanceQuery - 历史流程实例查询构建器
常见使用场景
1. 流程执行报表
企业需要统计一段时间内的流程执行情况,包括已完成的流程数量、平均耗时、完成率等。
2. 流程审计追溯
审计人员需要查询某个业务单据的完整流程历史,包括何时启动、何时结束、经过了哪些环节。
3. 用户工作量统计
统计某个员工参与了多少流程,作为绩效考核的依据。
4. 流程异常分析
查询长时间未完成的流程实例,分析是否存在流程卡住或异常的情况。
Kotlin + Spring Boot 调用示例
示例1:查询用户发起的所有流程
kotlin
import org.flowable.engine.HistoryService
import org.springframework.stereotype.Service
import java.util.Date
data class HistoricProcessInfo(
val processInstanceId: String,
val processDefinitionName: String,
val businessKey: String?,
val startTime: Date,
val endTime: Date?,
val duration: Long?,
val isFinished: Boolean
)
@Service
class UserProcessHistoryService(
private val historyService: HistoryService
) {
/**
* 查询用户发起的所有流程
* 企业场景:员工个人工作台,查看自己提交的所有申请
*/
fun getUserInitiatedProcesses(userId: String): List<HistoricProcessInfo> {
val historicInstances = historyService.createHistoricProcessInstanceQuery()
.startedBy(userId)
.orderByProcessInstanceStartTime()
.desc()
.list()
return historicInstances.map { instance ->
HistoricProcessInfo(
processInstanceId = instance.id,
processDefinitionName = instance.processDefinitionName,
businessKey = instance.businessKey,
startTime = instance.startTime,
endTime = instance.endTime,
duration = instance.durationInMillis,
isFinished = instance.endTime != null
)
}
}
}示例2:统计流程完成情况(按月度)
kotlin
import org.flowable.engine.HistoryService
import org.springframework.stereotype.Service
import java.time.LocalDate
import java.time.ZoneId
import java.util.Date
data class MonthlyProcessStatistics(
val month: String,
val totalStarted: Long,
val totalCompleted: Long,
val completionRate: Double,
val avgDurationDays: Double
)
@Service
class ProcessStatisticsService(
private val historyService: HistoryService
) {
/**
* 统计月度流程执行情况
* 企业场景:管理层月度报表,了解流程执行效率
*/
fun getMonthlyStatistics(
year: Int,
month: Int,
processDefinitionKey: String? = null
): MonthlyProcessStatistics {
// 计算月份开始和结束时间
val startDate = LocalDate.of(year, month, 1)
val endDate = startDate.plusMonths(1)
val startTime = Date.from(startDate.atStartOfDay(ZoneId.systemDefault()).toInstant())
val endTime = Date.from(endDate.atStartOfDay(ZoneId.systemDefault()).toInstant())
// 构建查询
val queryBuilder = historyService.createHistoricProcessInstanceQuery()
.startedAfter(startTime)
.startedBefore(endTime)
processDefinitionKey?.let {
queryBuilder.processDefinitionKey(it)
}
// 查询已启动的流程总数
val totalStarted = queryBuilder.count()
// 查询已完成的流程
val completedInstances = queryBuilder.finished().list()
val totalCompleted = completedInstances.size.toLong()
// 计算完成率
val completionRate = if (totalStarted > 0) {
(totalCompleted.toDouble() / totalStarted) * 100
} else 0.0
// 计算平均耗时(天)
val avgDuration = if (completedInstances.isNotEmpty()) {
val totalDuration = completedInstances.sumOf { it.durationInMillis ?: 0L }
(totalDuration.toDouble() / completedInstances.size) / (1000 * 60 * 60 * 24)
} else 0.0
return MonthlyProcessStatistics(
month = "$year-${month.toString().padStart(2, '0')}",
totalStarted = totalStarted,
totalCompleted = totalCompleted,
completionRate = String.format("%.2f", completionRate).toDouble(),
avgDurationDays = String.format("%.2f", avgDuration).toDouble()
)
}
}示例3:查询超时未完成的流程
kotlin
import org.flowable.engine.HistoryService
import org.springframework.stereotype.Service
import java.util.Date
import java.util.Calendar
data class TimeoutProcessInfo(
val processInstanceId: String,
val businessKey: String?,
val processName: String,
val startTime: Date,
val startUser: String,
val runningDays: Long
)
@Service
class TimeoutProcessMonitorService(
private val historyService: HistoryService
) {
/**
* 查询超时未完成的流程
* 企业场景:流程监控告警,发现长时间未处理的流程
*/
fun getTimeoutProcesses(
processDefinitionKey: String,
timeoutDays: Int
): List<TimeoutProcessInfo> {
// 计算超时时间点
val calendar = Calendar.getInstance()
calendar.add(Calendar.DAY_OF_MONTH, -timeoutDays)
val timeoutDate = calendar.time
// 查询未完成且超过指定天数的流程
val instances = historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.unfinished() // 未完成的流程
.startedBefore(timeoutDate) // 在超时时间点之前启动的
.orderByProcessInstanceStartTime()
.asc()
.list()
val now = System.currentTimeMillis()
return instances.map { instance ->
val runningDays = (now - instance.startTime.time) / (1000 * 60 * 60 * 24)
TimeoutProcessInfo(
processInstanceId = instance.id,
businessKey = instance.businessKey,
processName = instance.processDefinitionName,
startTime = instance.startTime,
startUser = instance.startUserId,
runningDays = runningDays
)
}
}
/**
* 发送超时提醒
*/
fun sendTimeoutAlert(processDefinitionKey: String, timeoutDays: Int) {
val timeoutProcesses = getTimeoutProcesses(processDefinitionKey, timeoutDays)
if (timeoutProcesses.isNotEmpty()) {
println("=== 流程超时告警 ===")
println("超时流程数: ${timeoutProcesses.size}")
timeoutProcesses.forEach { process ->
println("- 业务单号: ${process.businessKey}")
println(" 发起人: ${process.startUser}")
println(" 运行天数: ${process.runningDays}天")
}
}
}
}示例4:流程历史详情查询
kotlin
import org.flowable.engine.HistoryService
import org.springframework.stereotype.Service
import java.text.SimpleDateFormat
import java.util.Date
data class ProcessHistoryDetail(
val processInstanceId: String,
val processName: String,
val businessKey: String?,
val startUser: String,
val startTime: String,
val endTime: String?,
val status: String,
val durationDays: String?,
val deleteReason: String?
)
@Service
class ProcessHistoryDetailService(
private val historyService: HistoryService
) {
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
/**
* 获取流程历史详情
* 企业场景:流程审计,查看某个具体流程的完整执行情况
*/
fun getProcessHistoryDetail(processInstanceId: String): ProcessHistoryDetail {
val instance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult()
?: throw IllegalArgumentException("流程实例不存在: $processInstanceId")
val status = when {
instance.endTime != null && instance.deleteReason == null -> "已完成"
instance.deleteReason != null -> "已终止"
else -> "进行中"
}
val durationDays = instance.durationInMillis?.let {
String.format("%.2f", it.toDouble() / (1000 * 60 * 60 * 24))
}
return ProcessHistoryDetail(
processInstanceId = instance.id,
processName = instance.processDefinitionName,
businessKey = instance.businessKey,
startUser = instance.startUserId,
startTime = dateFormat.format(instance.startTime),
endTime = instance.endTime?.let { dateFormat.format(it) },
status = status,
durationDays = durationDays,
deleteReason = instance.deleteReason
)
}
}示例5:流程执行效率分析
kotlin
import org.flowable.engine.HistoryService
import org.springframework.stereotype.Service
import java.util.Date
import java.util.Calendar
data class ProcessEfficiencyReport(
val processName: String,
val period: String,
val totalCount: Long,
val completedCount: Long,
val completionRate: Double,
val avgDurationHours: Double,
val minDurationHours: Double,
val maxDurationHours: Double
)
@Service
class ProcessEfficiencyAnalyzer(
private val historyService: HistoryService
) {
/**
* 分析流程执行效率
* 企业场景:流程优化,找出效率瓶颈
*/
fun analyzeProcessEfficiency(
processDefinitionKey: String,
startDate: Date,
endDate: Date
): ProcessEfficiencyReport {
// 查询所有已完成的流程
val completedInstances = historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.finishedAfter(startDate)
.finishedBefore(endDate)
.finished()
.list()
// 查询总启动数
val totalCount = historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.startedAfter(startDate)
.startedBefore(endDate)
.count()
val completedCount = completedInstances.size.toLong()
// 计算完成率
val completionRate = if (totalCount > 0) {
(completedCount.toDouble() / totalCount) * 100
} else 0.0
// 计算耗时统计(小时)
val durations = completedInstances.mapNotNull { it.durationInMillis }
.map { it.toDouble() / (1000 * 60 * 60) }
val avgDuration = if (durations.isNotEmpty()) durations.average() else 0.0
val minDuration = durations.minOrNull() ?: 0.0
val maxDuration = durations.maxOrNull() ?: 0.0
val processName = completedInstances.firstOrNull()?.processDefinitionName
?: processDefinitionKey
return ProcessEfficiencyReport(
processName = processName,
period = "${startDate} 至 ${endDate}",
totalCount = totalCount,
completedCount = completedCount,
completionRate = String.format("%.2f", completionRate).toDouble(),
avgDurationHours = String.format("%.2f", avgDuration).toDouble(),
minDurationHours = String.format("%.2f", minDuration).toDouble(),
maxDurationHours = String.format("%.2f", maxDuration).toDouble()
)
}
}示例6:按业务Key查询流程历史
kotlin
import org.flowable.engine.HistoryService
import org.springframework.stereotype.Service
@Service
class BusinessKeyHistoryService(
private val historyService: HistoryService
) {
/**
* 根据业务Key查询流程历史
* 企业场景:在业务单据详情页,展示该单据的流程历史
*/
fun getProcessHistoryByBusinessKey(businessKey: String): Map<String, Any>? {
val instance = historyService.createHistoricProcessInstanceQuery()
.processInstanceBusinessKey(businessKey)
.singleResult()
?: return null
return mapOf(
"processInstanceId" to instance.id,
"processName" to instance.processDefinitionName,
"businessKey" to instance.businessKey,
"startTime" to instance.startTime,
"endTime" to (instance.endTime ?: "进行中"),
"status" to if (instance.endTime != null) "已完成" else "进行中",
"startUser" to instance.startUserId,
"duration" to (instance.durationInMillis?.let { "${it / 1000 / 60}分钟" } ?: "N/A")
)
}
/**
* 示例:查询订单的流程历史
*/
fun getOrderProcessHistory(orderId: String): Map<String, Any>? {
return getProcessHistoryByBusinessKey("ORDER-$orderId")
}
}示例7:流程历史REST API
kotlin
import org.flowable.engine.HistoryService
import org.springframework.format.annotation.DateTimeFormat
import org.springframework.web.bind.annotation.*
import java.util.Date
@RestController
@RequestMapping("/api/history/processes")
class ProcessHistoryController(
private val historyService: HistoryService
) {
/**
* 分页查询流程历史
*/
@GetMapping
fun listProcessHistory(
@RequestParam(required = false) processDefinitionKey: String?,
@RequestParam(required = false) businessKey: String?,
@RequestParam(required = false) startUser: String?,
@RequestParam(required = false) finished: Boolean?,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") startDate: Date?,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") endDate: Date?,
@RequestParam(defaultValue = "1") page: Int,
@RequestParam(defaultValue = "10") size: Int
): Map<String, Any> {
val query = historyService.createHistoricProcessInstanceQuery()
// 动态添加查询条件
processDefinitionKey?.let { query.processDefinitionKey(it) }
businessKey?.let { query.processInstanceBusinessKey(it) }
startUser?.let { query.startedBy(it) }
startDate?.let { query.startedAfter(it) }
endDate?.let { query.startedBefore(it) }
finished?.let {
if (it) query.finished() else query.unfinished()
}
// 查询总数
val total = query.count()
// 分页查询
val instances = query
.orderByProcessInstanceStartTime()
.desc()
.listPage((page - 1) * size, size)
return mapOf(
"data" to instances.map { instance ->
mapOf(
"id" to instance.id,
"processName" to instance.processDefinitionName,
"businessKey" to instance.businessKey,
"startUser" to instance.startUserId,
"startTime" to instance.startTime,
"endTime" to instance.endTime,
"status" to if (instance.endTime != null) "已完成" else "进行中"
)
},
"total" to total,
"page" to page,
"size" to size
)
}
/**
* 获取流程实例详情
*/
@GetMapping("/{processInstanceId}")
fun getProcessHistoryDetail(@PathVariable processInstanceId: String): Map<String, Any> {
val instance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult()
?: throw IllegalArgumentException("流程实例不存在")
return mapOf(
"processInstanceId" to instance.id,
"processDefinitionId" to instance.processDefinitionId,
"processName" to instance.processDefinitionName,
"businessKey" to instance.businessKey,
"startUser" to instance.startUserId,
"startTime" to instance.startTime,
"endTime" to instance.endTime,
"duration" to instance.durationInMillis,
"deleteReason" to instance.deleteReason
)
}
}注意事项
1. 历史数据范围
- 查询结果包含正在运行和已完成的流程实例
- 使用
finished()只查询已完成的 - 使用
unfinished()只查询未完成的
2. 性能优化
- 大数据量场景必须使用分页查询
listPage() - 添加时间范围条件,避免全表扫描
- 考虑对历史数据建立索引
3. 数据完整性
- 历史数据不会因为删除运行时数据而丢失
- 除非手动调用删除历史数据的API
4. 多租户隔离
- 多租户场景务必添加
processInstanceTenantId()条件
5. 时区问题
- 时间查询注意时区转换
- 建议统一使用UTC时间
相关 API
HistoryService.createHistoricProcessInstanceQuery()- 创建查询HistoryService.createHistoricTaskInstanceQuery()- 查询历史任务HistoryService.createHistoricActivityInstanceQuery()- 查询历史活动HistoryService.createHistoricVariableInstanceQuery()- 查询历史变量HistoryService.deleteHistoricProcessInstance()- 删除历史记录
最佳实践
1. 定期归档历史数据
kotlin
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
fun archiveOldProcessHistory() {
val oneYearAgo = Calendar.getInstance()
oneYearAgo.add(Calendar.YEAR, -1)
val oldInstances = historyService.createHistoricProcessInstanceQuery()
.finishedBefore(oneYearAgo.time)
.list()
// 归档逻辑...
}2. 缓存统计结果
kotlin
@Cacheable("processStatistics", key = "#processKey + '-' + #year + '-' + #month")
fun getCachedStatistics(processKey: String, year: Int, month: Int) {
// 统计逻辑...
}本文档说明
- 基于 Flowable 7.1.0 版本编写
- 所有示例均可直接在 Spring Boot + Kotlin 项目中使用
- 示例场景来自真实企业应用,具有实际参考价值