fuliqi
2025-01-03 a0e8d1bbff7a02f538be76e852aa233324bd8810
赋码
4个文件已修改
10个文件已添加
807 ■■■■■ 已修改文件
business/src/main/java/com/ycl/listener/flowable/FlowableOverTimeListener.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/task/FlowableTask.java 94 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/java/com/ycl/common/constant/ProcessOverTimeConstants.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/java/com/ycl/domain/dto/FlowTaskDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/java/com/ycl/domain/dto/FlowViewerDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/java/com/ycl/domain/entity/ProcessCoding.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/java/com/ycl/domain/form/ProcessCodingForm.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/java/com/ycl/domain/query/ProcessCodingQuery.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/java/com/ycl/domain/vo/ProcessCodingVO.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/java/com/ycl/mapper/ProcessCodingMapper.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/java/com/ycl/service/ProcessCodingService.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/java/com/ycl/service/impl/FlowTaskServiceImpl.java 104 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/java/com/ycl/service/impl/ProcessCodingServiceImpl.java 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/resources/mapper/ProcessCodingMapper.xml 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/listener/flowable/FlowableOverTimeListener.java
New file
@@ -0,0 +1,96 @@
package com.ycl.listener.flowable;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ycl.common.utils.spring.SpringUtils;
import com.ycl.domain.entity.ProcessCoding;
import com.ycl.factory.FlowServiceFactory;
import com.ycl.mapper.ProcessCodingMapper;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.el.FixedValue;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.service.delegate.DelegateTask;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import static com.ycl.common.constant.ProcessOverTimeConstants.GREEN;
/**
 * 任务监听器
 *
 * create(创建):在任务被创建且所有的任务属性设置完成后才触发
 * assignment(指派):在任务被分配给某个办理人之后触发
 * complete(完成):在配置了监听器的上一个任务完成时触发
 * delete(删除):在任务即将被删除前触发。请注意任务由completeTask正常完成时也会触发
 *
 * @author Tony
 * @date 2021/4/20
 */
