Prechádzať zdrojové kódy

超过3个月审核通过的历史流程数据进行冷数据处理。
前端界面版本更新优化调整

徐滕 3 dní pred
rodič
commit
bd2d35687d

+ 5 - 0
jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/flowable/factory/FlowableApiFallbackFactory.java

@@ -12,6 +12,7 @@ import org.springframework.stereotype.Component;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -26,6 +27,10 @@ public class FlowableApiFallbackFactory implements FallbackFactory <IFlowableApi
     public IFlowableApi create(Throwable throwable) {
         log.error ( "流程服务调用失败:{}", throwable.getMessage ( ) );
         return new IFlowableApi() {
+            @Override
+            public Map triggerArchive() {
+                return null;
+            }
 
             @Override
             public Map historicTaskList3(String procInsId) {

+ 7 - 0
jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/flowable/feign/IFlowableApi.java

@@ -138,4 +138,11 @@ public interface IFlowableApi {
      */
     @GetMapping(value = "/flowable/process/revokeProcIns")
     void revokeProcIns(@RequestParam(value = "procInsId")String procInsId);
+
+    /**
+     * 触发流程历史数据归档(将3个月前的已结束流程从热表迁移到冷表)
+     * 调用存储过程 sp_flow_archive
+     */
+    @GetMapping(value = "/flow/archive/trigger")
+    Map triggerArchive();
 }

+ 8 - 3
jeeplus-modules/jeeplus-flowable/src/main/java/com/jeeplus/flowable/controller/FlowArchiveController.java

@@ -3,6 +3,7 @@
  */
 package com.jeeplus.flowable.controller;
 
+import com.jeeplus.flowable.service.FlowArchiveService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.JdbcTemplate;
@@ -28,9 +29,12 @@ public class FlowArchiveController {
     @Autowired
     private JdbcTemplate jdbcTemplate;
 
+    @Autowired
+    private FlowArchiveService flowArchiveService;
+
     /**
      * 触发数据归档迁移
-     * 调用存储过程 sp_flow_archive,将3个月前已结束的流程数据从热表迁移到冷表
+     * 通过Java代码实现,将3个月前已结束的流程数据从热表迁移到冷表
      * XXL-Job配置: HTTP GET /flow/archive/trigger
      *
      * @return 迁移结果
@@ -40,10 +44,11 @@ public class FlowArchiveController {
         Map<String, Object> result = new HashMap<>();
         try {
             log.info("开始执行流程数据归档...");
-            jdbcTemplate.execute("CALL sp_flow_archive()");
-            log.info("流程数据归档执行完成");
+            Map<String, Object> archiveResult = flowArchiveService.executeArchive();
+            log.info("流程数据归档执行完成: {}", archiveResult);
             result.put("code", 200);
             result.put("msg", "归档执行成功");
+            result.put("data", archiveResult);
         } catch (Exception e) {
             log.error("流程数据归档执行失败", e);
             result.put("code", 500);

+ 120 - 0
jeeplus-modules/jeeplus-flowable/src/main/java/com/jeeplus/flowable/mapper/FlowArchiveMapper.java

@@ -0,0 +1,120 @@
+/**
+ * Copyright &copy; 2021-2026 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.flowable.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 流程归档数据 Mapper 接口
+ * 负责冷热数据分离的归档迁移操作
+ *
+ * @author jeeplus
+ * @version 2024-01-01
+ */
+@Mapper
+public interface FlowArchiveMapper {
+
+    /**
+     * 查询待归档的流程实例数量
+     * @param cutoffDate 截止日期
+     * @return 待归档数量
+     */
+    Integer countPendingArchive(@Param("cutoffDate") Date cutoffDate);
+
+    /**
+     * 分批迁移流程实例到归档表
+     * @param cutoffDate 截止日期
+     * @param batchSize 批次大小
+     * @return 影响行数
+     */
+    int insertProcinstArchive(@Param("cutoffDate") Date cutoffDate, @Param("batchSize") int batchSize);
+
+    /**
+     * 迁移活动实例到归档表
+     * @return 影响行数
+     */
+    int insertActinstArchive();
+
+    /**
+     * 迁移变量实例到归档表
+     * @return 影响行数
+     */
+    int insertVarinstArchive();
+
+    /**
+     * 迁移任务实例到归档表
+     * @return 影响行数
+     */
+    int insertTaskinstArchive();
+
+    /**
+     * 迁移参与者链接到归档表
+     * @return 影响行数
+     */
+    int insertIdentitylinkArchive();
+
+    /**
+     * 迁移评论到归档表
+     * @return 影响行数
+     */
+    int insertCommentArchive();
+
+    /**
+     * 删除热表中的活动实例
+     * @return 影响行数
+     */
+    int deleteActinst();
+
+    /**
+     * 删除热表中的变量实例
+     * @return 影响行数
+     */
+    int deleteVarinst();
+
+    /**
+     * 删除热表中的任务实例
+     * @return 影响行数
+     */
+    int deleteTaskinst();
+
+    /**
+     * 删除热表中的参与者链接
+     * @return 影响行数
+     */
+    int deleteIdentitylink();
+
+    /**
+     * 删除热表中的评论
+     * @return 影响行数
+     */
+    int deleteComment();
+
+    /**
+     * 删除热表中的流程实例
+     * @return 影响行数
+     */
+    int deleteProcinst();
+
+    /**
+     * 记录归档日志
+     * @param cutoffDate 截止日期
+     * @param procinstCount 流程实例数量
+     * @param actinstCount 活动实例数量
+     * @param varinstCount 变量实例数量
+     * @param taskinstCount 任务实例数量
+     * @param identitylinkCount 参与者链接数量
+     * @param commentCount 评论数量
+     */
+    void insertArchiveLog(@Param("cutoffDate") Date cutoffDate,
+                          @Param("procinstCount") int procinstCount,
+                          @Param("actinstCount") int actinstCount,
+                          @Param("varinstCount") int varinstCount,
+                          @Param("taskinstCount") int taskinstCount,
+                          @Param("identitylinkCount") int identitylinkCount,
+                          @Param("commentCount") int commentCount);
+}

+ 102 - 0
jeeplus-modules/jeeplus-flowable/src/main/java/com/jeeplus/flowable/mapper/xml/FlowArchiveMapper.xml

@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jeeplus.flowable.mapper.FlowArchiveMapper">
+
+    <!-- 查询待归档的流程实例数量 -->
+    <select id="countPendingArchive" resultType="java.lang.Integer">
+        SELECT COUNT(*) FROM act_hi_procinst
+        WHERE END_TIME_ IS NOT NULL AND END_TIME_ &lt; #{cutoffDate}
+    </select>
+
+    <!-- 分批迁移流程实例到归档表 -->
+    <insert id="insertProcinstArchive">
+        INSERT IGNORE INTO act_hi_procinst_archive
+        SELECT * FROM act_hi_procinst
+        WHERE END_TIME_ IS NOT NULL AND END_TIME_ &lt; #{cutoffDate}
+        LIMIT #{batchSize}
+    </insert>
+
+    <!-- 迁移活动实例到归档表 -->
+    <insert id="insertActinstArchive">
+        INSERT IGNORE INTO act_hi_actinst_archive
+        SELECT a.* FROM act_hi_actinst a
+        INNER JOIN act_hi_procinst_archive ar ON a.PROC_INST_ID_ = ar.PROC_INST_ID_
+        WHERE a.ID_ NOT IN (SELECT ID_ FROM act_hi_actinst_archive)
+    </insert>
+
+    <!-- 迁移变量实例到归档表 -->
+    <insert id="insertVarinstArchive">
+        INSERT IGNORE INTO act_hi_varinst_archive
+        SELECT v.* FROM act_hi_varinst v
+        INNER JOIN act_hi_procinst_archive ar ON v.PROC_INST_ID_ = ar.PROC_INST_ID_
+        WHERE v.ID_ NOT IN (SELECT ID_ FROM act_hi_varinst_archive)
+    </insert>
+
+    <!-- 迁移任务实例到归档表 -->
+    <insert id="insertTaskinstArchive">
+        INSERT IGNORE INTO act_hi_taskinst_archive
+        SELECT t.* FROM act_hi_taskinst t
+        INNER JOIN act_hi_procinst_archive ar ON t.PROC_INST_ID_ = ar.PROC_INST_ID_
+        WHERE t.ID_ NOT IN (SELECT ID_ FROM act_hi_taskinst_archive)
+    </insert>
+
+    <!-- 迁移参与者链接到归档表 -->
+    <insert id="insertIdentitylinkArchive">
+        INSERT IGNORE INTO act_hi_identitylink_archive
+        SELECT i.* FROM act_hi_identitylink i
+        INNER JOIN act_hi_procinst_archive ar ON i.PROC_INST_ID_ = ar.PROC_INST_ID_
+        WHERE i.ID_ NOT IN (SELECT ID_ FROM act_hi_identitylink_archive)
+    </insert>
+
+    <!-- 迁移评论到归档表 -->
+    <insert id="insertCommentArchive">
+        INSERT IGNORE INTO act_hi_comment_archive
+        SELECT c.* FROM act_hi_comment c
+        INNER JOIN act_hi_procinst_archive ar ON c.PROC_INST_ID_ = ar.PROC_INST_ID_
+        WHERE c.ID_ NOT IN (SELECT ID_ FROM act_hi_comment_archive)
+    </insert>
+
+    <!-- 删除热表中的活动实例 -->
+    <delete id="deleteActinst">
+        DELETE act_hi_actinst FROM act_hi_actinst
+        INNER JOIN act_hi_procinst_archive ar ON act_hi_actinst.PROC_INST_ID_ = ar.PROC_INST_ID_
+    </delete>
+
+    <!-- 删除热表中的变量实例 -->
+    <delete id="deleteVarinst">
+        DELETE act_hi_varinst FROM act_hi_varinst
+        INNER JOIN act_hi_procinst_archive ar ON act_hi_varinst.PROC_INST_ID_ = ar.PROC_INST_ID_
+    </delete>
+
+    <!-- 删除热表中的任务实例 -->
+    <delete id="deleteTaskinst">
+        DELETE act_hi_taskinst FROM act_hi_taskinst
+        INNER JOIN act_hi_procinst_archive ar ON act_hi_taskinst.PROC_INST_ID_ = ar.PROC_INST_ID_
+    </delete>
+
+    <!-- 删除热表中的参与者链接 -->
+    <delete id="deleteIdentitylink">
+        DELETE act_hi_identitylink FROM act_hi_identitylink
+        INNER JOIN act_hi_procinst_archive ar ON act_hi_identitylink.PROC_INST_ID_ = ar.PROC_INST_ID_
+    </delete>
+
+    <!-- 删除热表中的评论 -->
+    <delete id="deleteComment">
+        DELETE act_hi_comment FROM act_hi_comment
+        INNER JOIN act_hi_procinst_archive ar ON act_hi_comment.PROC_INST_ID_ = ar.PROC_INST_ID_
+    </delete>
+
+    <!-- 删除热表中的流程实例 -->
+    <delete id="deleteProcinst">
+        DELETE act_hi_procinst FROM act_hi_procinst
+        INNER JOIN act_hi_procinst_archive ar ON act_hi_procinst.ID_ = ar.PROC_INST_ID_
+    </delete>
+
+    <!-- 记录归档日志 -->
+    <insert id="insertArchiveLog">
+        INSERT INTO flow_archive_log
+        (archive_time, cutoff_date, procinst_count, actinst_count, varinst_count, taskinst_count, identitylink_count, comment_count, status)
+        VALUES (NOW(), #{cutoffDate}, #{procinstCount}, #{actinstCount}, #{varinstCount}, #{taskinstCount}, #{identitylinkCount}, #{commentCount}, 'SUCCESS')
+    </insert>
+
+</mapper>

+ 96 - 0
jeeplus-modules/jeeplus-flowable/src/main/java/com/jeeplus/flowable/service/FlowArchiveService.java

@@ -3,6 +3,7 @@
  */
 package com.jeeplus.flowable.service;
 
+import com.jeeplus.flowable.mapper.FlowArchiveMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.flowable.engine.impl.persistence.entity.HistoricActivityInstanceEntityImpl;
 import org.flowable.engine.history.HistoricActivityInstance;
@@ -30,6 +31,9 @@ public class FlowArchiveService {
     @Autowired
     private JdbcTemplate jdbcTemplate;
 
+    @Autowired
+    private FlowArchiveMapper flowArchiveMapper;
+
     /**
      * 从归档表查询活动实例列表
      */
@@ -98,6 +102,98 @@ public class FlowArchiveService {
     }
 
     /**
+     * 执行流程历史数据归档迁移(通过 MyBatis Mapper 实现)
+     * 将3个月前已结束的流程数据从热表迁移到冷表(归档表)
+     * SQL 写在 XML 文件中,不使用 @Transactional 让每条 SQL 自动提交
+     *
+     * @return 迁移统计信息
+     */
+    public Map<String, Object> executeArchive() {
+        log.info("===== 开始执行流程数据归档迁移 =====");
+        Map<String, Object> result = new LinkedHashMap<>();
+
+        // 设置截止日期为3个月前(增量迁移)
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.MONTH, -3);
+        Date cutoffDate = cal.getTime();
+        String cutoffStr = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(cutoffDate);
+        log.info("迁移截止日期: {}", cutoffStr);
+
+        // 1. 检查是否有需要归档的数据
+        Integer pendingCount = flowArchiveMapper.countPendingArchive(cutoffDate);
+        if (pendingCount == null || pendingCount == 0) {
+            log.info("没有需要归档的数据");
+            result.put("status", "SUCCESS");
+            result.put("msg", "没有需要归档的数据");
+            return result;
+        }
+        log.info("待归档流程实例数: {}", pendingCount);
+
+        int totalProcinst = 0;
+        int batchSize = 500;
+
+        // 2. 分批迁移流程实例(每次500条)
+        log.info("开始迁移 act_hi_procinst...");
+        while (true) {
+            int inserted = flowArchiveMapper.insertProcinstArchive(cutoffDate, batchSize);
+            totalProcinst += inserted;
+            log.info("act_hi_procinst 本批迁移 {} 条, 累计 {} 条", inserted, totalProcinst);
+            if (inserted < batchSize) break;
+        }
+        log.info("act_hi_procinst 迁移完成, 共 {} 条", totalProcinst);
+
+        // 3. 迁移关联数据
+        log.info("开始迁移 act_hi_actinst...");
+        int actinst = flowArchiveMapper.insertActinstArchive();
+        log.info("act_hi_actinst 迁移完成, 共 {} 条", actinst);
+
+        log.info("开始迁移 act_hi_varinst...");
+        int varinst = flowArchiveMapper.insertVarinstArchive();
+        log.info("act_hi_varinst 迁移完成, 共 {} 条", varinst);
+
+        log.info("开始迁移 act_hi_taskinst...");
+        int taskinst = flowArchiveMapper.insertTaskinstArchive();
+        log.info("act_hi_taskinst 迁移完成, 共 {} 条", taskinst);
+
+        log.info("开始迁移 act_hi_identitylink...");
+        int identitylink = flowArchiveMapper.insertIdentitylinkArchive();
+        log.info("act_hi_identitylink 迁移完成, 共 {} 条", identitylink);
+
+        log.info("开始迁移 act_hi_comment...");
+        int comment = flowArchiveMapper.insertCommentArchive();
+        log.info("act_hi_comment 迁移完成, 共 {} 条", comment);
+
+        // 4. 从热表删除已归档的数据(先删子表,再删主表)
+        log.info("开始清理热表数据...");
+        flowArchiveMapper.deleteActinst();
+        flowArchiveMapper.deleteVarinst();
+        flowArchiveMapper.deleteTaskinst();
+        flowArchiveMapper.deleteIdentitylink();
+        flowArchiveMapper.deleteComment();
+        flowArchiveMapper.deleteProcinst();
+        log.info("热表数据清理完成");
+
+        // 5. 记录归档日志
+        try {
+            flowArchiveMapper.insertArchiveLog(cutoffDate, totalProcinst, actinst, varinst, taskinst, identitylink, comment);
+        } catch (Exception e) {
+            log.warn("记录归档日志失败(flow_archive_log表可能不存在): {}", e.getMessage());
+        }
+
+        // 6. 组装返回结果
+        result.put("status", "SUCCESS");
+        result.put("cutoffDate", cutoffStr);
+        result.put("procinst", totalProcinst);
+        result.put("actinst", actinst);
+        result.put("varinst", varinst);
+        result.put("taskinst", taskinst);
+        result.put("identitylink", identitylink);
+        result.put("comment", comment);
+        log.info("===== 流程数据归档迁移完成, 共迁移 {} 条流程实例 =====", totalProcinst);
+        return result;
+    }
+
+    /**
      * HistoricActivityInstance 行映射器
      * 将归档表数据映射为 Flowable 的 HistoricActivityInstance 对象
      */

+ 24 - 0
jeeplus-modules/jeeplus-xxl-job-executor-sample/src/main/java/com/xxl/job/executor/service/jobhandler/SampleXxlJob.java

@@ -74,6 +74,30 @@ public class SampleXxlJob {
 
     }
 
+    /**
+     * Flowable历史数据归档定时任务(Bean模式)
+     * 每月执行一次,调用存储过程 sp_flow_archive,将3个月前已结束的流程数据从热表迁移到冷表(归档表)
+     * 建议在每月1号凌晨低峰期执行,避免影响业务
+     * XXL-Job配置: cron表达式如 "0 0 2 1 * ?"(每月1号凌晨2点)
+     */
+    @XxlJob("flowArchiveJob")
+    public void flowArchiveJob() throws Exception {
+        XxlJobHelper.log("开始执行Flowable历史数据归档...");
+        try {
+            Map result = SpringUtil.getBean(FlowableFeignApi.class).triggerArchive();
+            XxlJobHelper.log("归档结果: " + JSON.toJSONString(result));
+            if (result != null && Integer.valueOf(200).equals(result.get("code"))) {
+                XxlJobHelper.log("Flowable历史数据归档执行成功");
+            } else {
+                XxlJobHelper.log("Flowable历史数据归档执行失败: " + result);
+                XxlJobHelper.handleFail("归档执行失败: " + result);
+            }
+        } catch (Exception e) {
+            XxlJobHelper.log("Flowable历史数据归档执行异常: " + e.getMessage());
+            XxlJobHelper.handleFail("归档执行异常: " + e.getMessage());
+        }
+    }
+
     //根据实习结束时间修改实习状态并禁止登录
     @XxlJob("updateStatus")
     public void updateStatus(){