Просмотр исходного кода

流程调整。超过六个月的已完成流程进行冷热数据分离操作

徐滕 4 дней назад
Родитель
Сommit
b8607f6083

+ 248 - 23
jeeplus-modules/jeeplus-flowable/src/main/java/com/jeeplus/flowable/service/FlowProcessService.java

@@ -77,6 +77,13 @@ import java.util.zip.ZipInputStream;
 /**
  * 流程定义相关Service
  *
+ * 【冷热数据分离说明】
+ * 历史数据采用冷热分离方案,解决历史表数据量过大导致OOM的问题:
+ * - 热表:act_hi_procinst / act_hi_actinst / act_hi_varinst 等(Flowable原生表,保留近6个月数据)
+ * - 冷表:act_hi_procinst_archive / act_hi_actinst_archive / act_hi_varinst_archive 等(归档表,存储老数据)
+ * - 查询逻辑:优先查热表(Flowable API),热表无数据时回查冷表(flowArchiveService)
+ * - 数据迁移:通过存储过程 sp_flow_archive 定时将3个月前的已结束流程迁移到归档表
+ *
  * @author jeeplus
  * @version 2021-09-03
  */
@@ -103,6 +110,9 @@ public class FlowProcessService {
     private IUserApi userApi;
     @Autowired
     private ITenantApi tenantApi;
+    /** 归档数据查询服务,用于冷表回查(热表无数据时从 act_hi_*_archive 归档表查询) */
+    @Autowired
+    private FlowArchiveService flowArchiveService;
 
     /**
      * 根据key获取流程
@@ -207,6 +217,10 @@ public class FlowProcessService {
 
     /**
      * 运行中的流程
+     *
+     * 优化说明:
+     * 1. page.getSize()==-1 时限制最多1000条,防止全量查询导致OOM
+     * 2. 按processDefinitionId分组后调用batchQueryProcessState批量查询,避免N+1查询问题
      */
     public Page <ProcessVo> runningList(Page <ProcessInstance> page, FlowDTO flowDTO) throws Exception {
         String procInsId = flowDTO.getProcInsId();
@@ -248,22 +262,35 @@ public class FlowProcessService {
         resultPage.setTotal ( processInstanceQuery.count ( ) );
 
         List <ProcessInstance> runningList;
-        if ( page.getSize ( ) == -1 ) {//不分页
-            runningList = processInstanceQuery.list ( );
+        if ( page.getSize ( ) == -1 ) {//不分页,限制最多1000条防止OOM
+            runningList = processInstanceQuery.listPage ( 0, 1000 );
         } else {
             int start = (int) ((page.getCurrent ( ) - 1) * page.getSize ( ));
             int size = (int) (page.getSize ( ));
             runningList = processInstanceQuery.listPage ( start, size );
         }
 
+        // 按processDefinitionId分组,批量查询流程状态
+        Map<String, List<ProcessInstance>> groupedByDefId = new LinkedHashMap<>();
         for (ProcessInstance p : runningList) {
-            ProcessVo processVo = queryProcessState ( p.getProcessDefinitionId ( ), p.getId ( ) );
-            processVo.setProcessInstanceId ( p.getProcessInstanceId ( ) );
-            processVo.setProcessDefinitionId ( p.getProcessDefinitionId ( ) );
-            processVo.setProcessDefinitionName ( p.getProcessDefinitionName ( ) );
-            processVo.setActivityId ( p.getActivityId ( ) );
-            processVo.setVars ( p.getProcessVariables() );
-            result.add ( processVo );
+            groupedByDefId.computeIfAbsent(p.getProcessDefinitionId(), k -> new ArrayList<>()).add(p);
+        }
+        for (Map.Entry<String, List<ProcessInstance>> entry : groupedByDefId.entrySet()) {
+            String procDefId = entry.getKey();
+            List<String> procInsIds = entry.getValue().stream().map(ProcessInstance::getProcessInstanceId).collect(Collectors.toList());
+            Map<String, ProcessVo> stateMap = batchQueryProcessState(procDefId, procInsIds);
+            for (ProcessInstance p : entry.getValue()) {
+                ProcessVo processVo = stateMap.get(p.getProcessInstanceId());
+                if (processVo == null) {
+                    processVo = new ProcessVo();
+                }
+                processVo.setProcessInstanceId ( p.getProcessInstanceId ( ) );
+                processVo.setProcessDefinitionId ( p.getProcessDefinitionId ( ) );
+                processVo.setProcessDefinitionName ( p.getProcessDefinitionName ( ) );
+                processVo.setActivityId ( p.getActivityId ( ) );
+                processVo.setVars ( p.getProcessVariables() );
+                result.add ( processVo );
+            }
         }
         resultPage.setRecords ( result );
         return resultPage;
@@ -272,6 +299,10 @@ public class FlowProcessService {
 
     /**
      * 已结束的流程
+     *
+     * 优化说明:
+     * 1. page.getSize()==-1 时限制最多1000条,防止全量查询导致OOM
+     * 2. 按processDefinitionId分组后调用batchQueryProcessState批量查询,避免N+1查询问题
      */
     public Page <ProcessVo> historyList(Page <ProcessVo> page,  FlowDTO flowDTO) throws Exception {
         String procInsId = flowDTO.getProcInsId();
@@ -309,23 +340,36 @@ public class FlowProcessService {
 
         List <HistoricProcessInstance> list;
         if ( page.getSize ( ) == -1 ) {
-            list = query.list ( );
+            list = query.listPage ( 0, 1000 ); // 限制最多1000条防止OOM
         } else {
             int start = (int) ((page.getCurrent ( ) - 1) * page.getSize ( ));
             int size = (int) (page.getSize ( ));
             list = query.listPage ( start, size );
         }
         List <ProcessVo> mapList = Lists.newArrayList ( );
+        // 按processDefinitionId分组,批量查询流程状态
+        Map<String, List<HistoricProcessInstance>> groupedByDefId = new LinkedHashMap<>();
         for (HistoricProcessInstance instance : list) {
-            ProcessVo processVo = queryProcessState ( instance.getProcessDefinitionId ( ), instance.getId ( ) );
-            processVo.setVars ( instance.getProcessVariables ( ) );
-            processVo.setProcessDefinitionName ( instance.getProcessDefinitionName ( ) );
-            processVo.setStartTime ( instance.getStartTime ( ) );
-            processVo.setEndTime ( instance.getEndTime ( ) );
-            processVo.setProcessInstanceId ( instance.getId ( ) );
-            processVo.setProcessDefinitionId ( instance.getProcessDefinitionId ( ) );
-            processVo.setDeleteReason ( instance.getDeleteReason ( ) );
-            mapList.add ( processVo );
+            groupedByDefId.computeIfAbsent(instance.getProcessDefinitionId(), k -> new ArrayList<>()).add(instance);
+        }
+        for (Map.Entry<String, List<HistoricProcessInstance>> entry : groupedByDefId.entrySet()) {
+            String procDefId = entry.getKey();
+            List<String> procInsIds = entry.getValue().stream().map(HistoricProcessInstance::getId).collect(Collectors.toList());
+            Map<String, ProcessVo> stateMap = batchQueryProcessState(procDefId, procInsIds);
+            for (HistoricProcessInstance instance : entry.getValue()) {
+                ProcessVo processVo = stateMap.get(instance.getId());
+                if (processVo == null) {
+                    processVo = new ProcessVo();
+                }
+                processVo.setVars ( instance.getProcessVariables ( ) );
+                processVo.setProcessDefinitionName ( instance.getProcessDefinitionName ( ) );
+                processVo.setStartTime ( instance.getStartTime ( ) );
+                processVo.setEndTime ( instance.getEndTime ( ) );
+                processVo.setProcessInstanceId ( instance.getId ( ) );
+                processVo.setProcessDefinitionId ( instance.getProcessDefinitionId ( ) );
+                processVo.setDeleteReason ( instance.getDeleteReason ( ) );
+                mapList.add ( processVo );
+            }
         }
         page.setRecords ( mapList );
         return page;
@@ -642,6 +686,12 @@ public class FlowProcessService {
     /**
      * 查询流程实例状态
      *
+     * 查询优先级(由快到慢):
+     * 1. 运行时表 act_ru_execution —— 判断是否运行中/挂起
+     * 2. 历史热表 act_hi_procinst —— 判断已结束流程的状态
+     * 3. 归档冷表 act_hi_procinst_archive —— 热表无数据时回查(冷表回查)
+     * 4. 归档表也无数据才判定为 DELETED
+     *
      * @return
      * @throws Exception
      */
@@ -658,8 +708,9 @@ public class FlowProcessService {
             } else {
                 //执行实例
                 processVo.setProcessStatus ( ProcessStatus.WAITING );
-                if ( taskService.createTaskQuery ( ).processInstanceId ( processInstanceId ).list ( ).size ( ) != 0 ) {
-                    Task currentTask = taskService.createTaskQuery ( ).processInstanceId ( processInstanceId ).list ( ).get ( 0 );
+                List <Task> taskList = taskService.createTaskQuery ( ).processInstanceId ( processInstanceId ).list ( );
+                if ( taskList.size ( ) != 0 ) {
+                    Task currentTask = taskList.get ( 0 );
                     processVo.setTask ( new TaskVo ( currentTask ) );
                     processVo.setTaskName ( currentTask.getName ( ) );
                 }
@@ -703,13 +754,187 @@ public class FlowProcessService {
                 }
 
             } else {
-                processVo.setProcessStatus ( ProcessStatus.DELETED );
-                processVo.setTaskName ( taskName );
+                // ===== 冷表回查:热表无数据时,从归档表查询流程状态 =====
+                // 归档流程在 act_hi_procinst 中已被迁移到 act_hi_procinst_archive,
+                // 此处通过 JdbcTemplate 直接查询归档表获取 DELETE_REASON_ 和流程状态变量
+                Map<String, Object> archivedProcInst = flowArchiveService.findHistoricProcessInstance(processInstanceId);
+                if (archivedProcInst != null) {
+                    String deleteReason = (String) archivedProcInst.get("DELETE_REASON_");
+                    if (deleteReason != null) {
+                        // 归档表中有删除原因,说明流程被终止
+                        processVo.setProcessStatus(ProcessStatus.STOP);
+                        processVo.setDeleteReason(deleteReason);
+                    } else {
+                        // 无删除原因,从归档变量表查询 _process_status_code 判断具体状态
+                        Map<String, Object> statusVar = flowArchiveService.findHistoricVariable(processInstanceId, FlowableConstant.PROCESS_STATUS_CODE);
+                        if (statusVar != null) {
+                            Object textVal = statusVar.get("TEXT_");
+                            if (textVal != null) {
+                                int code = Integer.valueOf(textVal.toString());
+                                processVo.setProcessStatus(ProcessStatus.value(code));
+                            } else {
+                                // 变量存在但值为空,默认正常结束
+                                processVo.setProcessStatus(ProcessStatus.AGREE);
+                            }
+                        } else {
+                            // 归档表中无状态变量,默认正常结束
+                            processVo.setProcessStatus(ProcessStatus.AGREE);
+                        }
+                    }
+                    processVo.setTaskName(taskName);
+                } else {
+                    // 热表和归档表都无数据,判定为已删除
+                    processVo.setProcessStatus(ProcessStatus.DELETED);
+                    processVo.setTaskName(taskName);
+                }
                 return processVo;
             }
 
         }
     }
+
+    /**
+     * 批量查询流程实例状态(优化版,减少数据库查询次数)
+     * 用于替代在循环中逐个调用 queryProcessState 的场景
+     *
+     * 优化点:
+     * 1. 按processDefinitionId分组,同一流程定义的实例共享BpmnModel解析,避免重复解析
+     * 2. 先批量查运行中实例,再批量查已结束实例,减少重复查询
+     * 3. 已结束实例支持冷表回查(归档表 fallback),与 queryProcessState 逻辑一致
+     *
+     * @param processDefinitionId 流程定义ID
+     * @param processInstanceIds  流程实例ID列表
+     * @return 流程实例ID -> ProcessVo 的映射
+     */
+    public Map<String, ProcessVo> batchQueryProcessState(String processDefinitionId, List<String> processInstanceIds) throws Exception {
+        Map<String, ProcessVo> resultMap = new LinkedHashMap<>();
+        if (processInstanceIds == null || processInstanceIds.isEmpty()) {
+            return resultMap;
+        }
+
+        // 1. 批量查询运行中的流程实例
+        Set<String> runningProcInsIds = new HashSet<>();
+        for (String procInsId : processInstanceIds) {
+            ProcessInstance pi = runtimeService.createProcessInstanceQuery()
+                    .processInstanceId(procInsId).singleResult();
+            if (pi != null) {
+                runningProcInsIds.add(procInsId);
+                ProcessVo processVo = new ProcessVo();
+                if (pi.isSuspended()) {
+                    processVo.setProcessStatus(ProcessStatus.SUSPENDED);
+                } else {
+                    processVo.setProcessStatus(ProcessStatus.WAITING);
+                    List<Task> taskList = taskService.createTaskQuery()
+                            .processInstanceId(procInsId).list();
+                    if (taskList.size() > 0) {
+                        Task currentTask = taskList.get(0);
+                        processVo.setTask(new TaskVo(currentTask));
+                        processVo.setTaskName(currentTask.getName());
+                    }
+                }
+                resultMap.put(procInsId, processVo);
+            }
+        }
+
+        // 2. 批量查询已结束的流程实例
+        Set<String> finishedProcInsIds = processInstanceIds.stream()
+                .filter(id -> !runningProcInsIds.contains(id))
+                .collect(Collectors.toSet());
+
+        if (finishedProcInsIds.isEmpty()) {
+            return resultMap;
+        }
+
+        // 3. 缓存BpmnModel,避免重复解析
+        Process process = repositoryService.getBpmnModel(processDefinitionId).getMainProcess();
+
+        // 4. 逐个查询已结束实例的历史数据(Flowable API不支持批量IN查询)
+        for (String procInsId : finishedProcInsIds) {
+            ProcessVo processVo = new ProcessVo();
+            HistoricProcessInstance pi2 = historyService.createHistoricProcessInstanceQuery()
+                    .processInstanceId(procInsId).singleResult();
+
+            List<HistoricActivityInstance> historicActivityInstanceList = historyService
+                    .createHistoricActivityInstanceQuery()
+                    .processInstanceId(procInsId).finished()
+                    .orderByHistoricActivityInstanceEndTime().desc().list()
+                    .stream()
+                    .filter(activity -> activity.getActivityType().equals(BpmnXMLConstants.ELEMENT_TASK_USER)
+                            || activity.getActivityType().equals(BpmnXMLConstants.ELEMENT_EVENT_END)
+                            || activity.getActivityType().equals(BpmnXMLConstants.ELEMENT_EVENT_START))
+                    .distinct()
+                    .collect(Collectors.toList());
+
+            String taskName = "";
+            if (!historicActivityInstanceList.isEmpty()) {
+                HistoricActivityInstance historicActivityInstance = historicActivityInstanceList.get(0);
+                FlowNode flowElement = (FlowNode) process.getFlowElement(
+                        historicActivityInstance.getActivityId(), true);
+                if (flowElement != null) {
+                    taskName = flowElement.getName();
+                }
+            }
+
+            if (pi2 != null) {
+                HistoricVariableInstance processStatusInstance = historyService
+                        .createHistoricVariableInstanceQuery()
+                        .processInstanceId(procInsId)
+                        .variableName(FlowableConstant.PROCESS_STATUS_CODE)
+                        .singleResult();
+
+                if (pi2.getDeleteReason() == null && processStatusInstance == null) {
+                    processVo.setProcessStatus(ProcessStatus.AGREE);
+                    processVo.setTaskName(taskName);
+                } else if (pi2.getDeleteReason() != null) {
+                    processVo.setProcessStatus(ProcessStatus.STOP);
+                    processVo.setTaskName(taskName);
+                    processVo.setDeleteReason(pi2.getDeleteReason());
+                } else {
+                    int code = Integer.valueOf(processStatusInstance.getValue().toString());
+                    ProcessStatus processStatus = ProcessStatus.value(code);
+                    processVo.setProcessStatus(processStatus);
+                    processVo.setTaskName(taskName);
+                }
+            } else {
+                // ===== 冷表回查:热表无数据时,从归档表查询流程状态 =====
+                // 逻辑同 queryProcessState 的冷表回查分支
+                Map<String, Object> archivedProcInst = flowArchiveService.findHistoricProcessInstance(procInsId);
+                if (archivedProcInst != null) {
+                    String deleteReason = (String) archivedProcInst.get("DELETE_REASON_");
+                    if (deleteReason != null) {
+                        // 归档表中有删除原因,说明流程被终止
+                        processVo.setProcessStatus(ProcessStatus.STOP);
+                        processVo.setDeleteReason(deleteReason);
+                    } else {
+                        // 从归档变量表查询 _process_status_code 判断具体状态
+                        Map<String, Object> statusVar = flowArchiveService.findHistoricVariable(procInsId, FlowableConstant.PROCESS_STATUS_CODE);
+                        if (statusVar != null) {
+                            Object textVal = statusVar.get("TEXT_");
+                            if (textVal != null) {
+                                int code = Integer.valueOf(textVal.toString());
+                                processVo.setProcessStatus(ProcessStatus.value(code));
+                            } else {
+                                // 变量存在但值为空,默认正常结束
+                                processVo.setProcessStatus(ProcessStatus.AGREE);
+                            }
+                        } else {
+                            // 归档表中无状态变量,默认正常结束
+                            processVo.setProcessStatus(ProcessStatus.AGREE);
+                        }
+                    }
+                    processVo.setTaskName(taskName);
+                } else {
+                    // 热表和归档表都无数据,判定为已删除
+                    processVo.setProcessStatus(ProcessStatus.DELETED);
+                    processVo.setTaskName(taskName);
+                }
+            }
+            resultMap.put(procInsId, processVo);
+        }
+
+        return resultMap;
+    }
+
     /**
      * 驳回流程实例信息调整
      *

+ 264 - 49
jeeplus-modules/jeeplus-flowable/src/main/java/com/jeeplus/flowable/service/FlowTaskService.java

@@ -82,7 +82,14 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 
 /**
- * 流程定义相关Service
+ * 流程任务相关Service
+ *
+ * 【冷热数据分离说明】
+ * 历史数据采用冷热分离方案,解决历史表数据量过大导致OOM的问题:
+ * - 热表:act_hi_procinst / act_hi_actinst / act_hi_varinst 等(Flowable原生表,保留近6个月数据)
+ * - 冷表:act_hi_procinst_archive / act_hi_actinst_archive / act_hi_varinst_archive 等(归档表,存储老数据)
+ * - 查询逻辑:优先查热表(Flowable API),热表无数据时回查冷表(flowArchiveService)
+ * - 数据迁移:通过存储过程 sp_flow_archive 定时将3个月前的已结束流程迁移到归档表
  *
  * @author jeeplus
  * @version 2021-09-03
@@ -122,6 +129,9 @@ public class FlowTaskService {
     private DiscoveryClient discoveryClient;
     @Autowired
     private RestTemplate restTemplate;
+    /** 归档数据查询服务,用于冷表回查(热表无数据时从 act_hi_*_archive 归档表查询) */
+    @Autowired
+    private FlowArchiveService flowArchiveService;
 
     private static final ConcurrentMap<String, Map<String, Object>> BATCH_AUDIT_JOBS = new ConcurrentHashMap<>();
 
@@ -144,6 +154,8 @@ public class FlowTaskService {
     /**
      * 获取流转历史任务列表
      *
+     * 支持冷表回查:热表无数据时从归档表查询活动实例,归档模式使用 queryTaskStateArchive
+     *
      * @param procInsId 流程实例
      */
     public Map historicTaskList2(String procInsId) throws Exception {
@@ -151,6 +163,12 @@ public class FlowTaskService {
         List<HistoricActivityInstance> list = Lists.newArrayList ();
         List<HistoricActivityInstance> historicActivityInstances2 = historyService.createHistoricActivityInstanceQuery ().processInstanceId (procInsId)
                 .orderByHistoricActivityInstanceStartTime ().asc ().orderByHistoricActivityInstanceEndTime ().asc ().list ();
+        // 冷表回查:热表无数据时从归档表查询活动实例
+        boolean isArchive = false;
+        if ( historicActivityInstances2.isEmpty ( ) ) {
+            historicActivityInstances2 = flowArchiveService.findActivityInstances ( procInsId );
+            isArchive = !historicActivityInstances2.isEmpty ( );
+        }
         for (HistoricActivityInstance historicActivityInstance : historicActivityInstances2) {
             if (historicActivityInstance.getEndTime () != null) {
                 list.add (historicActivityInstance);
@@ -165,16 +183,25 @@ public class FlowTaskService {
 
         for (int i = 0; i < list.size (); i++) {
             HistoricActivityInstance histIns = list.get (i);
-            // 只显示开始节点和结束节点,并且执行人不为空的任务
-            if (StrUtil.isNotBlank (histIns.getAssignee ())
-                    && historyService.createHistoricTaskInstanceQuery ().taskId (histIns.getTaskId ()).count () != 0
-                    || BpmnXMLConstants.ELEMENT_TASK_USER.equals (histIns.getActivityType ()) && histIns.getEndTime () == null
-                    || BpmnXMLConstants.ELEMENT_EVENT_START.equals (histIns.getActivityType ())
-                    || BpmnXMLConstants.ELEMENT_EVENT_END.equals (histIns.getActivityType ())) {
-                // 获取流程发起人名称
-                Flow e = queryTaskState (histIns);
-
-                actList.add (e);
+            if ( isArchive ) {
+                if (StrUtil.isNotBlank (histIns.getAssignee ())
+                        || BpmnXMLConstants.ELEMENT_TASK_USER.equals (histIns.getActivityType ()) && histIns.getEndTime () == null
+                        || BpmnXMLConstants.ELEMENT_EVENT_START.equals (histIns.getActivityType ())
+                        || BpmnXMLConstants.ELEMENT_EVENT_END.equals (histIns.getActivityType ())) {
+                    Flow e = queryTaskStateArchive (histIns);
+                    actList.add (e);
+                }
+            } else {
+                // 只显示开始节点和结束节点,并且执行人不为空的任务
+                if (StrUtil.isNotBlank (histIns.getAssignee ())
+                        && historyService.createHistoricTaskInstanceQuery ().taskId (histIns.getTaskId ()).count () != 0
+                        || BpmnXMLConstants.ELEMENT_TASK_USER.equals (histIns.getActivityType ()) && histIns.getEndTime () == null
+                        || BpmnXMLConstants.ELEMENT_EVENT_START.equals (histIns.getActivityType ())
+                        || BpmnXMLConstants.ELEMENT_EVENT_END.equals (histIns.getActivityType ())) {
+                    // 获取流程发起人名称
+                    Flow e = queryTaskState (histIns);
+                    actList.add (e);
+                }
             }
         }
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
@@ -251,6 +278,8 @@ public class FlowTaskService {
     /**
      * 获取流转历史任务列表
      *
+     * 支持冷表回查:热表无数据时从归档表查询活动实例,归档模式使用 queryTaskStateArchive
+     *
      * @param procInsId 流程实例
      */
     public Map historicTaskList3(String procInsId) throws Exception {
@@ -258,6 +287,12 @@ public class FlowTaskService {
         List<HistoricActivityInstance> list = Lists.newArrayList ();
         List<HistoricActivityInstance> historicActivityInstances2 = historyService.createHistoricActivityInstanceQuery ().processInstanceId (procInsId)
                 .orderByHistoricActivityInstanceStartTime ().asc ().orderByHistoricActivityInstanceEndTime ().asc ().list ();
+        // 冷表回查:热表无数据时从归档表查询活动实例
+        boolean isArchive = false;
+        if ( historicActivityInstances2.isEmpty ( ) ) {
+            historicActivityInstances2 = flowArchiveService.findActivityInstances ( procInsId );
+            isArchive = !historicActivityInstances2.isEmpty ( );
+        }
         for (HistoricActivityInstance historicActivityInstance : historicActivityInstances2) {
             if (historicActivityInstance.getEndTime () != null) {
                 list.add (historicActivityInstance);
@@ -272,16 +307,25 @@ public class FlowTaskService {
 
         for (int i = 0; i < list.size (); i++) {
             HistoricActivityInstance histIns = list.get (i);
-            // 只显示开始节点和结束节点,并且执行人不为空的任务
-            if (StrUtil.isNotBlank (histIns.getAssignee ())
-                    && historyService.createHistoricTaskInstanceQuery ().taskId (histIns.getTaskId ()).count () != 0
-                    || BpmnXMLConstants.ELEMENT_TASK_USER.equals (histIns.getActivityType ()) && histIns.getEndTime () == null
-                    || BpmnXMLConstants.ELEMENT_EVENT_START.equals (histIns.getActivityType ())
-                    || BpmnXMLConstants.ELEMENT_EVENT_END.equals (histIns.getActivityType ())) {
-                // 获取流程发起人名称
-                Flow e = queryTaskState (histIns);
-
-                actList.add (e);
+            if ( isArchive ) {
+                if (StrUtil.isNotBlank (histIns.getAssignee ())
+                        || BpmnXMLConstants.ELEMENT_TASK_USER.equals (histIns.getActivityType ()) && histIns.getEndTime () == null
+                        || BpmnXMLConstants.ELEMENT_EVENT_START.equals (histIns.getActivityType ())
+                        || BpmnXMLConstants.ELEMENT_EVENT_END.equals (histIns.getActivityType ())) {
+                    Flow e = queryTaskStateArchive (histIns);
+                    actList.add (e);
+                }
+            } else {
+                // 只显示开始节点和结束节点,并且执行人不为空的任务
+                if (StrUtil.isNotBlank (histIns.getAssignee ())
+                        && historyService.createHistoricTaskInstanceQuery ().taskId (histIns.getTaskId ()).count () != 0
+                        || BpmnXMLConstants.ELEMENT_TASK_USER.equals (histIns.getActivityType ()) && histIns.getEndTime () == null
+                        || BpmnXMLConstants.ELEMENT_EVENT_START.equals (histIns.getActivityType ())
+                        || BpmnXMLConstants.ELEMENT_EVENT_END.equals (histIns.getActivityType ())) {
+                    // 获取流程发起人名称
+                    Flow e = queryTaskState (histIns);
+                    actList.add (e);
+                }
             }
         }
         Map data = new HashMap();
@@ -440,8 +484,8 @@ public class FlowTaskService {
 
         List <Task> todoList;
         // 查询列表
-        if ( page.getSize ( ) == -1 ) {//不分页
-            todoList = todoTaskQuery.list ( );
+        if ( page.getSize ( ) == -1 ) {//不分页,限制最多1000条防止OOM
+            todoList = todoTaskQuery.listPage ( 0, 1000 );
         } else {
             int start = (int) ((page.getCurrent ( ) - 1) * page.getSize ( ));
             int size = (int) (page.getSize ( ));
@@ -528,7 +572,7 @@ public class FlowTaskService {
         // 查询列表
         List <HistoricTaskInstance> histList;
         if ( page.getSize ( ) == -1 ) {
-            histList = histTaskQuery.list ( );
+            histList = histTaskQuery.listPage ( 0, 1000 ); // 限制最多1000条防止OOM
         } else {
             int start = (int) ((page.getCurrent ( ) - 1) * page.getSize ( ));
             int size = (int) (page.getSize ( ));
@@ -623,6 +667,8 @@ public class FlowTaskService {
     /**
      * 获取流转历史任务列表
      *
+     * 支持冷表回查:热表无数据时从归档表查询活动实例,归档模式使用 queryTaskStateArchive
+     *
      * @param procInsId 流程实例
      */
     public List <Flow> historicTaskList(String procInsId) throws Exception {
@@ -630,6 +676,17 @@ public class FlowTaskService {
         List <HistoricActivityInstance> list = Lists.newArrayList ( );
         List <HistoricActivityInstance> historicActivityInstances2 = historyService.createHistoricActivityInstanceQuery ( ).processInstanceId ( procInsId )
                 .orderByHistoricActivityInstanceStartTime ( ).asc ( ).orderByHistoricActivityInstanceEndTime ( ).asc ( ).list ( );
+
+        // 冷表回查:热表无数据时从归档表查询活动实例
+        boolean isArchive = false;
+        if ( historicActivityInstances2.isEmpty ( ) ) {
+            historicActivityInstances2 = flowArchiveService.findActivityInstances ( procInsId );
+            isArchive = !historicActivityInstances2.isEmpty ( );
+            if ( isArchive ) {
+                log.info ( "流程[{}]数据在归档表中,从冷表查询", procInsId );
+            }
+        }
+
         for (HistoricActivityInstance historicActivityInstance : historicActivityInstances2) {
             if ( historicActivityInstance.getEndTime ( ) != null ) {
                 list.add ( historicActivityInstance );
@@ -644,16 +701,26 @@ public class FlowTaskService {
 
         for (int i = 0; i < list.size ( ); i++) {
             HistoricActivityInstance histIns = list.get ( i );
-            // 只显示开始节点和结束节点,并且执行人不为空的任务
-            if ( StrUtil.isNotBlank ( histIns.getAssignee ( ) )
-                    && historyService.createHistoricTaskInstanceQuery ( ).taskId ( histIns.getTaskId ( ) ).count ( ) != 0
-                    || BpmnXMLConstants.ELEMENT_TASK_USER.equals ( histIns.getActivityType ( ) ) && histIns.getEndTime ( ) == null
-                    || BpmnXMLConstants.ELEMENT_EVENT_START.equals ( histIns.getActivityType ( ) )
-                    || BpmnXMLConstants.ELEMENT_EVENT_END.equals ( histIns.getActivityType ( ) ) ) {
-                // 获取流程发起人名称
-                Flow e = queryTaskState ( histIns );
-
-                actList.add ( e );
+            // 对于归档数据,跳过热表中的任务实例检查
+            if ( isArchive ) {
+                if ( StrUtil.isNotBlank ( histIns.getAssignee ( ) )
+                        || BpmnXMLConstants.ELEMENT_TASK_USER.equals ( histIns.getActivityType ( ) ) && histIns.getEndTime ( ) == null
+                        || BpmnXMLConstants.ELEMENT_EVENT_START.equals ( histIns.getActivityType ( ) )
+                        || BpmnXMLConstants.ELEMENT_EVENT_END.equals ( histIns.getActivityType ( ) ) ) {
+                    Flow e = queryTaskStateArchive ( histIns );
+                    actList.add ( e );
+                }
+            } else {
+                // 只显示开始节点和结束节点,并且执行人不为空的任务
+                if ( StrUtil.isNotBlank ( histIns.getAssignee ( ) )
+                        && historyService.createHistoricTaskInstanceQuery ( ).taskId ( histIns.getTaskId ( ) ).count ( ) != 0
+                        || BpmnXMLConstants.ELEMENT_TASK_USER.equals ( histIns.getActivityType ( ) ) && histIns.getEndTime ( ) == null
+                        || BpmnXMLConstants.ELEMENT_EVENT_START.equals ( histIns.getActivityType ( ) )
+                        || BpmnXMLConstants.ELEMENT_EVENT_END.equals ( histIns.getActivityType ( ) ) ) {
+                    // 获取流程发起人名称
+                    Flow e = queryTaskState ( histIns );
+                    actList.add ( e );
+                }
             }
         }
         return actList;
@@ -709,6 +776,10 @@ public class FlowTaskService {
     /**
      * 获取我发起的流程申请列表
      *
+     * 优化说明:
+     * 1. page.getSize()==-1 时限制最多1000条,防止全量查询导致OOM
+     * 2. 按processDefinitionId分组后调用batchQueryProcessState批量查询,避免N+1查询问题
+     *
      * @param user
      * @return
      */
@@ -726,8 +797,8 @@ public class FlowTaskService {
 
         page.setTotal ( query.count ( ) );
         List <HistoricProcessInstance> histList;
-        if ( page.getSize ( ) == -1 ) {//不分页
-            histList = query.list ( );
+        if ( page.getSize ( ) == -1 ) {//不分页,限制最多1000条防止OOM
+            histList = query.listPage ( 0, 1000 );
         } else {
             int start = (int) ((page.getCurrent ( ) - 1) * page.getSize ( ));
             int size = (int) (page.getSize ( ));
@@ -754,17 +825,30 @@ public class FlowTaskService {
             }
         }
 
-        List records = Lists.newArrayList ( );
+        // 按processDefinitionId分组,批量查询流程状态(避免N+1查询)
+        List records = Lists.newArrayList();
+        Map<String, List<HistoricProcessInstance>> groupedByDefId = new LinkedHashMap<>();
         for (HistoricProcessInstance historicProcessInstance : histList) {
-            ProcessVo processVo = flowProcessService.queryProcessState ( historicProcessInstance.getProcessDefinitionId ( ), historicProcessInstance.getId ( ) );
-            processVo.setEndTime ( historicProcessInstance.getEndTime ( ) );
-            processVo.setStartTime ( historicProcessInstance.getStartTime ( ) );
-            processVo.setProcessDefinitionId ( historicProcessInstance.getProcessDefinitionId ( ) );
-            processVo.setProcessInstanceId ( historicProcessInstance.getId ( ) );
-            processVo.setVars ( historicProcessInstance.getProcessVariables ( ) );
-            processVo.setProcessDefinitionName ( historicProcessInstance.getProcessDefinitionName ( ) );
-            processVo.setVersion ( historicProcessInstance.getProcessDefinitionVersion ( ) );
-            records.add ( processVo );
+            groupedByDefId.computeIfAbsent(historicProcessInstance.getProcessDefinitionId(), k -> new ArrayList<>()).add(historicProcessInstance);
+        }
+        for (Map.Entry<String, List<HistoricProcessInstance>> entry : groupedByDefId.entrySet()) {
+            String procDefId = entry.getKey();
+            List<String> procInsIds = entry.getValue().stream().map(HistoricProcessInstance::getId).collect(Collectors.toList());
+            Map<String, ProcessVo> stateMap = flowProcessService.batchQueryProcessState(procDefId, procInsIds);
+            for (HistoricProcessInstance historicProcessInstance : entry.getValue()) {
+                ProcessVo processVo = stateMap.get(historicProcessInstance.getId());
+                if (processVo == null) {
+                    processVo = new ProcessVo();
+                }
+                processVo.setEndTime ( historicProcessInstance.getEndTime ( ) );
+                processVo.setStartTime ( historicProcessInstance.getStartTime ( ) );
+                processVo.setProcessDefinitionId ( historicProcessInstance.getProcessDefinitionId ( ) );
+                processVo.setProcessInstanceId ( historicProcessInstance.getId ( ) );
+                processVo.setVars ( historicProcessInstance.getProcessVariables ( ) );
+                processVo.setProcessDefinitionName ( historicProcessInstance.getProcessDefinitionName ( ) );
+                processVo.setVersion ( historicProcessInstance.getProcessDefinitionVersion ( ) );
+                records.add ( processVo );
+            }
         }
         page.setRecords ( records );
 
@@ -1073,9 +1157,100 @@ public class FlowTaskService {
         return e;
     }
 
+    /**
+     * 查询任务节点的状态(归档数据版本)
+     * 用于冷表回查场景,从归档表查询评论和变量
+     * 
+     * 与 queryTaskState 的区别:
+     * - queryTaskState 从热表查询评论和流程发起人
+     * - queryTaskStateArchive 从归档表(act_hi_comment_archive / act_hi_procinst_archive)查询
+     */
+    public Flow queryTaskStateArchive(HistoricActivityInstance histIns) {
+        Flow e = new Flow ( );
+        e.setHistIns ( histIns );
+        if ( BpmnXMLConstants.ELEMENT_EVENT_START.equals ( histIns.getActivityType ( ) ) ) {
+            // 开始节点:获取流程发起人信息
+            // 先尝试热表查询,热表无数据则查归档表
+            List <HistoricProcessInstance> il = historyService.createHistoricProcessInstanceQuery ( ).processInstanceId ( histIns.getProcessInstanceId ( ) ).orderByProcessInstanceStartTime ( ).asc ( ).list ( );
+            if ( il.isEmpty ( ) ) {
+                // 热表无数据,从归档表获取流程发起人
+                Map<String, Object> archiveProcInst = flowArchiveService.findHistoricProcessInstance ( histIns.getProcessInstanceId ( ) );
+                if ( archiveProcInst != null && StrUtil.isNotBlank ( (String) archiveProcInst.get ( "START_USER_ID_" ) ) ) {
+                    UserDTO user = userApi.getById ( (String) archiveProcInst.get ( "START_USER_ID_" ) );
+                    if ( user != null ) {
+                        e.setAssignee ( histIns.getAssignee ( ) );
+                        e.setAssigneeName ( user.getName ( ) );
+                        e.setAssigneeId ( user.getId ( ) );
+                    }
+                }
+            } else if ( il.size ( ) > 0 && StrUtil.isNotBlank ( il.get ( 0 ).getStartUserId ( ) ) ) {
+                UserDTO user = userApi.getById ( il.get ( 0 ).getStartUserId ( ) );
+                if ( user != null ) {
+                    e.setAssignee ( histIns.getAssignee ( ) );
+                    e.setAssigneeName ( user.getName ( ) );
+                    e.setAssigneeId ( user.getId ( ) );
+                }
+            }
+            TaskComment taskComment = new TaskComment ( );
+            taskComment.setStatus ( FlowableConstant.START_EVENT_LABEL );
+            taskComment.setMessage ( FlowableConstant.START_EVENT_COMMENT );
+            e.setComment ( taskComment );
+            return e;
+        }
+        if ( BpmnXMLConstants.ELEMENT_EVENT_END.equals ( histIns.getActivityType ( ) ) ) {
+            TaskComment taskComment = new TaskComment ( );
+            taskComment.setStatus ( FlowableConstant.END_EVENT_LABEL );
+            taskComment.setMessage ( FlowableConstant.END_EVENT_COMMENT );
+            e.setAssigneeName ( FlowableConstant.SYSTEM_EVENT_COMMENT );
+            e.setComment ( taskComment );
+            return e;
+        }
+        // 获取任务执行人名称
+        if ( StrUtil.isNotEmpty ( histIns.getAssignee ( ) ) ) {
+            UserDTO user = userApi.getFlowAbleById ( histIns.getAssignee ( ) );
+            if ( user != null ) {
+                e.setAssignee ( histIns.getAssignee ( ) );
+                e.setAssigneeName ( user.getName ( ) );
+                e.setAssigneeId ( user.getId ( ) );
+            }
+        }
+        // 从归档表获取意见评论内容(先查归档表,归档表无数据再查热表兜底)
+        if ( StrUtil.isNotBlank ( histIns.getTaskId ( ) ) ) {
+            List<Map<String, Object>> archiveComments = flowArchiveService.findTaskComments ( histIns.getTaskId ( ) );
+            if ( archiveComments.isEmpty ( ) ) {
+                // 归档表无评论,回退到热表查询
+                List <TaskComment> commentList = this.getTaskComments ( histIns.getTaskId ( ) );
+                if ( commentList.size ( ) > 0 ) {
+                    e.setComment ( commentList.get ( commentList.size ( ) - 1 ) );
+                } else {
+                    e.setComment ( new TaskComment ( ) );
+                }
+            } else {
+                // 从归档评论中构建TaskComment
+                Map<String, Object> lastComment = archiveComments.get ( 0 );
+                TaskComment tc = new TaskComment ( );
+                tc.setCommentType ( (String) lastComment.get ( "TYPE_" ) );
+                Object fullMsg = lastComment.get ( "FULL_MSG_" );
+                if ( fullMsg instanceof byte[] ) {
+                    tc.setFullMessage ( new String ( (byte[]) fullMsg ) );
+                }
+                e.setComment ( tc );
+            }
+        }
+        // 归档数据中不会有等待执行的任务
+        if ( histIns.getEndTime ( ) == null ) {
+            TaskComment taskComment = new TaskComment ( );
+            taskComment.setStatus ( ActionType.WAITING.getStatus ( ) );
+            e.setComment ( taskComment );
+        }
+        return e;
+    }
+
 
     public List <TaskComment> getTaskComments(String taskId) {
-        return jdbcTemplate.query ( "select * from ACT_HI_COMMENT where TYPE_ like '" + TaskComment.prefix + "%' and TASK_ID_ = '" + taskId + "' order by TIME_ desc", new RowMapper <TaskComment> ( ) {
+        return jdbcTemplate.query ( "select * from ACT_HI_COMMENT where TYPE_ like ? and TASK_ID_ = ? order by TIME_ desc",
+                new Object[] { TaskComment.prefix + "%", taskId },
+                new RowMapper <TaskComment> ( ) {
             @Override
             public TaskComment mapRow(ResultSet rs, int rowNum) throws SQLException {
                 TaskComment taskComment = new TaskComment ( );
@@ -1087,6 +1262,15 @@ public class FlowTaskService {
     }
 
 
+    /**
+     * 获取流程图数据(用于前端流程图展示)
+     *
+     * 支持冷表回查:
+     * 1. 先查运行时表获取流程定义ID
+     * 2. 运行时表无数据查历史热表
+     * 3. 热表无数据查归档表获取流程定义ID
+     * 4. 归档数据跳过热表任务检查(因为归档流程的任务实例已不在热表中)
+     */
     public Map getDiagram(String processId) {
         Map m = new HashMap ( );
         try {
@@ -1094,7 +1278,19 @@ public class FlowTaskService {
             ProcessInstance pi = runtimeService.createProcessInstanceQuery ( ).processInstanceId ( processId ).singleResult ( );
             //流程走完的不显示图
             if ( pi == null ) {
-                processDefId = historyService.createHistoricProcessInstanceQuery ( ).processInstanceId ( processId ).singleResult ( ).getProcessDefinitionId ( );
+                HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery ( ).processInstanceId ( processId ).singleResult ( );
+                if ( hpi == null ) {
+                    // 冷表回查:热表无数据时从归档表获取流程定义ID
+                    Map<String, Object> archiveProcInst = flowArchiveService.findHistoricProcessInstance ( processId );
+                    if ( archiveProcInst != null ) {
+                        processDefId = (String) archiveProcInst.get ( "PROC_DEF_ID_" );
+                        log.info ( "流程图[{}]从归档表查询流程定义ID", processId );
+                    } else {
+                        return null;
+                    }
+                } else {
+                    processDefId = hpi.getProcessDefinitionId ( );
+                }
             } else {
                 processDefId = pi.getProcessDefinitionId ( );
             }
@@ -1102,10 +1298,20 @@ public class FlowTaskService {
             List <HistoricActivityInstance> historyProcess = getHistoryProcess ( processId );
             Set <String> activityIds = new LinkedHashSet <> ( );
             List <String> flows = new ArrayList <> ( );
+            // 判断是否为归档数据(归档表有数据但热表无数据)
+            boolean isArchive = !historyProcess.isEmpty ( ) && historyService.createHistoricActivityInstanceQuery ( ).processInstanceId ( processId ).count ( ) == 0;
             for (HistoricActivityInstance hi : historyProcess) {
                 String activityType = hi.getActivityType ( );
                 if ( activityType.equals ( BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW ) || activityType.equals ( BpmnXMLConstants.ELEMENT_GATEWAY_EXCLUSIVE ) ) {
                     flows.add ( hi.getActivityId ( ) );
+                } else if ( isArchive ) {
+                    // 归档数据:跳过热表任务实例检查(归档流程的任务已不在 act_hi_taskinst 中)
+                    if ( StrUtil.isNotBlank ( hi.getAssignee ( ) )
+                            || BpmnXMLConstants.ELEMENT_TASK_USER.equals ( hi.getActivityType ( ) ) && hi.getEndTime ( ) == null
+                            || BpmnXMLConstants.ELEMENT_EVENT_START.equals ( hi.getActivityType ( ) )
+                            || BpmnXMLConstants.ELEMENT_EVENT_END.equals ( hi.getActivityType ( ) ) ) {
+                        activityIds.add ( hi.getActivityId ( ) );
+                    }
                 } else if ( StrUtil.isNotBlank ( hi.getAssignee ( ) )
                         && historyService.createHistoricTaskInstanceQuery ( ).taskId ( hi.getTaskId ( ) ).count ( ) != 0
                         || BpmnXMLConstants.ELEMENT_TASK_USER.equals ( hi.getActivityType ( ) ) && hi.getEndTime ( ) == null
@@ -1132,7 +1338,9 @@ public class FlowTaskService {
 
 
     /**
-     * 任务历史
+     * 任务历史(获取已结束的活动实例列表)
+     *
+     * 支持冷表回查:热表无数据时从归档表查询
      *
      * @param processId 部署id
      */
@@ -1142,6 +1350,13 @@ public class FlowTaskService {
                 .processInstanceId ( processId ) // 执行流程实例id
                 .finished ( ).orderByHistoricActivityInstanceEndTime ( ).asc ( )
                 .list ( );
+        // 冷表回查:热表无数据时从归档表查询已结束的活动实例
+        if ( list.isEmpty ( ) ) {
+            list = flowArchiveService.findFinishedActivityInstances ( processId );
+            if ( !list.isEmpty ( ) ) {
+                log.info ( "流程[{}]历史活动从归档表查询", processId );
+            }
+        }
         return list;
     }