@Slf4j
@Component
public class FlowableOverTimeListener extends FlowServiceFactory implements TaskListener {
    /**
     * 黄码时间
     */
    private FixedValue yellowTime;
    /**
     * 红码时间
     */
    private FixedValue redTime;
    /**
     * 计时起始节点定义Id
     */
    private FixedValue startTaskId;
    @Override
    public void notify(DelegateTask delegateTask) {
        log.info("触发超时监听器:{}", delegateTask);
        //Flowable的bean自己管理的需要手动获取
        ProcessCodingMapper processCodingMapper = SpringUtils.getBean(ProcessCodingMapper.class);
        //任务id
        String taskId = delegateTask.getId();
        //流程实例id
        String processInstanceId = delegateTask.getProcessInstanceId();
        ProcessCoding processCoding = new ProcessCoding();
        processCoding.setTaskId(taskId);
        processCoding.setProcessInsId(processInstanceId);
        processCoding.setTaskDefKey(delegateTask.getTaskDefinitionKey());
        //涉及到驳回,需要查一下这个节点是否已经添加过了,如果添加过就不进行后续操作
        List<ProcessCoding> processCodings = processCodingMapper.selectList(new QueryWrapper<ProcessCoding>()
                .eq("process_ins_id", processInstanceId)
                .eq("task_def_key", delegateTask.getTaskDefinitionKey()));
        if (CollectionUtils.isEmpty(processCodings)) {
            processCoding.setStatus(GREEN);
            if (yellowTime != null && yellowTime.getValue(delegateTask) != null) {
                processCoding.setYellowTime(Integer.parseInt(yellowTime.getValue(delegateTask).toString()));
            }
            if (redTime != null && redTime.getValue(delegateTask) != null) {
                processCoding.setRedTime(Integer.parseInt(redTime.getValue(delegateTask).toString()));
            }
            if (startTaskId != null && startTaskId.getValue(delegateTask) != null) {
                String taskDefKey = startTaskId.getValue(delegateTask).toString();
                //通过流程实例id和节点定义id找到对应taskId
                if (delegateTask.getTaskDefinitionKey().equals(taskDefKey)) {
                    //当前节点为开始计时节点
                    processCoding.setStartTaskId(delegateTask.getId());
                } else {
                    //开始节点从历史节点查询 取多条中最早的一条
                    List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
                            .processInstanceId(processInstanceId)
                            .taskDefinitionKey(delegateTask.getTaskDefinitionKey())
                            .orderByHistoricTaskInstanceStartTime()
                            .asc()
                            .list();
                    if (!CollectionUtils.isEmpty(list)) {
                        HistoricTaskInstance hisTask = list.get(0);
                        processCoding.setStartTaskId(hisTask.getId());
                    }
                }
            }
            processCodingMapper.insert(processCoding);
            log.info("添加节点到定时器");
        }
    }
}
business/src/main/java/com/ycl/task/FlowableTask.java
@@ -2,23 +2,27 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.ycl.domain.entity.ProcessCoding;
import com.ycl.domain.entity.ProjectInfo;
import com.ycl.domain.entity.ProjectProcess;
import com.ycl.factory.FlowServiceFactory;
import com.ycl.mapper.ProcessCodingMapper;
import com.ycl.mapper.ProjectInfoMapper;
import com.ycl.mapper.ProjectProcessMapper;
import com.ycl.service.ProjectProcessService;
import com.ycl.service.ProcessCodingService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.ycl.common.constant.ProcessOverTimeConstants.*;
@Slf4j
@Component("flowableTask")
@@ -27,28 +31,88 @@
    private ProjectProcessMapper projectProcessMapper;
    @Autowired
    private ProjectInfoMapper projectInfoMapper;
    @Autowired
    private ProcessCodingMapper processCodingMapper;
    /**
     * 赋码任务
     * 两个逻辑 改项目码、改节点颜色
     */
    public void expireTask() {
        log.info("开始赋码");
        //当前正在运行的所有任务节点
        List<Task> taskList = taskService.createTaskQuery().list();
        if (!CollectionUtils.isEmpty(taskList)) {
        if(CollectionUtils.isEmpty(taskList)) return;
        List<String> taskIds = taskList.stream().map(TaskInfo::getId).collect(Collectors.toList());
        //需要监控的赋码任务
        List<ProcessCoding> processCodingList = processCodingMapper.selectList(new QueryWrapper<ProcessCoding>().in("task_id",taskIds));
        if (!CollectionUtils.isEmpty(processCodingList)) {
            //key为taskId value为本体对象
            Map<String, ProcessCoding> taskMap = processCodingList.stream().collect(Collectors.toMap(ProcessCoding::getTaskId, Function.identity()));
            //拿到开始计时的节点集合 key:taskId value:开始时间
            Map<String, Date> startTaskMap = getStartTaskList(processCodingList);
            //提前准备接收数据的map key:流程实例id value:需要改变的颜色
            Map<String, List<String>> map = new HashMap<>();
            List<ProcessCoding> list = new ArrayList<>();
            map.put(GREEN, new ArrayList<>());
            map.put(RED, new ArrayList<>());
            map.put(YELLOW, new ArrayList<>());
            Date now = new Date();
            // 按超时状态分组任务
            Map<Boolean, List<String>> processInstanceMap = taskList.stream()
                    .collect(Collectors.groupingBy(
                            task -> task.getDueDate() != null && now.after(task.getDueDate()),
                            Collectors.mapping(TaskInfo::getProcessInstanceId, Collectors.toList())
                    ));
            // 处理超时和未超时的项目
            updateProjectCoding(processInstanceMap.get(true), "red");    // 超时项目
            updateProjectCoding(processInstanceMap.get(false), "green"); // 未超时项目
            //遍历所有代办的节点
            for (Task task : taskList) {
                String taskId = task.getId();
                ProcessCoding processCoding = taskMap.get(taskId);
                if (processCoding == null) {
                    //不需要监控的任务节点直接改为绿色
                    List<String> processInsIds = map.get(GREEN);
                    processInsIds.add(task.getProcessInstanceId());
                    continue;
                }
                //判断是否超时
                Date startTime = startTaskMap.get(processCoding.getStartTaskId());
                Integer yellowTime = processCoding.getYellowTime();
                Integer redTime = processCoding.getRedTime();
                if (startTime == null) continue;
//                long durationDay = (now.getTime() - startTime.getTime()) / (1000 * 60 * 60 * 24);
                long durationDay = (now.getTime() - startTime.getTime()) / (1000 * 60);
                String status = GREEN; // 默认状态为绿色
                if (redTime != null && durationDay >= redTime) {
                    status = RED; // 如果超过红色时间阈值,则状态为红色
                } else if (yellowTime != null && durationDay >= yellowTime) {
                    status = YELLOW; // 否则,如果超过黄色时间阈值,则状态为黄色
                }
                List<String> processInsIds = map.get(status);
                processInsIds.add(task.getProcessInstanceId());
                processCoding.setStatus(status);
                list.add(processCoding);
            }
            //更新项目码
            map.forEach((key,value)-> updateProjectCoding(value, key));
            //更新节点状态 自定义的mybatis方法
            if(!CollectionUtils.isEmpty(list)) processCodingMapper.updateBatch(list);
        }
        log.info("结束赋码");
    }
    private Map<String, Date> getStartTaskList(List<ProcessCoding> processCodingList) {
        //查出任务计时起始节点集合
        List<String> startTaskIds = processCodingList.stream().map(ProcessCoding::getStartTaskId).collect(Collectors.toList());
        //查出起始计时节点数据 注意正在进行的任务不会进入his表 结束了才会进入 所以需要查两张表
        Map<String, Date> startDateMap = new HashMap<>();
        List<Task> startTasks = taskService.createTaskQuery().taskIds(startTaskIds).list();
        List<HistoricTaskInstance> hisStartTasks = historyService.createHistoricTaskInstanceQuery().taskIds(startTaskIds).list();
        if (!CollectionUtils.isEmpty(startTasks)) {
            startTasks.forEach(task -> {
                startDateMap.put(task.getId(), task.getCreateTime());
            });
        }
        if (!CollectionUtils.isEmpty(hisStartTasks)) {
            hisStartTasks.forEach(hisTask -> {
                startDateMap.put(hisTask.getId(), hisTask.getStartTime());
            });
        }
        return startDateMap;
    }
    /**
     * 赋码
     *
flowable/src/main/java/com/ycl/common/constant/ProcessOverTimeConstants.java
New file
@@ -0,0 +1,18 @@
package com.ycl.common.constant;
public class ProcessOverTimeConstants {
    /**
     * 绿码
     */
    public static final String GREEN = "green";
    /**
     * 红码
     */
    public static final String RED = "red";
    /**
     * 黄码
     */
    public static final String YELLOW = "yellow";
}
flowable/src/main/java/com/ycl/domain/dto/FlowTaskDto.java
@@ -99,6 +99,6 @@
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date finishTime;
    private Boolean overtime;
    private String overtime;
}
flowable/src/main/java/com/ycl/domain/dto/FlowViewerDto.java
@@ -22,7 +22,7 @@
    private boolean completed;
    /**
     * 是否已经超时
     * 项目超时颜色码 green/yellow/red
     */
    private boolean overtime;
    private String overtime;
}
flowable/src/main/java/com/ycl/domain/entity/ProcessCoding.java
New file
@@ -0,0 +1,49 @@
package com.ycl.domain.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ycl.system.domain.base.AbsEntity;
import lombok.Data;
/**
 *
 *
 * @author flq
 * @since 2025-01-02
 */
@Data
@TableName("t_process_coding")
public class ProcessCoding extends AbsEntity {
    private static final long serialVersionUID = 1L;
    @TableField("task_id")
    /** 节点id */
    private String taskId;
    @TableField("task_def_key")
    /** 节点定义key */
    private String taskDefKey;
    @TableField("start_task_id")
    /** 计时的起始节点id */
    private String startTaskId;
    @TableField("process_ins_id")
    /** 流程实例id */
    private String processInsId;
    @TableField("yellow_time")
    /** 变黄码的天数 */
    private Integer yellowTime;
    @TableField("red_time")
    /** 变红码的天数 */
    private Integer redTime;
    @TableField("status")
    /** 任务状态 */
    private String status;
}
flowable/src/main/java/com/ycl/domain/form/ProcessCodingForm.java
New file
@@ -0,0 +1,62 @@
package com.ycl.domain.form;
import com.ycl.common.group.Update;
import com.ycl.common.group.Add;
import com.ycl.system.domain.base.AbsForm;
import com.ycl.domain.entity.ProcessCoding;
import org.springframework.beans.BeanUtils;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.springframework.lang.NonNull;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
/**
 * 表单
 *
 * @author flq
 * @since 2025-01-02
 */
@Data
@ApiModel(value = "ProcessCoding表单", description = "表单")
public class ProcessCodingForm extends AbsForm {
    @NotBlank(message = "节点id不能为空", groups = {Add.class, Update.class})
    @ApiModelProperty("节点id")
    private String taskId;
    @NotBlank(message = "节点定义key不能为空", groups = {Add.class, Update.class})
    @ApiModelProperty("节点定义key")
    private String taskDefKey;
    @NotBlank(message = "计时的起始节点id不能为空", groups = {Add.class, Update.class})
    @ApiModelProperty("计时的起始节点id")
    private String startTaskId;
    @NotBlank(message = "流程实例id不能为空", groups = {Add.class, Update.class})
    @ApiModelProperty("流程实例id")
    private String processInsId;
    @NotNull(message = "变黄码的天数不能为空", groups = {Add.class, Update.class})
    @ApiModelProperty("变黄码的天数")
    private Integer yellowTime;
    @NotNull(message = "变红码的天数不能为空", groups = {Add.class, Update.class})
    @ApiModelProperty("变红码的天数")
    private Integer redTime;
    @NotNull(message = "任务状态", groups = {Add.class, Update.class})
    @ApiModelProperty("任务状态green/yellow/red")
    private String status;
    public static ProcessCoding getEntityByForm(@NonNull ProcessCodingForm form, ProcessCoding entity) {
        if(entity == null) {
          entity = new ProcessCoding();
        }
        BeanUtils.copyProperties(form, entity);
        return entity;
    }
}
flowable/src/main/java/com/ycl/domain/query/ProcessCodingQuery.java
New file
@@ -0,0 +1,22 @@
package com.ycl.domain.query;
import com.ycl.system.domain.base.AbsQuery;
import java.util.List;
import org.springframework.lang.NonNull;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * 查询
 *
 * @author flq
 * @since 2025-01-02
 */
@Data
@ApiModel(value = "ProcessCoding查询参数", description = "查询参数")
public class ProcessCodingQuery extends AbsQuery {
}
flowable/src/main/java/com/ycl/domain/vo/ProcessCodingVO.java
New file
@@ -0,0 +1,59 @@
package com.ycl.domain.vo;
import com.ycl.system.domain.base.AbsVo;
import com.ycl.domain.entity.ProcessCoding;
import java.util.List;
import org.springframework.lang.NonNull;
import org.springframework.beans.BeanUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
/**
 * 展示
 *
 * @author flq
 * @since 2025-01-02
 */
@Data
@ApiModel(value = "响应数据", description = "响应数据")
public class ProcessCodingVO extends AbsVo {
    /** 节点id */
    @ApiModelProperty("节点id")
    private String taskId;
    /** 节点定义key */
    @ApiModelProperty("节点定义key")
    private String taskDefKey;
    /** 计时的起始节点id */
    @ApiModelProperty("计时的起始节点id")
    private String startTaskId;
    /** 流程实例id */
    @ApiModelProperty("流程实例id")
    private String processInsId;
    /** 变黄码的天数 */
    @ApiModelProperty("变黄码的天数")
    private Integer yellowTime;
    /** 变红码的天数 */
    @ApiModelProperty("变红码的天数")
    private Integer redTime;
    /** 任务状态0进行中1结束 */
    @ApiModelProperty("任务状态green/red/yellow")
    private String status;
    public static ProcessCodingVO getVoByEntity(@NonNull ProcessCoding entity, ProcessCodingVO vo) {
        if(vo == null) {
            vo = new ProcessCodingVO();
        }
        BeanUtils.copyProperties(entity, vo);
        return vo;
    }
}
flowable/src/main/java/com/ycl/mapper/ProcessCodingMapper.java
New file
@@ -0,0 +1,39 @@
package com.ycl.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.ycl.domain.entity.ProcessCoding;
import com.ycl.domain.query.ProcessCodingQuery;
import com.ycl.domain.vo.ProcessCodingVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 *  Mapper 接口
 *
 * @author flq
 * @since 2025-01-02
 */
@Mapper
public interface ProcessCodingMapper extends BaseMapper<ProcessCoding> {
    /**
     * id查找
     * @param id
     * @return
     */
    ProcessCodingVO getById(Integer id);
    /**
    *  分页
    */
    IPage getPage(IPage page, @Param("query") ProcessCodingQuery query);
    /**
     * 自定义批量更新
     * @param list
     */
    void updateBatch(@Param("list") List<ProcessCoding> list);
}
flowable/src/main/java/com/ycl/service/ProcessCodingService.java
New file
@@ -0,0 +1,66 @@
package com.ycl.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ycl.common.base.Result;
import com.ycl.domain.entity.ProcessCoding;
import com.ycl.domain.form.ProcessCodingForm;
import com.ycl.domain.query.ProcessCodingQuery;
import java.util.List;
/**
 *  服务类
 *
 * @author flq
 * @since 2025-01-02
 */
public interface ProcessCodingService extends IService<ProcessCoding> {
    /**
     * 添加
     * @param form
     * @return
     */
    Result add(ProcessCodingForm form);
    /**
     * 修改
     * @param form
     * @return
     */
    Result update(ProcessCodingForm form);
    /**
     * 批量删除
     * @param ids
     * @return
     */
    Result remove(List<String> ids);
    /**
     * id删除
     * @param id
     * @return
     */
    Result removeById(String id);
    /**
     * 分页查询
     * @param query
     * @return
     */
    Result page(ProcessCodingQuery query);
    /**
     * 根据id查找
     * @param id
     * @return
     */
    Result detail(Integer id);
    /**
     * 列表
     * @return
     */
    Result all();
}
flowable/src/main/java/com/ycl/service/impl/FlowTaskServiceImpl.java
@@ -4,6 +4,7 @@
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ycl.common.constant.ProcessConstants;
import com.ycl.common.core.domain.AjaxResult;
@@ -17,6 +18,7 @@
import com.ycl.domain.dto.FlowNextDto;
import com.ycl.domain.dto.FlowTaskDto;
import com.ycl.domain.dto.FlowViewerDto;
import com.ycl.domain.entity.ProcessCoding;
import com.ycl.domain.entity.SysForm;
import com.ycl.domain.vo.FlowQueryVo;
import com.ycl.domain.vo.FlowTaskVo;
@@ -25,6 +27,7 @@
import com.ycl.flow.CustomProcessDiagramGenerator;
import com.ycl.flow.FindNextNodeUtil;
import com.ycl.flow.FlowableUtils;
import com.ycl.mapper.ProcessCodingMapper;
import com.ycl.service.IFlowTaskService;
import com.ycl.service.ISysDeployFormService;
import com.ycl.service.ISysFormService;
@@ -71,6 +74,9 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static com.ycl.common.constant.ProcessOverTimeConstants.RED;
import static com.ycl.common.constant.ProcessOverTimeConstants.YELLOW;
/**
 * @author Tony
 * @date 2021-04-03
@@ -86,6 +92,7 @@
    private final ISysDeployFormService sysInstanceFormService;
    private final ISysFormService sysFormService;
    private final TaskCommonService taskCommonService;
    private final ProcessCodingMapper processCodingMapper;
    /**
     * 完成审核任务
@@ -114,8 +121,8 @@
    /**
     * 完成表单提交任务/普通任务
     *
     * @param taskId  任务id
     * @param variables  表单数据
     * @param taskId    任务id
     * @param variables 表单数据
     * @return
     */
    @Override
@@ -126,7 +133,7 @@
            return AjaxResult.error("任务不存在");
        }
        Map<String, Object> newV = new HashMap<>(2);
        if (! org.springframework.util.CollectionUtils.isEmpty(variables)) {
        if (!org.springframework.util.CollectionUtils.isEmpty(variables)) {
            for (String key : variables.keySet()) {
                newV.put(task.getTaskDefinitionKey() + "&" + key, variables.get(key));
//                if (ProcessConstants.TASK_FORM_KEY.equals(key)) {
@@ -310,7 +317,6 @@
        if (!isSequential) {
            throw new CustomException("当前节点相对于目标节点,不属于串行关系,无法回退");
        }
        // 获取所有正常进行的任务节点 Key,这些任务不能直接使用,需要找出其中需要撤回的任务
        List<Task> runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
@@ -792,11 +798,11 @@
                    .processInstanceId(procInsId)
                    .orderByHistoricActivityInstanceStartTime()
                    .desc().list();
            Date now =new Date();
            //扩展
            List<HistoricTaskInstance> taskList = historyService.createHistoricTaskInstanceQuery()
                    .processInstanceId(procInsId)
                    .list();
            Date now = new Date();
            //扩展 获取这个流程实例的监控信息 key:TaskId value:实体类
            Map<String, ProcessCoding> processCodingMap = processCodingMapper.selectList(new QueryWrapper<ProcessCoding>().eq("process_ins_id", procInsId))
                    .stream()
                    .collect(Collectors.toMap(ProcessCoding::getTaskId, Function.identity()));
            List<FlowTaskDto> hisFlowList = new ArrayList<>();
            for (HistoricActivityInstance histIns : list) {
                // 展示开始节点
@@ -855,21 +861,19 @@
                    }
                    flowTask.setDuration(histIns.getDurationInMillis() == null || histIns.getDurationInMillis() == 0 ? null : getDate(histIns.getDurationInMillis()));
                    String taskId = histIns.getTaskId();
                    //扩展 判断是否超时
                    for (HistoricTaskInstance taskInstance : taskList) {
                        Date dueDate = taskInstance.getDueDate();
                        //找到对应任务节点
                        if(dueDate!=null && taskInstance.getId().equals(taskId) ) {
                            //如果任务还没完成
                            if(flowTask.getDuration()==null) {
                                //判断当前时间是否超过到期时间
                                if (now.after(dueDate)) flowTask.setOvertime(Boolean.TRUE);
                            }else {
                                //如果任务节点已经完成了,用完成时间判断
                                if(histIns.getEndTime().after(dueDate)) flowTask.setOvertime(Boolean.TRUE);
                    ProcessCoding processCoding = processCodingMap.get(histIns.getTaskId());
                    if(processCoding!=null){
                        //如果任务是代办节点
                        if (flowTask.getDuration() == null) {
                            if(RED.equals(processCoding.getStatus()) || YELLOW.equals(processCoding.getStatus())){
                                flowTask.setOvertime(processCoding.getStatus());
                            }
                        }else {
                            //如果任务节点属于历史节点
                            if(RED.equals(processCoding.getStatus())){
                                flowTask.setOvertime(processCoding.getStatus());
                            };
                        }
                    }
                    // 获取意见评论内容
@@ -1117,35 +1121,28 @@
    @Override
    public AjaxResult flowXmlAndNode(String procInsId, String deployId) {
        try {
            Date now = new Date();
            List<FlowViewerDto> flowViewerList = new ArrayList<>();
            // 获取已经完成的节点
            List<HistoricActivityInstance> listFinished = historyService.createHistoricActivityInstanceQuery()
                    .processInstanceId(procInsId)
                    .finished()
                    .list();
            //获取所有历史任务节点信息(扩展)
            List<HistoricTaskInstance> taskHistoryList = historyService.createHistoricTaskInstanceQuery()
                    .processInstanceId(procInsId)
                    .finished()
                    .list();
            //获取所有当前任务节点信息(扩展)
            List<Task> taskList = taskService.createTaskQuery()
                    .processInstanceId(procInsId)
                    .list();
            //获取这个流程实例的监控信息 key:TaskId value:实体类
            Map<String, ProcessCoding> processCodingMap = processCodingMapper.selectList(new QueryWrapper<ProcessCoding>().eq("process_ins_id", procInsId))
                    .stream()
                    .collect(Collectors.toMap(ProcessCoding::getTaskId, Function.identity()));
            // 保存已经完成的流程节点编号
            listFinished.forEach(s -> {
                FlowViewerDto flowViewerDto = new FlowViewerDto();
                flowViewerDto.setKey(s.getActivityId());
                flowViewerDto.setCompleted(true);
                //扩展内容 已完成的用完成时间判断
                Date endTime = s.getEndTime();
                for (HistoricTaskInstance task : taskHistoryList) {
                    if(s.getTaskId()!=null && s.getTaskId().equals(task.getId())){
                        if(task.getDueDate()!=null && endTime.after(task.getDueDate())){
                            flowViewerDto.setOvertime(true);
                        }
                    }
                //扩展内容 不反overtime前端默认是绿色
                ProcessCoding processCoding = processCodingMap.get(s.getTaskId());
                //如果有监控数据, 历史节点只判断红码
                if (processCoding != null && RED.equals(processCoding.getStatus())) {
                    flowViewerDto.setOvertime(processCoding.getStatus());
                }
                // 退回节点不进行展示
                if (StringUtils.isBlank(s.getDeleteReason())) {
@@ -1158,7 +1155,6 @@
                    .processInstanceId(procInsId)
                    .unfinished()
                    .list();
            // 保存需要代办的节点编号
            listUnFinished.forEach(s -> {
                // 删除已退回节点
@@ -1166,13 +1162,11 @@
                FlowViewerDto flowViewerDto = new FlowViewerDto();
                flowViewerDto.setKey(s.getActivityId());
                flowViewerDto.setCompleted(false);
                //扩展内容 代办的通过当前时间去判断
                for (Task task : taskList) {
                    if(s.getTaskId()!=null && s.getTaskId().equals(task.getId())){
                        if(task.getDueDate()!=null && now.after(task.getDueDate())){
                            flowViewerDto.setOvertime(true);
                        }
                    }
                // 扩展内容 代办的通过当前时间作为endTime
                ProcessCoding processCoding = processCodingMap.get(s.getTaskId());
                //如果有监控数据 不反的话前端默认是进行中(蓝色)
                if (processCoding != null && (RED.equals(processCoding.getStatus()) || YELLOW.equals(processCoding.getStatus()))) {
                    flowViewerDto.setOvertime(processCoding.getStatus());
                }
                flowViewerList.add(flowViewerDto);
            });
@@ -1185,6 +1179,7 @@
            result.put("xmlData", xmlData);
            return AjaxResult.success(result);
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("高亮历史任务失败");
        }
    }
@@ -1234,15 +1229,15 @@
    /**
     * 获取当前节点和上一节点的表单内容
     *
     * @param parameters 根据任务查找出来的参数
     * @param formKey task自身关联的表单id
     * @param taskName task自身的任务名
     * @param processDefId 流程定义id
     * @param parameters    根据任务查找出来的参数
     * @param formKey       task自身关联的表单id
     * @param taskName      task自身的任务名
     * @param processDefId  流程定义id
     * @param processDefKey 流程实例id
     * @return
     */
    private List<FormDetailVO> getBeforeNodeForm(Map<String, Object> parameters, String formKey, String taskName, String processDefId, String processDefKey, Boolean currentNeedData) {
        if (! parameters.keySet().stream().anyMatch(key -> key.contains(ProcessConstants.TASK_FORM_KEY))) {
        if (!parameters.keySet().stream().anyMatch(key -> key.contains(ProcessConstants.TASK_FORM_KEY))) {
            // 如果是空的,使用formId去查
            if (StringUtils.isNotBlank(formKey)) {
                SysForm sysForm = sysFormService.selectSysFormById(Long.parseLong(formKey));
@@ -1277,8 +1272,7 @@
                    if (key.startsWith(formDetailVO.getBeforeNodeDefId())) {
                        if (key.contains(ProcessConstants.TASK_FORM_KEY)) {
                            newP.put(key, parameters.get(key));
                        }
                        else {
                        } else {
                            newP.put(key.split("&")[1], parameters.get(key));
                        }
                    }
flowable/src/main/java/com/ycl/service/impl/ProcessCodingServiceImpl.java
New file
@@ -0,0 +1,119 @@
package com.ycl.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ycl.common.base.Result;
import com.ycl.domain.entity.ProcessCoding;
import com.ycl.domain.form.ProcessCodingForm;
import com.ycl.domain.query.ProcessCodingQuery;
import com.ycl.domain.vo.ProcessCodingVO;
import com.ycl.framework.utils.PageUtil;
import com.ycl.mapper.ProcessCodingMapper;
import com.ycl.service.ProcessCodingService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.util.List;
import java.util.stream.Collectors;
/**
 *  服务实现类
 *
 * @author flq
 * @since 2025-01-02
 */
@Service
@RequiredArgsConstructor
public class ProcessCodingServiceImpl extends ServiceImpl<ProcessCodingMapper, ProcessCoding> implements ProcessCodingService {
    private final ProcessCodingMapper processCodingMapper;
    /**
     * 添加
     * @param form
     * @return
     */
    @Override
    public Result add(ProcessCodingForm form) {
        ProcessCoding entity = ProcessCodingForm.getEntityByForm(form, null);
        baseMapper.insert(entity);
        return Result.ok("添加成功");
    }
    /**
     * 修改
     * @param form
     * @return
     */
    @Override
    public Result update(ProcessCodingForm form) {
        ProcessCoding entity = baseMapper.selectById(form.getId());
        // 为空抛IllegalArgumentException,做全局异常处理
        Assert.notNull(entity, "记录不存在");
        BeanUtils.copyProperties(form, entity);
        baseMapper.updateById(entity);
        return Result.ok("修改成功");
    }
    /**
     * 批量删除
     * @param ids
     * @return
     */
    @Override
    public Result remove(List<String> ids) {
        baseMapper.deleteBatchIds(ids);
        return Result.ok("删除成功");
    }
    /**
     * id删除
     * @param id
     * @return
     */
    @Override
    public Result removeById(String id) {
        baseMapper.deleteById(id);
        return Result.ok("删除成功");
    }
    /**
     * 分页查询
     * @param query
     * @return
     */
    @Override
    public Result page(ProcessCodingQuery query) {
        IPage<ProcessCodingVO> page = PageUtil.getPage(query, ProcessCodingVO.class);
        baseMapper.getPage(page, query);
        return Result.ok().data(page.getRecords()).total(page.getTotal());
    }
    /**
     * 根据id查找
     * @param id
     * @return
     */
    @Override
    public Result detail(Integer id) {
        ProcessCodingVO vo = baseMapper.getById(id);
        Assert.notNull(vo, "记录不存在");
        return Result.ok().data(vo);
    }
    /**
     * 列表
     * @return
     */
    @Override
    public Result all() {
        List<ProcessCoding> entities = baseMapper.selectList(null);
        List<ProcessCodingVO> vos = entities.stream()
                .map(entity -> ProcessCodingVO.getVoByEntity(entity, null))
                .collect(Collectors.toList());
        return Result.ok().data(vos);
    }
}
flowable/src/main/resources/mapper/ProcessCodingMapper.xml
New file
@@ -0,0 +1,73 @@
<?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.ycl.mapper.ProcessCodingMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.ycl.domain.vo.ProcessCodingVO">
        <result column="task_id" property="taskId" />
        <result column="task_def_key" property="taskDefKey" />
        <result column="start_task_id" property="startTaskId" />
        <result column="process_ins_id" property="processInsId" />
        <result column="yellow_time" property="yellowTime" />
        <result column="red_time" property="redTime" />
        <result column="status" property="status" />
    </resultMap>
    <select id="getById" resultMap="BaseResultMap">
        SELECT
            TPC.task_id,
            TPC.task_def_key,
            TPC.start_task_id,
            TPC.process_ins_id,
            TPC.yellow_time,
            TPC.red_time,
            TPC.status,
            TPC.id
        FROM
            t_process_coding TPC
        WHERE
            TPC.id = #{id} AND TPC.deleted = 0
    </select>
    <select id="getPage" resultMap="BaseResultMap">
        SELECT
            TPC.task_id,
            TPC.task_def_key,
            TPC.start_task_id,
            TPC.process_ins_id,
            TPC.yellow_time,
            TPC.red_time,
            TPC.status,
            TPC.id
        FROM
            t_process_coding TPC
        WHERE
            TPC.deleted = 0
    </select>
    <!-- 自定义批量更新,使用前判断list是否为空 转换为sql
    update t_process_coding
    set status =
    case
        when id = #{item.id} then #{item.status}
        ...
    end
    where id in (...);-->
    <update id="updateBatch" parameterType="java.util.List">
        update t_process_coding
        <trim prefix="set" suffixOverrides=","><!-- 表示在生成的 SQL 语句前面添加 set 关键字,并移除末尾逗号 -->
            <trim prefix="status =case" suffix="end,"><!-- 构造case语法 末尾加上end,如果需要更新多个字段复制这个trim -->
                <foreach collection="list" item="item">
                    when id=#{item.id} then #{item.status}
                </foreach>
            </trim>
        </trim>
        where id in
        <foreach collection="list" index="index" item="item" separator="," open="(" close=")">
            #{item.id,jdbcType=BIGINT}
        </foreach>
    </update>
</mapper>