已修改41个文件
已重命名1个文件
598 ■■■■ 文件已修改
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/pojo/CommonResult.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/SimpleModelUtils.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java 89 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/api/point/DataPointApiImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-api/src/main/java/com/iailab/module/model/api/mcs/McsApi.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-api/src/main/java/com/iailab/module/model/api/mcs/dto/ChartParamDTO.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-api/src/main/java/com/iailab/module/model/api/mdk/MdkApi.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/api/McsApiImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/api/MdkApiImpl.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/api/controller/admin/MdkApiController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/controller/admin/StScheduleSchemeController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/StScheduleSchemeService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/impl/StScheduleSchemeServiceImpl.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/vo/StScheduleRecordRespVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mdk/sample/PredictSampleDataConstructor.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mdk/sample/PredictSampleInfoConstructor.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mdk/sample/SampleConstructor.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mdk/sample/SampleInfoConstructor.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mdk/sample/ScheduleSampleInfoConstructor.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/controller/admin/ChartController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/controller/admin/ChartParamController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/controller/admin/MdkController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/ChartDTO.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/ChartParamService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/ChartParamServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/resources/template/cpp.vm 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/enums/ErrorCodeConstants.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/tenant/TenantPackageMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantPackageServiceImpl.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/pojo/CommonResult.java
@@ -1,11 +1,12 @@
package com.iailab.framework.common.pojo;
import cn.hutool.core.lang.Assert;
import com.iailab.framework.common.exception.ErrorCode;
import com.iailab.framework.common.exception.ServiceException;
import com.iailab.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iailab.framework.common.exception.util.ServiceExceptionUtil;
import lombok.Data;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.Objects;
@@ -41,7 +42,7 @@
     * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
     *
     * @param result 传入的 result 对象
     * @param <T>    返回的泛型
     * @param <T> 返回的泛型
     * @return 新的 CommonResult 对象
     */
    public static <T> CommonResult<T> error(CommonResult<?> result) {
@@ -49,13 +50,21 @@
    }
    public static <T> CommonResult<T> error(Integer code, String message) {
        Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code), "code 必须是错误的!");
        cn.hutool.core.lang.Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), code, "code 必须是错误的!");
        CommonResult<T> result = new CommonResult<>();
        result.code = code;
        result.msg = message;
        return result;
    }
    public static <T> CommonResult<T> error(ErrorCode errorCode, Object... params) {
        Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), errorCode.getCode(), "code 必须是错误的!");
        CommonResult<T> result = new CommonResult<>();
        result.code = errorCode.getCode();
        result.msg = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMsg(), params);
        return result;
    }
    public static <T> CommonResult<T> error(ErrorCode errorCode) {
        return error(errorCode.getCode(), errorCode.getMsg());
    }
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java
@@ -97,7 +97,8 @@
            return null;
        }
        byte[] bpmnBytes = modelService.getModelBpmnXML(id);
        return success(BpmModelConvert.INSTANCE.buildModel(model, bpmnBytes));
        BpmSimpleModelNodeVO simpleModel = modelService.getSimpleModel(id);
        return success(BpmModelConvert.INSTANCE.buildModel(model, bpmnBytes, simpleModel));
    }
    @PostMapping("/create")
@@ -106,7 +107,6 @@
    public CommonResult<String> createModel(@Valid @RequestBody BpmModelSaveReqVO createRetVO) {
        return success(modelService.createModel(createRetVO));
    }
    @PutMapping("/update")
    @Operation(summary = "修改模型")
@@ -141,6 +141,7 @@
        return success(true);
    }
    @Deprecated
    @PutMapping("/update-bpmn")
    @Operation(summary = "修改模型的 BPMN")
    @PreAuthorize("@ss.hasPermission('bpm:model:update')")
@@ -167,6 +168,7 @@
        return success(modelService.getSimpleModel(modelId));
    }
    @Deprecated
    @PostMapping("/simple/update")
    @Operation(summary = "保存仿钉钉流程设计模型")
    @PreAuthorize("@ss.hasPermission('bpm:model:update')")
@@ -174,6 +176,4 @@
        modelService.updateSimpleModel(getLoginUserId(), reqVO);
        return success(Boolean.TRUE);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
@@ -1,6 +1,7 @@
package com.iailab.module.bpm.controller.admin.definition.vo.model;
import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -35,15 +36,19 @@
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime createTime;
    @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
    private String bpmnXml;
    @Schema(description = "可发起的用户数组")
    private List<UserSimpleBaseVO> startUsers;
    @Schema(description = "BPMN XML")
    private String bpmnXml;
    @Schema(description = "仿钉钉流程设计模型对象")
    private BpmSimpleModelNodeVO simpleModel;
    /**
     * 最新部署的流程定义
     */
    private BpmProcessDefinitionRespVO processDefinition;
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java
@@ -1,8 +1,10 @@
package com.iailab.module.bpm.controller.admin.definition.vo.model;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
@Schema(description = "管理后台 - 流程模型的保存 Request VO")
@@ -23,4 +25,11 @@
    @Schema(description = "流程分类", example = "1")
    private String category;
    @Schema(description = "BPMN XML")
    private String bpmnXml;
    @Schema(description = "仿钉钉流程设计模型对象")
    @Valid
    private BpmSimpleModelNodeVO simpleModel;
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java
@@ -12,9 +12,12 @@
@Data
public class BpmTaskPageReqVO extends PageParam {
    @Schema(description = "流程任务名", example = "平台")
    @Schema(description = "流程任务名", example = "芋道")
    private String name;
    @Schema(description = "流程分类", example = "1")
    private String category;
    @Schema(description = "创建时间")
    @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
    private LocalDateTime[] createTime;
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java
@@ -8,6 +8,7 @@
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelRespVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO;
@@ -58,12 +59,13 @@
        return result;
    }
    default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes) {
    default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes, BpmSimpleModelNodeVO simpleModel) {
        BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
        BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null);
        if (ArrayUtil.isNotEmpty(bpmnBytes)) {
            modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes));
        }
        modelVO.setSimpleModel(simpleModel);
        return modelVO;
    }
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java
@@ -58,7 +58,7 @@
                                                    Map<String, HistoricProcessInstance> processInstanceMap,
                                                    Map<Long, AdminUserRespDTO> userMap,
                                                    Map<Long, DeptRespDTO> deptMap) {
        List<BpmTaskRespVO> taskVOList = convertList(pageResult.getList(), task -> {
        List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(pageResult.getList(), task -> {
            BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
            taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task));
            // 用户信息
@@ -83,7 +83,7 @@
                                                                 Map<Long, BpmFormDO> formMap,
                                                                 Map<Long, AdminUserRespDTO> userMap,
                                                                 Map<Long, DeptRespDTO> deptMap) {
        return convertList(taskList, task -> {
        return CollectionUtils.convertList(taskList, task -> {
            // 特殊:已取消的任务,不返回
            BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
            Integer taskStatus = FlowableUtils.getTaskStatus(task);
@@ -125,12 +125,18 @@
    }
    default BpmTaskRespVO buildTodoTask(Task todoTask, List<Task> childrenTasks,
                                              Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting) {
        return BeanUtils.toBean(todoTask, BpmTaskRespVO.class)
                                        Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting,
                                        BpmFormDO form) {
        BpmTaskRespVO bpmTaskRespVO = BeanUtils.toBean(todoTask, BpmTaskRespVO.class)
                .setStatus(FlowableUtils.getTaskStatus(todoTask)).setReason(FlowableUtils.getTaskReason(todoTask))
                .setButtonsSetting(buttonsSetting)
                .setChildren(convertList(childrenTasks, childTask -> BeanUtils.toBean(childTask, BpmTaskRespVO.class)
                        .setStatus(FlowableUtils.getTaskStatus(childTask))));
        if (form != null) {
            bpmTaskRespVO.setFormId(form.getId()).setFormName(form.getName())
                    .setFormConf(form.getConf()).setFormFields(form.getFields());
        }
        return bpmTaskRespVO;
    }
    default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser,
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
@@ -54,13 +54,13 @@
        Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
        if (assigneeUserIds == null) {
            assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
            execution.setVariable(super.collectionVariable, assigneeUserIds);
            if (CollUtil.isEmpty(assigneeUserIds)) {
                // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
                // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
                // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
                assigneeUserIds = SetUtils.asSet((Long) null);
            }
            execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
        }
        return assigneeUserIds.size();
    }
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
@@ -10,7 +10,6 @@
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import java.util.LinkedHashSet;
import java.util.Set;
/**
@@ -44,17 +43,18 @@
        super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
        // 第二步,获取任务的所有处理人
        // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人
        @SuppressWarnings("unchecked")
        Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
        Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);
        if (assigneeUserIds == null) {
            assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
            execution.setVariable(super.collectionVariable, assigneeUserIds);
            if (CollUtil.isEmpty(assigneeUserIds)) {
                // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
                // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
                // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
                assigneeUserIds = SetUtils.asSet((Long) null);
            }
            execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
        }
        return assigneeUserIds.size();
    }
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
@@ -1,13 +1,17 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.other;
import cn.hutool.core.convert.Convert;
import com.google.common.collect.Sets;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -17,6 +21,7 @@
 * @author iailab
 */
@Component
@Slf4j
public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrategy {
    @Override
@@ -38,8 +43,16 @@
    @Override
    public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
                                              Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
        Object result = FlowableUtils.getExpressionValue(processVariables, param);
        return Convert.toSet(Long.class, result);
        Map<String, Object> variables = processVariables == null ? new HashMap<>() : processVariables;
        try {
            Object result = FlowableUtils.getExpressionValue(variables, param);
            return Convert.toSet(Long.class, result);
        } catch (FlowableException ex) {
            // 预测未运行的节点时候,表达式如果包含 execution 或者不存在的流程变量会抛异常,
            log.warn("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex);
            // 不能预测候选人,返回空列表, 避免流程无法进行
            return Sets.newHashSet();
        }
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -73,7 +73,6 @@
        extensionElement.setName(name);
        attributes.forEach((key, value) -> {
            ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value);
            extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
            extensionElement.addAttribute(extensionAttribute);
        });
        element.addExtensionElement(extensionElement);
@@ -278,8 +277,8 @@
        }
        Map<String, String> fieldsPermission = MapUtil.newHashMap();
        extensionElements.forEach(element -> {
            String field = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE);
            String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE);
            String field = element.getAttributeValue(null, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE);
            String permission = element.getAttributeValue(null, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE);
            if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) {
                fieldsPermission.put(field, permission);
            }
@@ -321,9 +320,9 @@
        }
        Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = Maps.newHashMapWithExpectedSize(extensionElements.size());
        extensionElements.forEach(element -> {
            String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE);
            String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE);
            String enable = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE);
            String id = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE);
            String displayName = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE);
            String enable = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE);
            if (StrUtil.isNotEmpty(id)) {
                BpmTaskRespVO.OperationButtonSetting setting = new BpmTaskRespVO.OperationButtonSetting();
                buttonSettings.put(Integer.valueOf(id), setting.setDisplayName(displayName).setEnable(Boolean.parseBoolean(enable)));
@@ -699,9 +698,9 @@
        // 情况:StartEvent/EndEvent/UserTask/ServiceTask
        if (currentElement instanceof StartEvent
            || currentElement instanceof EndEvent
            || currentElement instanceof UserTask
            || currentElement instanceof ServiceTask) {
                || currentElement instanceof EndEvent
                || currentElement instanceof UserTask
                || currentElement instanceof ServiceTask) {
            // 添加元素
            FlowNode flowNode = (FlowNode) currentElement;
            resultElements.add(flowNode);
@@ -720,7 +719,7 @@
                            && evalConditionExpress(variables, flow.getConditionExpression()));
            if (matchSequenceFlow == null) {
                matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
                        flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
                        flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
                // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
                if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {
                    matchSequenceFlow = gateway.getOutgoingFlows().get(0);
@@ -742,7 +741,7 @@
                            && evalConditionExpress(variables, flow.getConditionExpression()));
            if (CollUtil.isEmpty(matchSequenceFlows)) {
                matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
                        flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
                        flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
                // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
                if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {
                    matchSequenceFlows = gateway.getOutgoingFlows();
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java
@@ -22,6 +22,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
/**
 * Flowable 相关的工具方法
@@ -40,6 +41,17 @@
        Authentication.setAuthenticatedUserId(null);
    }
    public static <V> V executeAuthenticatedUserId(Long userId, Callable<V> callable) {
        setAuthenticatedUserId(userId);
        try {
            return callable.call();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            clearAuthenticatedUserId();
        }
    }
    public static String getTenantId() {
        Long tenantId = TenantContextHolder.getTenantId();
        return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID;
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
@@ -82,7 +82,7 @@
    private static BpmSimpleModelNodeVO buildStartNode() {
        return new BpmSimpleModelNodeVO().setId(START_EVENT_NODE_ID)
                .setName(BpmSimpleModelNodeType.START_USER_NODE.getName())
                .setName(BpmSimpleModelNodeType.START_NODE.getName())
                .setType(BpmSimpleModelNodeType.START_NODE.getType());
    }
@@ -316,7 +316,7 @@
            userTask.setName(node.getName());
            // 人工审批
            addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType());
            addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType());
            // 候选人策略为发起人自己
            addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask);
            // 添加表单字段权限属性元素
@@ -619,7 +619,7 @@
    }
    private static void simulateNextNode(BpmSimpleModelNodeVO currentNode, Map<String, Object> variables,
                                  List<BpmSimpleModelNodeVO> resultNodes) {
                                         List<BpmSimpleModelNodeVO> resultNodes) {
        // 如果不合法(包括为空),则直接结束
        if (!isValidNode(currentNode)) {
            return;
@@ -629,10 +629,10 @@
        // 情况:START_NODE/START_USER_NODE/APPROVE_NODE/COPY_NODE/END_NODE
        if (nodeType == BpmSimpleModelNodeType.START_NODE
            || nodeType == BpmSimpleModelNodeType.START_USER_NODE
            || nodeType == BpmSimpleModelNodeType.APPROVE_NODE
            || nodeType == BpmSimpleModelNodeType.COPY_NODE
            || nodeType == BpmSimpleModelNodeType.END_NODE) {
                || nodeType == BpmSimpleModelNodeType.START_USER_NODE
                || nodeType == BpmSimpleModelNodeType.APPROVE_NODE
                || nodeType == BpmSimpleModelNodeType.COPY_NODE
                || nodeType == BpmSimpleModelNodeType.END_NODE) {
            // 添加元素
            resultNodes.add(currentNode);
        }
@@ -642,7 +642,7 @@
            // 查找满足条件的 BpmSimpleModelNodeVO 节点
            BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
                    conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow())
                        && evalConditionExpress(variables, conditionNode));
                            && evalConditionExpress(variables, conditionNode));
            if (matchConditionNode == null) {
                matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
                        conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow()));
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java
@@ -2,6 +2,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.iailab.framework.common.util.json.JsonUtils;
import com.iailab.framework.common.util.validation.ValidationUtils;
@@ -12,11 +13,11 @@
import com.iailab.module.bpm.convert.definition.BpmModelConvert;
import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO;
import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum;
import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.iailab.module.bpm.framework.flowable.core.util.SimpleModelUtils;
import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.StartEvent;
@@ -28,7 +29,6 @@
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
@@ -66,7 +66,7 @@
    public List<Model> getModelList(String name) {
        ModelQuery modelQuery = repositoryService.createModelQuery();
        if (StrUtil.isNotEmpty(name)) {
            modelQuery.modelNameLike(name);
            modelQuery.modelNameLike("%" + name + "%");
        }
        return modelQuery.list();
    }
@@ -83,26 +83,54 @@
            throw exception(MODEL_KEY_EXISTS, createReqVO.getKey());
        }
        // 2.1 创建流程定义
        // 2. 创建 Model 对象
        createReqVO.setSort(System.currentTimeMillis()); // 使用当前时间,作为排序
        Model model = repositoryService.newModel();
        BpmModelConvert.INSTANCE.copyToModel(model, createReqVO);
        model.setTenantId(FlowableUtils.getTenantId());
        // 2.2 保存流程定义
        repositoryService.saveModel(model);
        // 3. 保存模型
        saveModel(model, createReqVO);
        return model.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务
    public void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO) {
    public void updateModel(Long userId, BpmModelSaveReqVO updateReqVO) {
        // 1. 校验流程模型存在
        Model model = validateModelManager(updateReqVO.getId(), userId);
        // 修改流程定义
        // 2. 填充 Model 信息
        BpmModelConvert.INSTANCE.copyToModel(model, updateReqVO);
        // 更新模型
        // 3. 保存模型
        saveModel(model, updateReqVO);
    }
    /**
     * 保存模型的基本信息、流程图
     *
     * @param model 模型
     * @param saveReqVO 保存信息
     */
    private void saveModel(Model model, BpmModelSaveReqVO saveReqVO) {
        // 1. 保存模型的基础信息
        repositoryService.saveModel(model);
        // 2. 保存流程图
        if (ObjUtil.equals(BpmModelTypeEnum.BPMN.getType(), saveReqVO.getType())
                && StrUtil.isNotEmpty(saveReqVO.getBpmnXml())) {
            updateModelBpmnXml(model.getId(), saveReqVO.getBpmnXml());
        } else if (ObjUtil.equals(BpmModelTypeEnum.SIMPLE.getType(), saveReqVO.getType())
                && saveReqVO.getSimpleModel() != null) {
            // JSON 转换成 bpmnModel
            BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(),
                    saveReqVO.getSimpleModel());
            // 保存 Bpmn XML
            updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel));
            // 保存 JSON 数据
            updateModelSimpleJson(model.getId(), saveReqVO.getSimpleModel());
        }
    }
    @Override
@@ -111,7 +139,7 @@
        // 1.1 校验流程模型存在
        List<Model> models = repositoryService.createModelQuery()
                .modelTenantId(FlowableUtils.getTenantId()).list();
        models.removeIf(model ->!ids.contains(model.getId()));
        models.removeIf(model -> !ids.contains(model.getId()));
        if (ids.size() != models.size()) {
            throw exception(MODEL_NOT_EXISTS);
        }
@@ -174,7 +202,8 @@
        String simpleJson = getModelSimpleJson(model.getId());
        // 2.1 创建流程定义
        String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleJson, form);
        String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleJson,
                form);
        // 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。
        updateProcessDefinitionSuspended(model.getDeploymentId());
@@ -221,7 +250,8 @@
        // 1.1 校验流程模型存在
        Model model = validateModelManager(id, userId);
        // 1.2 校验流程定义存在
        ProcessDefinition definition = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId());
        ProcessDefinition definition = processDefinitionService
                .getProcessDefinitionByDeploymentId(model.getDeploymentId());
        if (definition == null) {
            throw exception(PROCESS_DEFINITION_NOT_EXISTS);
        }
@@ -277,7 +307,8 @@
            }
            return form;
        } else {
            if (StrUtil.isEmpty(metaInfo.getFormCustomCreatePath()) || StrUtil.isEmpty(metaInfo.getFormCustomViewPath())) {
            if (StrUtil.isEmpty(metaInfo.getFormCustomCreatePath())
                    || StrUtil.isEmpty(metaInfo.getFormCustomViewPath())) {
                throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG);
            }
            return null;
@@ -324,7 +355,8 @@
        if (oldDefinition == null) {
            return;
        }
        processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode());
        processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(),
                SuspensionState.SUSPENDED.getStateCode());
    }
    private Model getModelByKey(String key) {
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -1 +1 @@
package com.iailab.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.date.DateUtils; import com.iailab.framework.common.util.json.JsonUtils; import com.iailab.framework.common.util.object.ObjectUtils; import com.iailab.framework.common.util.object.PageUtils; import com.iailab.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import com.iailab.module.bpm.controller.admin.task.vo.instance.*; import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import com.iailab.module.bpm.convert.task.BpmProcessInstanceConvert; import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import com.iailab.module.bpm.enums.ErrorCodeConstants; import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum; import com.iailab.module.bpm.enums.definition.BpmSimpleModelNodeType; import com.iailab.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import com.iailab.module.bpm.enums.task.BpmReasonEnum; import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept.BpmTaskCandidateStartUserSelectStrategy; import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import com.iailab.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import com.iailab.module.bpm.framework.flowable.core.util.SimpleModelUtils; import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; import com.iailab.module.bpm.service.message.BpmMessageService; import com.iailab.module.system.api.dept.DeptApi; import com.iailab.module.system.api.dept.dto.DeptRespDTO; import com.iailab.module.system.api.user.AdminUserApi; import com.iailab.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.model.*; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.util.*; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.framework.common.util.collection.CollectionUtils.*; import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNodeTask; import static com.iailab.module.bpm.enums.ErrorCodeConstants.*; import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.flowable.bpmn.constants.BpmnXMLConstants.*; /**  * 流程实例 Service 实现类  * <p>  * ProcessDefinition & ProcessInstance & Execution & Task 的关系:  * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" />  * <p>  * HistoricProcessInstance & ProcessInstance 的关系:  * 1. <a href=" https://my.oschina.net/843294669/blog/71902" />  * <p>  * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例  *  * @author 芋道源码  */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService {     @Resource     private RuntimeService runtimeService;     @Resource     private HistoryService historyService;     @Resource     private BpmProcessDefinitionService processDefinitionService;     @Resource     @Lazy // 避免循环依赖     private BpmTaskService taskService;     @Resource     private BpmMessageService messageService;     @Resource     private AdminUserApi adminUserApi;     @Resource     private DeptApi deptApi;     @Resource     private BpmProcessInstanceEventPublisher processInstanceEventPublisher;     @Resource     private BpmTaskCandidateInvoker taskCandidateInvoker;     // ========== Query 查询相关方法 ==========     @Override     public ProcessInstance getProcessInstance(String id) {         return runtimeService.createProcessInstanceQuery()                 .includeProcessVariables()                 .processInstanceId(id)                 .singleResult();     }     @Override     public List<ProcessInstance> getProcessInstances(Set<String> ids) {         return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();     }     @Override     public HistoricProcessInstance getHistoricProcessInstance(String id) {         return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult();     }     @Override     public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {         return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();     }     @Override     public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,                                                                       BpmProcessInstancePageReqVO pageReqVO) {         // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页         HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery()                 .includeProcessVariables()                 .processInstanceTenantId(FlowableUtils.getTenantId())                 .orderByProcessInstanceStartTime().desc();         if (userId != null) { // 【我的流程】菜单时,需要传递该字段             processInstanceQuery.startedBy(String.valueOf(userId));         } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段             processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId()));         }         if (StrUtil.isNotEmpty(pageReqVO.getName())) {             processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%");         }         if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) {             processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey());         }         if (StrUtil.isNotEmpty(pageReqVO.getCategory())) {             processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory());         }         if (pageReqVO.getStatus() != null) {             processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus());         }         if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) {             processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0]));             processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1]));         }         // 查询数量         long processInstanceCount = processInstanceQuery.count();         if (processInstanceCount == 0) {             return PageResult.empty(processInstanceCount);         }         // 查询列表         List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize());         return new PageResult<>(processInstanceList, processInstanceCount);     }     private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel,                                                         String activityId, String taskId) {         // 1. 获取流程活动编号。流程活动 Id 为空事,从流程任务中获取流程活动 Id         if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) {             activityId = Optional.ofNullable(taskService.getHistoricTask(taskId))                     .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null);         }         if (StrUtil.isEmpty(activityId)) {             return null;         }         // 2. 从 BpmnModel 中解析表单字段权限         return BpmnModelUtils.parseFormFieldsPermission(bpmnModel, activityId);     }     @Override     public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) {         // 1.1 从 reqVO 中,读取公共变量         Long startUserId = loginUserId; // 流程发起人         HistoricProcessInstance historicProcessInstance = null; // 流程实例         Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态         Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量         // 1.2 如果是流程已发起的场景,则使用流程实例的数据         if (reqVO.getProcessInstanceId() != null) {             historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId());             if (historicProcessInstance == null) {                 throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);             }             startUserId = Long.valueOf(historicProcessInstance.getStartUserId());             processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance);             processVariables = historicProcessInstance.getProcessVariables();         }         // 1.3 读取其它相关数据         ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(                 historicProcessInstance != null ? historicProcessInstance.getProcessDefinitionId() : reqVO.getProcessDefinitionId());         BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId());         BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());         // 2.1 已结束 + 进行中的活动节点         List<ActivityNode> endActivityNodes = null; // 已结束的审批信息         List<ActivityNode> runActivityNodes = null; // 进行中的审批信息         List<HistoricActivityInstance> activities = null; // 流程实例列表         if (reqVO.getProcessInstanceId() != null) {             activities = taskService.getActivityListByProcessInstanceId(reqVO.getProcessInstanceId());             List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(reqVO.getProcessInstanceId(), true);             endActivityNodes = getEndActivityNodeList(startUserId, bpmnModel, processDefinitionInfo,                     historicProcessInstance, processInstanceStatus, activities, tasks);             runActivityNodes = getRunApproveNodeList(startUserId, bpmnModel, processDefinition, processVariables, activities, tasks);         }         // 2.2 流程已经结束,直接 return,无需预测         if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) {             return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,                     processInstanceStatus, endActivityNodes, runActivityNodes, null, null);         }         // 3.1 计算当前登录用户的待办任务         // TODO @jason:有一个极端情况,如果一个用户有 2 个 task A 和 B,A 已经通过,B 需要审核。这个时,通过 A 进来,todo 拿到 B,会不会表单权限不一致哈。         BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId());         // 3.2 预测未运行节点的审批信息         List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, processDefinitionInfo,                 processVariables, activities);         // 4. 拼接最终数据         return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,                 processInstanceStatus, endActivityNodes, runActivityNodes, simulateActivityNodes, todoTask);     }     /**      * 拼接审批详情的最终数据      * <p>      * 主要是,拼接审批人的用户信息、部门信息      */     private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO,                                                         BpmnModel bpmnModel,                                                         ProcessDefinition processDefinition,                                                         BpmProcessDefinitionInfoDO processDefinitionInfo,                                                         HistoricProcessInstance processInstance,                                                         Integer processInstanceStatus,                                                         List<ActivityNode> endApprovalNodeInfos,                                                         List<ActivityNode> runningApprovalNodeInfos,                                                         List<ActivityNode> simulateApprovalNodeInfos,                                                         BpmTaskRespVO todoTask) {         // 1. 获取所有需要读取用户信息的 userIds         List<ActivityNode> approveNodes = newArrayList(asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos));         Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds(processInstance, approveNodes, todoTask);         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));         // 2. 表单权限         Map<String, String> formFieldsPermission = getFormFieldsPermission(bpmnModel, reqVO.getActivityId(), reqVO.getTaskId());         // 3. 拼接数据         return BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel, processDefinition, processDefinitionInfo, processInstance,                 processInstanceStatus, approveNodes, todoTask, formFieldsPermission, userMap, deptMap);     }     /**      * 获得【已结束】的活动节点们      */     private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel,                                                       BpmProcessDefinitionInfoDO processDefinitionInfo,                                                       HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus,                                                       List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) {         // 遍历 tasks 列表,只处理已结束的 UserTask         // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点         List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null);         List<ActivityNode> approvalNodes = convertList(endTasks, task -> {             FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());             ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName())                     .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) ?                             BpmSimpleModelNodeType.START_USER_NODE.getType() : BpmSimpleModelNodeType.APPROVE_NODE.getType())                     .setStatus(FlowableUtils.getTaskStatus(task))                     .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))                     .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime()))                     .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)));             // 如果是取消状态,则跳过             if (BpmTaskStatusEnum.isCancelStatus(activityNode.getStatus())) {                 return null;             }             return activityNode;         });         // 遍历 activities,只处理已结束的 StartEvent、EndEvent         List<HistoricActivityInstance> endActivities = filterList(activities, activity -> activity.getEndTime() != null                 && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_EVENT_END)));         endActivities.forEach(activity -> {             // StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点             if (ELEMENT_EVENT_START.equals(activity.getActivityType())                     && BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) {                 ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID)                         .setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus());                 ActivityNode startNode = new ActivityNode().setId(startTask.getId())                         .setName(BpmSimpleModelNodeType.START_USER_NODE.getName())                         .setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType())                         .setStatus(startTask.getStatus()).setTasks(ListUtil.of(startTask))                         .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime()));                 approvalNodes.add(0, startNode);                 return;             }             // EndEvent             if (ELEMENT_EVENT_END.equals(activity.getActivityType())) {                 if (BpmProcessInstanceStatusEnum.isRejectStatus(processInstanceStatus)) {                     // 拒绝情况下,不需要展示 EndEvent 结束节点。原因是:前端已经展示 x 效果,无需重复展示                     return;                 }                 ActivityNode endNode = new ActivityNode().setId(activity.getId())                         .setName(BpmSimpleModelNodeType.END_NODE.getName())                         .setNodeType(BpmSimpleModelNodeType.END_NODE.getType()).setStatus(processInstanceStatus)                         .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime()));                 String reason = FlowableUtils.getProcessInstanceReason(historicProcessInstance);                 if (StrUtil.isNotEmpty(reason)) {                     endNode.setTasks(singletonList(new ActivityNodeTask().setId(endNode.getId())                             .setStatus(endNode.getStatus()).setReason(reason)));                 }                 approvalNodes.add(endNode);             }         });         return approvalNodes;     }     /**      * 获得【进行中】的活动节点们      */     private List<ActivityNode> getRunApproveNodeList(Long startUserId,                                                      BpmnModel bpmnModel,                                                      ProcessDefinition processDefinition,                                                      Map<String, Object> processVariables,                                                      List<HistoricActivityInstance> activities,                                                      List<HistoricTaskInstance> tasks) {         // 构建运行中的任务,基于 activityId 分组         List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null                 && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER)));         Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities, HistoricActivityInstance::getActivityId);         // 按照 activityId 分组,构建 ApprovalNodeInfo 节点         Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId);         return convertList(runningTaskMap.entrySet(), entry -> {             String activityId = entry.getKey();             List<HistoricActivityInstance> taskActivities = entry.getValue();             // 构建活动节点             FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);             HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同             ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId()).setName(firstActivity.getActivityName())                     .setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()).setStatus(BpmTaskStatusEnum.RUNNING.getStatus())                     .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))                     .setStartTime(DateUtils.of(CollUtil.getFirst(taskActivities).getStartTime()))                     .setTasks(new ArrayList<>());             // 处理每个任务的 tasks 属性             for (HistoricActivityInstance activity : taskActivities) {                 HistoricTaskInstance task = taskMap.get(activity.getTaskId());                 activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task));                 // 加签子任务,需要过滤掉已经完成的加签子任务                 List<HistoricTaskInstance> childrenTasks = filterList(                         taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks),                         childTask -> childTask.getEndTime() == null);                 if (CollUtil.isNotEmpty(childrenTasks)) {                     activityNode.getTasks().addAll(convertList(childrenTasks, BpmProcessInstanceConvert.INSTANCE::buildApprovalTaskInfo));                 }             }             // 处理每个任务的 candidateUsers 属性:如果是依次审批,需要预测它的后续审批人。因为 Task 是审批完一个,创建一个新的 Task             if (BpmnModelUtils.isSequentialUserTask(flowNode)) {                 List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, flowNode.getId(),                         startUserId, processDefinition.getId(), processVariables);                 // 截取当前审批人位置后面的候选人,不包含当前审批人                 ActivityNodeTask approvalTaskInfo = CollUtil.getFirst(activityNode.getTasks());                 Assert.notNull(approvalTaskInfo, "任务不能为空");                 int index = CollUtil.indexOf(candidateUserIds, userId -> ObjectUtils.equalsAny(userId, approvalTaskInfo.getOwner(),                         approvalTaskInfo.getAssignee())); // 委派或者向前加签情况,需要先比较 owner                 activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size()));             }             return activityNode;         });     }     /**      * 获得【预测(未来)】的活动节点们      */     private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel,                                                           BpmProcessDefinitionInfoDO processDefinitionInfo,                                                           Map<String, Object> processVariables,                                                           List<HistoricActivityInstance> activities) {         // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录         Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId);         // 情况一:BPMN 设计器         if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) {             List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables);             return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel,                     processDefinitionInfo, processVariables, flowElement, runActivityIds));         }         // 情况二:SIMPLE 设计器         if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) {             BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class);             List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables);             return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel,                     processDefinitionInfo, processVariables, simpleNode, runActivityIds));         }         throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType());     }     private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel,                                                          BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,                                                          BpmSimpleModelNodeVO node, Set<String> runActivityIds) {         // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录         if (runActivityIds.contains(node.getId())) {             return null;         }         ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName())                 .setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy())                 .setStatus(BpmTaskStatusEnum.NOT_START.getStatus());         // 1. 开始节点/审批节点         if (ObjectUtils.equalsAny(node.getType(),                 BpmSimpleModelNodeType.START_USER_NODE.getType(),                 BpmSimpleModelNodeType.APPROVE_NODE.getType())) {             List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),                     startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables);             activityNode.setCandidateUserIds(candidateUserIds);             return activityNode;         }         // 2. 结束节点         if (BpmSimpleModelNodeType.END_NODE.getType().equals(node.getType())) {             return activityNode;         }         // 3. 抄送节点         if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人                 BpmSimpleModelNodeType.COPY_NODE.getType().equals(node.getType())) {             return activityNode;         }         return null;     }     private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel,                                                        BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,                                                        FlowElement node, Set<String> runActivityIds) {         if (runActivityIds.contains(node.getId())) {             return null;         }         ActivityNode activityNode = new ActivityNode().setId(node.getId()).setStatus(BpmTaskStatusEnum.NOT_START.getStatus());         // 1. 开始节点         if (node instanceof StartEvent) {             return activityNode.setName(BpmSimpleModelNodeType.START_USER_NODE.getName())                     .setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType());         }         // 2. 审批节点         if (node instanceof UserTask) {             List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),                     startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables);             return activityNode.setName(node.getName()).setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType())                     .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))                     .setCandidateUserIds(candidateUserIds);         }         // 3. 结束节点         if (node instanceof EndEvent) {             return activityNode.setName(BpmSimpleModelNodeType.END_NODE.getName())                     .setNodeType(BpmSimpleModelNodeType.END_NODE.getType());         }         return null;     }     private List<Long> getTaskCandidateUserList(BpmnModel bpmnModel, String activityId,                                                 Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {         Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,                 startUserId, processDefinitionId, processVariables);         return new ArrayList<>(userIds);     }     @Override     public BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id) {         // 1.1 获得流程实例         HistoricProcessInstance processInstance = getHistoricProcessInstance(id);         if (processInstance == null) {             return null;         }         // 1.2 获得流程定义         BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId());         if (bpmnModel == null) {             return null;         }         BpmSimpleModelNodeVO simpleModel = null;         BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(                 processInstance.getProcessDefinitionId());         if (processDefinitionInfo != null && BpmModelTypeEnum.SIMPLE.getType().equals(processDefinitionInfo.getModelType())) {             simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class);         }         // 1.3 获得流程实例对应的活动实例列表 + 任务列表         List<HistoricActivityInstance> activities = taskService.getActivityListByProcessInstanceId(id);         List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(id, true);         // 2.1 拼接进度信息         Set<String> unfinishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,                 activityInstance -> activityInstance.getEndTime() == null);         Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,                 activityInstance -> activityInstance.getEndTime() != null                         && ObjectUtil.notEqual(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));         Set<String> finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,                 activityInstance -> activityInstance.getEndTime() != null                         && ObjectUtil.equals(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));         // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉         unfinishedTaskActivityIds.removeAll(finishedTaskActivityIds);         // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。         // 注意,只取最后一个。因为会存在多次拒绝的情况,拒绝驳回到指定节点         Set<String> rejectTaskActivityIds = CollUtil.newHashSet();         if (BpmProcessInstanceStatusEnum.isRejectStatus(FlowableUtils.getProcessInstanceStatus(processInstance))) {             tasks.stream()                     .filter(task -> BpmTaskStatusEnum.isRejectStatus(FlowableUtils.getTaskStatus(task)))                     .max(Comparator.comparing(HistoricTaskInstance::getEndTime))                     .ifPresent(reject -> rejectTaskActivityIds.add(reject.getTaskDefinitionKey()));             finishedTaskActivityIds.removeAll(rejectTaskActivityIds);         }         // 2.2 拼接基础信息         Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds02(processInstance, tasks);         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));         return BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceBpmnModelView(processInstance, tasks, bpmnModel, simpleModel,                 unfinishedTaskActivityIds, finishedTaskActivityIds, finishedSequenceFlowActivityIds, rejectTaskActivityIds,                 userMap, deptMap);     }     // ========== Update 写入相关方法 ==========     @Override     @Transactional(rollbackFor = Exception.class)     public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) {         // 获得流程定义         ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());         // 发起流程         return createProcessInstance0(userId, definition, createReqVO.getVariables(), null,                 createReqVO.getStartUserSelectAssignees());     }     @Override     public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) {         // 获得流程定义         ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());         // 发起流程         return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(),                 createReqDTO.getStartUserSelectAssignees());     }     private String createProcessInstance0(Long userId, ProcessDefinition definition,                                           Map<String, Object> variables, String businessKey,                                           Map<String, List<Long>> startUserSelectAssignees) {         // 1.1 校验流程定义         if (definition == null) {             throw exception(PROCESS_DEFINITION_NOT_EXISTS);         }         if (definition.isSuspended()) {             throw exception(PROCESS_DEFINITION_IS_SUSPENDED);         }         BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId());         if (processDefinitionInfo == null) {             throw exception(PROCESS_DEFINITION_NOT_EXISTS);         }         // 1.2 校验是否能够发起         if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) {             throw exception(PROCESS_INSTANCE_START_USER_CAN_START);         }         // 1.3 校验发起人自选审批人         validateStartUserSelectAssignees(definition, startUserSelectAssignees);         // 2. 创建流程实例         if (variables == null) {             variables = new HashMap<>();         }         FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用         variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, userId); // 设置流程变量,发起人 ID         variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中                 BpmProcessInstanceStatusEnum.RUNNING.getStatus());         if (CollUtil.isNotEmpty(startUserSelectAssignees)) {             variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees);         }         ProcessInstance instance = runtimeService.createProcessInstanceBuilder()                 .processDefinitionId(definition.getId())                 .businessKey(businessKey)                 .name(definition.getName().trim())                 .variables(variables)                 .start();         return instance.getId();     }     private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) {         // 1. 获得发起人自选审批人的 UserTask/ServiceTask 列表         BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId());         List<Task> tasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectTaskList(bpmnModel);         if (CollUtil.isEmpty(tasks)) {             return;         }         // 2. 校验发起人自选审批人的审批人和抄送人是否都配置了         tasks.forEach(task -> {             List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null;             if (CollUtil.isEmpty(assignees)) {                 throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName());             }             Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees);             assignees.forEach(assignee -> {                 if (userMap.get(assignee) == null) {                     throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, task.getName(), assignee);                 }             });         });     }     @Override     public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) {         // 1.1 校验流程实例存在         ProcessInstance instance = getProcessInstance(cancelReqVO.getId());         if (instance == null) {             throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);         }         // 1.2 只能取消自己的         if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {             throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);         }         // 2. 取消流程         updateProcessInstanceCancel(cancelReqVO.getId(),                 BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason()));     }     @Override     public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) {         // 1.1 校验流程实例存在         ProcessInstance instance = getProcessInstance(cancelReqVO.getId());         if (instance == null) {             throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);         }         // 2. 取消流程         AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData();         updateProcessInstanceCancel(cancelReqVO.getId(),                 BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason()));     }     private void updateProcessInstanceCancel(String id, String reason) {         // 1. 更新流程实例 status         runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS,                 BpmProcessInstanceStatusEnum.CANCEL.getStatus());         runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason);         // 2. 结束流程         taskService.moveTaskToEnd(id);     }     @Override     public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) {         runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS,                 BpmProcessInstanceStatusEnum.REJECT.getStatus());         runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON,                 BpmReasonEnum.REJECT_TASK.format(reason));     }     // ========== Event 事件相关方法 ==========     @Override     public void processProcessInstanceCompleted(ProcessInstance instance) {         // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号         FlowableUtils.execute(instance.getTenantId(), () -> {             // 1.1 获取当前状态             Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);             String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON);             // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态             // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了             if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) {                 status = BpmProcessInstanceStatusEnum.APPROVE.getStatus();                 runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status);             }             // 2. 发送对应的消息通知             if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) {                 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance));             } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) {                 messageService.sendMessageWhenProcessInstanceReject(                         BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason));             }             // 3. 发送流程实例的状态事件             processInstanceEventPublisher.sendProcessInstanceResultEvent(                     BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status));         });     } }
package com.iailab.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.date.DateUtils; import com.iailab.framework.common.util.json.JsonUtils; import com.iailab.framework.common.util.object.ObjectUtils; import com.iailab.framework.common.util.object.PageUtils; import com.iailab.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import com.iailab.module.bpm.controller.admin.task.vo.instance.*; import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import com.iailab.module.bpm.convert.task.BpmProcessInstanceConvert; import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import com.iailab.module.bpm.enums.ErrorCodeConstants; import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum; import com.iailab.module.bpm.enums.definition.BpmSimpleModelNodeType; import com.iailab.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import com.iailab.module.bpm.enums.task.BpmReasonEnum; import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept.BpmTaskCandidateStartUserSelectStrategy; import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import com.iailab.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import com.iailab.module.bpm.framework.flowable.core.util.SimpleModelUtils; import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; import com.iailab.module.bpm.service.message.BpmMessageService; import com.iailab.module.system.api.dept.DeptApi; import com.iailab.module.system.api.dept.dto.DeptRespDTO; import com.iailab.module.system.api.user.AdminUserApi; import com.iailab.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.model.*; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.util.*; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.framework.common.util.collection.CollectionUtils.*; import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNodeTask; import static com.iailab.module.bpm.enums.ErrorCodeConstants.*; import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.flowable.bpmn.constants.BpmnXMLConstants.*; /**  * 流程实例 Service 实现类  * <p>  * ProcessDefinition & ProcessInstance & Execution & Task 的关系:  * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" />  * <p>  * HistoricProcessInstance & ProcessInstance 的关系:  * 1. <a href=" https://my.oschina.net/843294669/blog/71902" />  * <p>  * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例  *  * @author 芋道源码  */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService {     @Resource     private RuntimeService runtimeService;     @Resource     private HistoryService historyService;     @Resource     private BpmProcessDefinitionService processDefinitionService;     @Resource     @Lazy // 避免循环依赖     private BpmTaskService taskService;     @Resource     private BpmMessageService messageService;     @Resource     private AdminUserApi adminUserApi;     @Resource     private DeptApi deptApi;     @Resource     private BpmProcessInstanceEventPublisher processInstanceEventPublisher;     @Resource     private BpmTaskCandidateInvoker taskCandidateInvoker;     // ========== Query 查询相关方法 ==========     @Override     public ProcessInstance getProcessInstance(String id) {         return runtimeService.createProcessInstanceQuery()                 .includeProcessVariables()                 .processInstanceId(id)                 .singleResult();     }     @Override     public List<ProcessInstance> getProcessInstances(Set<String> ids) {         return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();     }     @Override     public HistoricProcessInstance getHistoricProcessInstance(String id) {         return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult();     }     @Override     public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {         return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();     }     @Override     public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,                                                                       BpmProcessInstancePageReqVO pageReqVO) {         // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页         HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery()                 .includeProcessVariables()                 .processInstanceTenantId(FlowableUtils.getTenantId())                 .orderByProcessInstanceStartTime().desc();         if (userId != null) { // 【我的流程】菜单时,需要传递该字段             processInstanceQuery.startedBy(String.valueOf(userId));         } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段             processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId()));         }         if (StrUtil.isNotEmpty(pageReqVO.getName())) {             processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%");         }         if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) {             processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey());         }         if (StrUtil.isNotEmpty(pageReqVO.getCategory())) {             processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory());         }         if (pageReqVO.getStatus() != null) {             processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus());         }         if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) {             processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0]));             processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1]));         }         // 查询数量         long processInstanceCount = processInstanceQuery.count();         if (processInstanceCount == 0) {             return PageResult.empty(processInstanceCount);         }         // 查询列表         List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize());         return new PageResult<>(processInstanceList, processInstanceCount);     }     private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel,                                                         String activityId, String taskId) {         // 1. 获取流程活动编号。流程活动 Id 为空事,从流程任务中获取流程活动 Id         if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) {             activityId = Optional.ofNullable(taskService.getHistoricTask(taskId))                     .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null);         }         if (StrUtil.isEmpty(activityId)) {             return null;         }         // 2. 从 BpmnModel 中解析表单字段权限         return BpmnModelUtils.parseFormFieldsPermission(bpmnModel, activityId);     }     @Override     public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) {         // 1.1 从 reqVO 中,读取公共变量         Long startUserId = loginUserId; // 流程发起人         HistoricProcessInstance historicProcessInstance = null; // 流程实例         Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态         Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量         // 1.2 如果是流程已发起的场景,则使用流程实例的数据         if (reqVO.getProcessInstanceId() != null) {             historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId());             if (historicProcessInstance == null) {                 throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);             }             startUserId = Long.valueOf(historicProcessInstance.getStartUserId());             processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance);             processVariables = historicProcessInstance.getProcessVariables();         }         // 1.3 读取其它相关数据         ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(                 historicProcessInstance != null ? historicProcessInstance.getProcessDefinitionId() : reqVO.getProcessDefinitionId());         BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId());         BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());         // 2.1 已结束 + 进行中的活动节点         List<ActivityNode> endActivityNodes = null; // 已结束的审批信息         List<ActivityNode> runActivityNodes = null; // 进行中的审批信息         List<HistoricActivityInstance> activities = null; // 流程实例列表         if (reqVO.getProcessInstanceId() != null) {             activities = taskService.getActivityListByProcessInstanceId(reqVO.getProcessInstanceId());             List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(reqVO.getProcessInstanceId(), true);             endActivityNodes = getEndActivityNodeList(startUserId, bpmnModel, processDefinitionInfo,                     historicProcessInstance, processInstanceStatus, activities, tasks);             runActivityNodes = getRunApproveNodeList(startUserId, bpmnModel, processDefinition, processVariables, activities, tasks);         }         // 2.2 流程已经结束,直接 return,无需预测         if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) {             return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,                     processInstanceStatus, endActivityNodes, runActivityNodes, null, null);         }         // 3.1 计算当前登录用户的待办任务         // TODO @jason:有一个极端情况,如果一个用户有 2 个 task A 和 B,A 已经通过,B 需要审核。这个时,通过 A 进来,todo 拿到 B,会不会表单权限不一致哈。         BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId());         // 3.2 预测未运行节点的审批信息         List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, processDefinitionInfo,                 processVariables, activities);         // 4. 拼接最终数据         return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,                 processInstanceStatus, endActivityNodes, runActivityNodes, simulateActivityNodes, todoTask);     }     /**      * 拼接审批详情的最终数据      * <p>      * 主要是,拼接审批人的用户信息、部门信息      */     private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO,                                                         BpmnModel bpmnModel,                                                         ProcessDefinition processDefinition,                                                         BpmProcessDefinitionInfoDO processDefinitionInfo,                                                         HistoricProcessInstance processInstance,                                                         Integer processInstanceStatus,                                                         List<ActivityNode> endApprovalNodeInfos,                                                         List<ActivityNode> runningApprovalNodeInfos,                                                         List<ActivityNode> simulateApprovalNodeInfos,                                                         BpmTaskRespVO todoTask) {         // 1. 获取所有需要读取用户信息的 userIds         List<ActivityNode> approveNodes = newArrayList(asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos));         Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds(processInstance, approveNodes, todoTask);         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));         // 2. 表单权限         String taskId = reqVO.getTaskId() == null && todoTask != null ? todoTask.getId() : reqVO.getTaskId();         Map<String, String> formFieldsPermission = getFormFieldsPermission(bpmnModel, reqVO.getActivityId(), taskId);         // 3. 拼接数据         return BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel, processDefinition, processDefinitionInfo, processInstance,                 processInstanceStatus, approveNodes, todoTask, formFieldsPermission, userMap, deptMap);     }     /**      * 获得【已结束】的活动节点们      */     private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel,                                                       BpmProcessDefinitionInfoDO processDefinitionInfo,                                                       HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus,                                                       List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) {         // 遍历 tasks 列表,只处理已结束的 UserTask         // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点         List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null);         List<ActivityNode> approvalNodes = convertList(endTasks, task -> {             FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());             ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName())                     .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) ?                             BpmSimpleModelNodeType.START_USER_NODE.getType() : BpmSimpleModelNodeType.APPROVE_NODE.getType())                     .setStatus(FlowableUtils.getTaskStatus(task))                     .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))                     .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime()))                     .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)));             // 如果是取消状态,则跳过             if (BpmTaskStatusEnum.isCancelStatus(activityNode.getStatus())) {                 return null;             }             return activityNode;         });         // 遍历 activities,只处理已结束的 StartEvent、EndEvent         List<HistoricActivityInstance> endActivities = filterList(activities, activity -> activity.getEndTime() != null                 && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_EVENT_END)));         endActivities.forEach(activity -> {             // StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点             if (ELEMENT_EVENT_START.equals(activity.getActivityType())                     && BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) {                 ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID)                         .setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus());                 ActivityNode startNode = new ActivityNode().setId(startTask.getId())                         .setName(BpmSimpleModelNodeType.START_USER_NODE.getName())                         .setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType())                         .setStatus(startTask.getStatus()).setTasks(ListUtil.of(startTask))                         .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime()));                 approvalNodes.add(0, startNode);                 return;             }             // EndEvent             if (ELEMENT_EVENT_END.equals(activity.getActivityType())) {                 if (BpmProcessInstanceStatusEnum.isRejectStatus(processInstanceStatus)) {                     // 拒绝情况下,不需要展示 EndEvent 结束节点。原因是:前端已经展示 x 效果,无需重复展示                     return;                 }                 ActivityNode endNode = new ActivityNode().setId(activity.getId())                         .setName(BpmSimpleModelNodeType.END_NODE.getName())                         .setNodeType(BpmSimpleModelNodeType.END_NODE.getType()).setStatus(processInstanceStatus)                         .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime()));                 String reason = FlowableUtils.getProcessInstanceReason(historicProcessInstance);                 if (StrUtil.isNotEmpty(reason)) {                     endNode.setTasks(singletonList(new ActivityNodeTask().setId(endNode.getId())                             .setStatus(endNode.getStatus()).setReason(reason)));                 }                 approvalNodes.add(endNode);             }         });         return approvalNodes;     }     /**      * 获得【进行中】的活动节点们      */     private List<ActivityNode> getRunApproveNodeList(Long startUserId,                                                      BpmnModel bpmnModel,                                                      ProcessDefinition processDefinition,                                                      Map<String, Object> processVariables,                                                      List<HistoricActivityInstance> activities,                                                      List<HistoricTaskInstance> tasks) {         // 构建运行中的任务,基于 activityId 分组         List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null                 && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER)));         Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities, HistoricActivityInstance::getActivityId);         // 按照 activityId 分组,构建 ApprovalNodeInfo 节点         Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId);         return convertList(runningTaskMap.entrySet(), entry -> {             String activityId = entry.getKey();             List<HistoricActivityInstance> taskActivities = entry.getValue();             // 构建活动节点             FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);             HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同             ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId()).setName(firstActivity.getActivityName())                     .setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()).setStatus(BpmTaskStatusEnum.RUNNING.getStatus())                     .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))                     .setStartTime(DateUtils.of(CollUtil.getFirst(taskActivities).getStartTime()))                     .setTasks(new ArrayList<>());             // 处理每个任务的 tasks 属性             for (HistoricActivityInstance activity : taskActivities) {                 HistoricTaskInstance task = taskMap.get(activity.getTaskId());                 activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task));                 // 加签子任务,需要过滤掉已经完成的加签子任务                 List<HistoricTaskInstance> childrenTasks = filterList(                         taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks),                         childTask -> childTask.getEndTime() == null);                 if (CollUtil.isNotEmpty(childrenTasks)) {                     activityNode.getTasks().addAll(convertList(childrenTasks, BpmProcessInstanceConvert.INSTANCE::buildApprovalTaskInfo));                 }             }             // 处理每个任务的 candidateUsers 属性:如果是依次审批,需要预测它的后续审批人。因为 Task 是审批完一个,创建一个新的 Task             if (BpmnModelUtils.isSequentialUserTask(flowNode)) {                 List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, flowNode.getId(),                         startUserId, processDefinition.getId(), processVariables);                 // 截取当前审批人位置后面的候选人,不包含当前审批人                 ActivityNodeTask approvalTaskInfo = CollUtil.getFirst(activityNode.getTasks());                 Assert.notNull(approvalTaskInfo, "任务不能为空");                 int index = CollUtil.indexOf(candidateUserIds, userId -> ObjectUtils.equalsAny(userId, approvalTaskInfo.getOwner(),                         approvalTaskInfo.getAssignee())); // 委派或者向前加签情况,需要先比较 owner                 activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size()));             }             return activityNode;         });     }     /**      * 获得【预测(未来)】的活动节点们      */     private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel,                                                           BpmProcessDefinitionInfoDO processDefinitionInfo,                                                           Map<String, Object> processVariables,                                                           List<HistoricActivityInstance> activities) {         // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录         Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId);         // 情况一:BPMN 设计器         if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) {             List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables);             return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel,                     processDefinitionInfo, processVariables, flowElement, runActivityIds));         }         // 情况二:SIMPLE 设计器         if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) {             BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class);             List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables);             return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel,                     processDefinitionInfo, processVariables, simpleNode, runActivityIds));         }         throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType());     }     private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel,                                                          BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,                                                          BpmSimpleModelNodeVO node, Set<String> runActivityIds) {         // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录         if (runActivityIds.contains(node.getId())) {             return null;         }         ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName())                 .setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy())                 .setStatus(BpmTaskStatusEnum.NOT_START.getStatus());         // 1. 开始节点/审批节点         if (ObjectUtils.equalsAny(node.getType(),                 BpmSimpleModelNodeType.START_USER_NODE.getType(),                 BpmSimpleModelNodeType.APPROVE_NODE.getType())) {             List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),                     startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables);             activityNode.setCandidateUserIds(candidateUserIds);             return activityNode;         }         // 2. 结束节点         if (BpmSimpleModelNodeType.END_NODE.getType().equals(node.getType())) {             return activityNode;         }         // 3. 抄送节点         if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人                 BpmSimpleModelNodeType.COPY_NODE.getType().equals(node.getType())) {             return activityNode;         }         return null;     }     private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel,                                                        BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,                                                        FlowElement node, Set<String> runActivityIds) {         if (runActivityIds.contains(node.getId())) {             return null;         }         ActivityNode activityNode = new ActivityNode().setId(node.getId()).setStatus(BpmTaskStatusEnum.NOT_START.getStatus());         // 1. 开始节点         if (node instanceof StartEvent) {             return activityNode.setName(BpmSimpleModelNodeType.START_USER_NODE.getName())                     .setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType());         }         // 2. 审批节点         if (node instanceof UserTask) {             List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),                     startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables);             return activityNode.setName(node.getName()).setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType())                     .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))                     .setCandidateUserIds(candidateUserIds);         }         // 3. 结束节点         if (node instanceof EndEvent) {             return activityNode.setName(BpmSimpleModelNodeType.END_NODE.getName())                     .setNodeType(BpmSimpleModelNodeType.END_NODE.getType());         }         return null;     }     private List<Long> getTaskCandidateUserList(BpmnModel bpmnModel, String activityId,                                                 Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {         Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,                 startUserId, processDefinitionId, processVariables);         return new ArrayList<>(userIds);     }     @Override     public BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id) {         // 1.1 获得流程实例         HistoricProcessInstance processInstance = getHistoricProcessInstance(id);         if (processInstance == null) {             return null;         }         // 1.2 获得流程定义         BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId());         if (bpmnModel == null) {             return null;         }         BpmSimpleModelNodeVO simpleModel = null;         BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(                 processInstance.getProcessDefinitionId());         if (processDefinitionInfo != null && BpmModelTypeEnum.SIMPLE.getType().equals(processDefinitionInfo.getModelType())) {             simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class);         }         // 1.3 获得流程实例对应的活动实例列表 + 任务列表         List<HistoricActivityInstance> activities = taskService.getActivityListByProcessInstanceId(id);         List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(id, true);         // 2.1 拼接进度信息         Set<String> unfinishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,                 activityInstance -> activityInstance.getEndTime() == null);         Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,                 activityInstance -> activityInstance.getEndTime() != null                         && ObjectUtil.notEqual(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));         Set<String> finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,                 activityInstance -> activityInstance.getEndTime() != null                         && ObjectUtil.equals(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));         // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉         finishedTaskActivityIds.removeAll(unfinishedTaskActivityIds);         // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。         // 注意,只取最后一个。因为会存在多次拒绝的情况,拒绝驳回到指定节点         Set<String> rejectTaskActivityIds = CollUtil.newHashSet();         if (BpmProcessInstanceStatusEnum.isRejectStatus(FlowableUtils.getProcessInstanceStatus(processInstance))) {             tasks.stream()                     .filter(task -> BpmTaskStatusEnum.isRejectStatus(FlowableUtils.getTaskStatus(task)))                     .max(Comparator.comparing(HistoricTaskInstance::getEndTime))                     .ifPresent(reject -> rejectTaskActivityIds.add(reject.getTaskDefinitionKey()));             finishedTaskActivityIds.removeAll(rejectTaskActivityIds);         }         // 2.2 拼接基础信息         Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds02(processInstance, tasks);         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));         return BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceBpmnModelView(processInstance, tasks, bpmnModel, simpleModel,                 unfinishedTaskActivityIds, finishedTaskActivityIds, finishedSequenceFlowActivityIds, rejectTaskActivityIds,                 userMap, deptMap);     }     // ========== Update 写入相关方法 ==========     @Override     @Transactional(rollbackFor = Exception.class)     public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) {         // 获得流程定义         ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());         // 发起流程         return createProcessInstance0(userId, definition, createReqVO.getVariables(), null,                 createReqVO.getStartUserSelectAssignees());     }     @Override     public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) {         return FlowableUtils.executeAuthenticatedUserId(userId, () -> {             // 获得流程定义             ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());             // 发起流程             return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(),                     createReqDTO.getStartUserSelectAssignees());         });     }     private String createProcessInstance0(Long userId, ProcessDefinition definition,                                           Map<String, Object> variables, String businessKey,                                           Map<String, List<Long>> startUserSelectAssignees) {         // 1.1 校验流程定义         if (definition == null) {             throw exception(PROCESS_DEFINITION_NOT_EXISTS);         }         if (definition.isSuspended()) {             throw exception(PROCESS_DEFINITION_IS_SUSPENDED);         }         BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId());         if (processDefinitionInfo == null) {             throw exception(PROCESS_DEFINITION_NOT_EXISTS);         }         // 1.2 校验是否能够发起         if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) {             throw exception(PROCESS_INSTANCE_START_USER_CAN_START);         }         // 1.3 校验发起人自选审批人         validateStartUserSelectAssignees(definition, startUserSelectAssignees);         // 2. 创建流程实例         if (variables == null) {             variables = new HashMap<>();         }         FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用         variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, userId); // 设置流程变量,发起人 ID         variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中                 BpmProcessInstanceStatusEnum.RUNNING.getStatus());         if (CollUtil.isNotEmpty(startUserSelectAssignees)) {             variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees);         }         ProcessInstance instance = runtimeService.createProcessInstanceBuilder()                 .processDefinitionId(definition.getId())                 .businessKey(businessKey)                 .name(definition.getName().trim())                 .variables(variables)                 .start();         return instance.getId();     }     private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) {         // 1. 获得发起人自选审批人的 UserTask/ServiceTask 列表         BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId());         List<Task> tasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectTaskList(bpmnModel);         if (CollUtil.isEmpty(tasks)) {             return;         }         // 2. 校验发起人自选审批人的审批人和抄送人是否都配置了         tasks.forEach(task -> {             List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null;             if (CollUtil.isEmpty(assignees)) {                 throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName());             }             Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees);             assignees.forEach(assignee -> {                 if (userMap.get(assignee) == null) {                     throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, task.getName(), assignee);                 }             });         });     }     @Override     public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) {         // 1.1 校验流程实例存在         ProcessInstance instance = getProcessInstance(cancelReqVO.getId());         if (instance == null) {             throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);         }         // 1.2 只能取消自己的         if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {             throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);         }         // 2. 取消流程         updateProcessInstanceCancel(cancelReqVO.getId(),                 BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason()));     }     @Override     public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) {         // 1.1 校验流程实例存在         ProcessInstance instance = getProcessInstance(cancelReqVO.getId());         if (instance == null) {             throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);         }         // 2. 取消流程         AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData();         updateProcessInstanceCancel(cancelReqVO.getId(),                 BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason()));     }     private void updateProcessInstanceCancel(String id, String reason) {         // 1. 更新流程实例 status         runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS,                 BpmProcessInstanceStatusEnum.CANCEL.getStatus());         runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason);         // 2. 结束流程         taskService.moveTaskToEnd(id);     }     @Override     public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) {         runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS,                 BpmProcessInstanceStatusEnum.REJECT.getStatus());         runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON,                 BpmReasonEnum.REJECT_TASK.format(reason));     }     // ========== Event 事件相关方法 ==========     @Override     public void processProcessInstanceCompleted(ProcessInstance instance) {         // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号         FlowableUtils.execute(instance.getTenantId(), () -> {             // 1.1 获取当前状态             Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);             String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON);             // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态             // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了             if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) {                 status = BpmProcessInstanceStatusEnum.APPROVE.getStatus();                 runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status);             }             // 2. 发送对应的消息通知             if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) {                 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance));             } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) {                 messageService.sendMessageWhenProcessInstanceReject(                         BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason));             }             // 3. 发送流程实例的状态事件             processInstanceEventPublisher.sendProcessInstanceResultEvent(                     BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status));         });     } }
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -13,6 +13,7 @@
import com.iailab.framework.web.core.util.WebFrameworkUtils;
import com.iailab.module.bpm.controller.admin.task.vo.task.*;
import com.iailab.module.bpm.convert.task.BpmTaskConvert;
import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO;
import com.iailab.module.bpm.enums.definition.*;
import com.iailab.module.bpm.enums.task.BpmCommentTypeEnum;
import com.iailab.module.bpm.enums.task.BpmReasonEnum;
@@ -21,6 +22,7 @@
import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.iailab.module.bpm.service.definition.BpmFormService;
import com.iailab.module.bpm.service.definition.BpmModelService;
import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService;
import com.iailab.module.bpm.service.message.BpmMessageService;
@@ -92,6 +94,8 @@
    private BpmModelService modelService;
    @Resource
    private BpmMessageService messageService;
    @Resource
    private BpmFormService formService;
    @Resource
    private AdminUserApi adminUserApi;
@@ -109,6 +113,9 @@
                .orderByTaskCreateTime().desc(); // 创建时间倒序
        if (StrUtil.isNotBlank(pageVO.getName())) {
            taskQuery.taskNameLike("%" + pageVO.getName() + "%");
        }
        if (StrUtil.isNotEmpty(pageVO.getCategory())) {
            taskQuery.taskCategory(pageVO.getCategory());
        }
        if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
            taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
@@ -154,7 +161,13 @@
        BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId());
        Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting = BpmnModelUtils.parseButtonsSetting(
                bpmnModel, todoTask.getTaskDefinitionKey());
        return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting);
        // 4. 任务表单
        BpmFormDO taskForm = null;
        if (StrUtil.isNotBlank(todoTask.getFormKey())){
            taskForm = formService.getForm(NumberUtils.parseLong(todoTask.getFormKey()));
        }
        return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting, taskForm);
    }
    @Override
@@ -188,6 +201,9 @@
                .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
        if (StrUtil.isNotBlank(pageVO.getName())) {
            taskQuery.taskNameLike("%" + pageVO.getName() + "%");
        }
        if (StrUtil.isNotEmpty(pageVO.getCategory())) {
            taskQuery.taskCategory(pageVO.getCategory());
        }
        if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
            taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
@@ -442,7 +458,7 @@
     * 判断指定用户,是否是当前任务的加签人
     *
     * @param userId 用户 Id
     * @param task 任务
     * @param task   任务
     * @return 是否
     */
    private boolean isAddSignUserTask(Long userId, Task task) {
@@ -670,7 +686,7 @@
                reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId());
        // 2. 调用 Flowable 框架的退回逻辑
        returnTask(task, targetElement, reqVO);
        returnTask(userId, task, targetElement, reqVO);
    }
    /**
@@ -702,11 +718,12 @@
    /**
     * 执行退回逻辑
     *
     * @param userId        用户编号
     * @param currentTask   当前退回的任务
     * @param targetElement 需要退回到的目标任务
     * @param reqVO         前端参数封装
     */
    public void returnTask(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
    public void returnTask(Long userId, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
        // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤
        // 1.1 获取所有正常进行的任务节点 Key
        List<Task> taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list();
@@ -722,22 +739,29 @@
            if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) {
                return;
            }
            // 2.1 添加评论
            taskService.addComment(task.getId(), currentTask.getProcessInstanceId(), BpmCommentTypeEnum.RETURN.getType(),
                    BpmCommentTypeEnum.RETURN.formatComment(reqVO.getReason()));
            // 2.2 更新 task 状态 + 原因
            updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason());
            // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务
            if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记
                // 2.1.1 添加评论
                taskService.addComment(task.getId(), currentTask.getProcessInstanceId(), BpmCommentTypeEnum.RETURN.getType(),
                        BpmCommentTypeEnum.RETURN.formatComment(reqVO.getReason()));
                // 2.1.2 更新 task 状态 + 原因
                updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason());
            } else { // 情况二:别人的任务,进行 CANCEL 标记
                processTaskCanceled(task.getId());
            }
        });
        // 3. 设置流程变量节点驳回标记:用于驳回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过
        runtimeService.setVariable(currentTask.getProcessInstanceId(),
                String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE);
        // 4. 执行驳回
        // 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因:
        // 当多实例任务回退的时候有问题。相关 issue: https://github.com/flowable/flowable-engine/issues/3944
        List<String> runExecutionIds = convertList(taskList, Task::getExecutionId);
        runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(currentTask.getProcessInstanceId())
                .moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多)
                        reqVO.getTargetTaskDefinitionKey()) // targetKey 跳转到的节点(1)
                .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey())
                .changeState();
    }
@@ -1022,14 +1046,22 @@
        Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTaskElement);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            /**
             * 特殊情况:部分情况下,TransactionSynchronizationManager 注册 afterCommit 监听时,不会被调用,但是 afterCompletion 可以
             * 例如说:第一个 task 就是配置【自动通过】或者【自动拒绝】时
             * 参见 <a href="https://gitee.com/zhijiantianya/yudao-cloud/issues/IB7V7Q">issue</a> 反馈
             */
            @Override
            public void afterCompletion(int transactionStatus) {
                // 特殊情况:部分情况下,TransactionSynchronizationManager 注册 afterCommit 监听时,不会被调用,但是 afterCompletion 可以
                // 例如说:第一个 task 就是配置【自动通过】或者【自动拒绝】时
                if (ObjectUtil.notEqual(transactionStatus, TransactionSynchronization.STATUS_COMMITTED)) {
                // 回滚情况,直接返回
                if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_ROLLED_BACK)) {
                    return;
                }
                // TODO 芋艿:可以后续优化成 getSelf();
                // 特殊情况:第一个 task 【自动通过】时,第二个任务设置审批人时 transactionStatus 会为 STATUS_UNKNOWN,不知道啥原因
                if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_UNKNOWN)
                        && getTask(task.getId()) == null) {
                    return;
                }
                // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝
                if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) {
                    // 如果有审批人、或者拥有人,则说明不满足情况一,不自动通过、不自动拒绝
@@ -1037,19 +1069,19 @@
                        return;
                    }
                    if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) {
                        SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
                        getSelf().approveTask(null, new BpmTaskApproveReqVO()
                                .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason()));
                    } else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) {
                        SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO()
                        getSelf().rejectTask(null, new BpmTaskRejectReqVO()
                                .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason()));
                    }
                    // 特殊情况二:【自动审核】审批类型为自动通过、不通过
                } else {
                    if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType())) {
                        SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
                        getSelf().approveTask(null, new BpmTaskApproveReqVO()
                                .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_APPROVE.getReason()));
                    } else if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
                        SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO()
                        getSelf().rejectTask(null, new BpmTaskRejectReqVO()
                                .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_REJECT.getReason()));
                    }
                }
@@ -1088,8 +1120,22 @@
        // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            /**
             * 特殊情况:部分情况下,TransactionSynchronizationManager 注册 afterCommit 监听时,不会被调用,但是 afterCompletion 可以
             * 例如说:第一个 task 就是配置【自动通过】或者【自动拒绝】时
             * 参见 <a href="https://gitee.com/zhijiantianya/yudao-cloud/issues/IB7V7Q">issue</a> 反馈
             */
            @Override
            public void afterCommit() {
            public void afterCompletion(int transactionStatus) {
                // 回滚情况,直接返回
                if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_ROLLED_BACK)) {
                    return;
                }
                // 特殊情况:第一个 task 【自动通过】时,第二个任务设置审批人时 transactionStatus 会为 STATUS_UNKNOWN,不知道啥原因
                if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_UNKNOWN)
                        && getTask(task.getId()) == null) {
                    return;
                }
                if (StrUtil.isEmpty(task.getAssignee())) {
                    log.error("[processTaskAssigned][taskId({}) 没有分配到负责人]", task.getId());
                    return;
@@ -1146,6 +1192,7 @@
                        }
                    }
                }
                AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData();
                messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
            }
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/api/point/DataPointApiImpl.java
@@ -113,6 +113,7 @@
            pointCollector.setValue(wr);
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }
iailab-module-model/iailab-module-model-api/src/main/java/com/iailab/module/model/api/mcs/McsApi.java
@@ -87,4 +87,8 @@
    @GetMapping(PREFIX + "/schedule-scheme/list")
    @Operation(summary = "获取调度方案列表")
    List<StScheduleSchemeDTO> listScheduleScheme(@RequestParam("triggerMethod") String triggerMethod, @RequestParam("triggerCondition") String triggerCondition);
    @GetMapping(PREFIX + "/chart/param/list")
    @Operation(summary = "图表配置列表")
    List<ChartParamDTO> getChartParamList(@RequestParam("chartCode") String chartCode);
}
iailab-module-model/iailab-module-model-api/src/main/java/com/iailab/module/model/api/mcs/dto/ChartParamDTO.java
文件名从 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/ChartParamDTO.java 修改
@@ -1,4 +1,4 @@
package com.iailab.module.model.mpk.dto;
package com.iailab.module.model.api.mcs.dto;
import lombok.Data;
iailab-module-model/iailab-module-model-api/src/main/java/com/iailab/module/model/api/mdk/MdkApi.java
@@ -37,6 +37,10 @@
    @Operation(summary = "执行调度方案")
    MdkScheduleRespDTO doSchedule(@Valid @RequestBody MdkScheduleReqDTO reqDTO);
    @PostMapping(PREFIX + "/schedule-result")
    @Operation(summary = "获取调度执行结果,不保存记录")
    MdkScheduleRespDTO runSchedule(MdkScheduleReqDTO reqDTO);
    @PostMapping(PREFIX + "/schedule-model/out")
    @Operation(summary = "调度模型数据下发")
    Boolean scheduleModelOut(@RequestBody MdkScheduleRespDTO dto);
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/api/McsApiImpl.java
@@ -26,6 +26,7 @@
import com.iailab.module.model.mcs.sche.service.StScheduleSuggestService;
import com.iailab.module.model.mcs.sche.vo.StScheduleSuggestSaveReqVO;
import com.iailab.module.model.mdk.vo.ItemVO;
import com.iailab.module.model.mpk.service.ChartParamService;
import com.iailab.module.model.mpk.service.ChartService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -86,6 +87,9 @@
    @Autowired
    private StScheduleSchemeService stScheduleSchemeService;
    @Autowired
    private ChartParamService chartParamService;
    private int HOUR_MINS = 60;
@@ -560,6 +564,14 @@
        return stScheduleSchemeService.list(params);
    }
    @Override
    public List<ChartParamDTO> getChartParamList(String chartCode) {
        if (StringUtils.isBlank(chartCode)) {
            return null;
        }
        return chartParamService.list(chartCode);
    }
    private Date[] calResultTime(ItemVO predictItem, Date startTimeReq, Date endTimeReq, int lengthLeft, int lengthRight) {
        Date[] result = new Date[3];
        Date predictTime = predictItem.getLastTime();
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/api/MdkApiImpl.java
@@ -1,6 +1,7 @@
package com.iailab.module.model.api;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.iailab.module.data.api.point.DataPointApi;
import com.iailab.module.data.api.point.dto.ApiPointValueWriteDTO;
import com.iailab.module.model.api.mcs.dto.StScheduleModelOutDTO;
@@ -24,11 +25,13 @@
import com.iailab.module.model.mdk.vo.ScheduleResultVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.iailab.module.model.common.enums.ModelOutResultType.D;
@@ -69,6 +72,11 @@
    @Autowired
    private DataPointApi dataPointApi;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    public static final long offset = 60 * 3L;
    /**
     * 按模块预测
@@ -216,6 +224,7 @@
        resp.setScheduleTime(reqDTO.getScheduleTime());
        try {
            log.info("调度计算开始: " + System.currentTimeMillis());
            log.info("reqDTO=" + JSON.toJSONString(reqDTO));
            ScheduleResultVO scheduleResult = scheduleModelHandler.doSchedule(reqDTO.getScheduleCode(), reqDTO.getScheduleTime(),
                    reqDTO.getDynamicDataLength(), reqDTO.getDynamicSettings());
            resp.setStatusCode(scheduleResult.getResultCode());
@@ -231,6 +240,40 @@
        return resp;
    }
    /**
     * 执行调度模型
     *
     * @param reqDTO
     * @return
     */
    @Override
    public MdkScheduleRespDTO runSchedule(MdkScheduleReqDTO reqDTO) {
        MdkScheduleRespDTO resp = new MdkScheduleRespDTO();
        resp.setScheduleCode(reqDTO.getScheduleCode());
        resp.setScheduleTime(reqDTO.getScheduleTime());
        String catchKey = "ScheduleResult:" + reqDTO.getScheduleCode();
        try {
            if (redisTemplate.hasKey(catchKey)) {
                log.info("查找调度结果缓存: " + catchKey);
                return JSON.parseObject(JSONObject.toJSONString(redisTemplate.opsForValue().get(catchKey)), MdkScheduleRespDTO.class);
            }
            log.info("调度计算开始: " + System.currentTimeMillis());
            log.info("reqDTO=" + JSON.toJSONString(reqDTO));
            ScheduleResultVO scheduleResult = scheduleModelHandler.doSchedule(reqDTO.getScheduleCode(), reqDTO.getScheduleTime(),
                    reqDTO.getDynamicDataLength(), reqDTO.getDynamicSettings());
            resp.setStatusCode(scheduleResult.getResultCode());
            resp.setResult(scheduleResult.getResult());
            redisTemplate.opsForValue().set(catchKey, JSON.toJSONString(resp), offset, TimeUnit.SECONDS);
            stScheduleSchemeService.updateTime(scheduleResult.getSchemeId(), scheduleResult.getScheduleTime(), scheduleResult.getResultCode());
            log.info("预测计算结束: " + System.currentTimeMillis());
        } catch (Exception ex) {
            log.info("调度计算异常: " + System.currentTimeMillis());
            ex.printStackTrace();
            return resp;
        }
        return resp;
    }
    @Override
    public Boolean scheduleModelOut(MdkScheduleRespDTO dto) {
        String modelId = stScheduleSchemeService.getByCode(dto.getScheduleCode()).getModelId();
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/api/controller/admin/MdkApiController.java
@@ -1,5 +1,6 @@
package com.iailab.module.model.api.controller.admin;
import com.iailab.module.model.api.mdk.MdkApi;
import com.iailab.module.model.api.mdk.dto.MdkScheduleReqDTO;
import com.iailab.module.model.api.mdk.dto.MdkScheduleRespDTO;
import com.iailab.module.model.common.utils.ApiSecurityUtils;
import io.swagger.v3.oas.annotations.Operation;
@@ -36,4 +37,12 @@
        apiSecurityUtils.validate(request);
        return mdkApi.scheduleModelOut(dto);
    }
    @PostMapping("/schedule-result")
    @Operation(summary = "获取调度执行结果,不保存记录")
    public MdkScheduleRespDTO runSchedule(HttpServletResponse response, HttpServletRequest
            request, @RequestBody MdkScheduleReqDTO reqDTO) throws Exception {
        apiSecurityUtils.validate(request);
        return mdkApi.runSchedule(reqDTO);
    }
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/controller/admin/StScheduleSchemeController.java
@@ -83,7 +83,7 @@
    @GetMapping("/export-excel")
    @Operation(summary = "导出调度方案 Excel")
    @PreAuthorize("@ss.hasPermission('system:tenant:export')")
    @PreAuthorize("@ss.hasPermission('sche:scheme:export')")
    @ApiAccessLog(operateType = EXPORT)
    public void exportTenantExcel(@Valid StScheduleSchemePageReqVO exportReqVO,
                                  HttpServletResponse response) throws IOException {
@@ -93,4 +93,20 @@
        ExcelUtils.write(response, "调度方案.xls", "数据", StScheduleSchemeEntity.class,
                BeanUtils.toBean(list, StScheduleSchemeEntity.class));
    }
    @PreAuthorize("@ss.hasPermission('sche:scheme:update')")
    @PutMapping("/enable")
    @Operation(summary = "启用")
    public CommonResult<Boolean> enable(@RequestBody String[] ids) {
        stScheduleSchemeService.enableByIds(ids);
        return success(true);
    }
    @PreAuthorize("@ss.hasPermission('sche:scheme:update')")
    @PutMapping("/disable")
    @Operation(summary = "禁用")
    public CommonResult<Boolean> disable(@RequestBody String[] ids) {
        stScheduleSchemeService.disableByIds(ids);
        return success(true);
    }
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/StScheduleSchemeService.java
@@ -32,4 +32,8 @@
    StScheduleSchemeEntity getByCode(String code);
    List<StScheduleSchemeDTO> list(Map<String, Object> params);
    void enableByIds(String[] ids);
    void disableByIds(String[] ids);
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/impl/StScheduleSchemeServiceImpl.java
@@ -1,5 +1,6 @@
package com.iailab.module.model.mcs.sche.service.impl;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.service.impl.BaseServiceImpl;
@@ -14,6 +15,7 @@
import com.iailab.module.model.mcs.sche.vo.StScheduleSchemePageReqVO;
import com.iailab.module.model.mcs.sche.vo.StScheduleSchemeSaveReqVO;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.*;
@@ -77,4 +79,32 @@
        entity.setScheduleTime(scheduleTime);
        baseDao.updateById(entity);
    }
    @Override
    @DSTransactional(rollbackFor = Exception.class)
    public void enableByIds(String[] ids) {
        if (CollectionUtils.isEmpty(Arrays.asList(ids))) {
            return;
        }
        Arrays.asList(ids).forEach(item -> {
            StScheduleSchemeEntity entity = new StScheduleSchemeEntity();
            entity.setId(item);
            entity.setStatus(0);
            baseDao.updateById(entity);
        });
    }
    @Override
    @DSTransactional(rollbackFor = Exception.class)
    public void disableByIds(String[] ids) {
        if (CollectionUtils.isEmpty(Arrays.asList(ids))) {
            return;
        }
        Arrays.asList(ids).forEach(item -> {
            StScheduleSchemeEntity entity = new StScheduleSchemeEntity();
            entity.setId(item);
            entity.setStatus(1);
            baseDao.updateById(entity);
        });
    }
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/vo/StScheduleRecordRespVO.java
@@ -1,6 +1,7 @@
package com.iailab.module.model.mcs.sche.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -30,9 +31,11 @@
    private String modelName;
    @Schema(description = "调度时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private String scheduleTime;
    @Schema(description = "创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;
    @Schema(description = "结果状态")
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mdk/sample/PredictSampleDataConstructor.java
@@ -66,7 +66,7 @@
     * @return
     */
    @Override
    public List<SampleData>  prepareSampleData(SampleInfo sampleInfo) throws Exception {
    public List<SampleData> prepareSampleData(SampleInfo sampleInfo) throws Exception {
        List<SampleData> sampleDataList = new ArrayList<>();
        Map<String, ApiPointDTO> pointMap = sampleInfo.getPointMap();
        Map<String, ApiPlanItemDTO> planMap = sampleInfo.getPlanMap();
@@ -91,11 +91,11 @@
            //对每一项依次进行数据查询,然后将查询出的值赋给matrix对应的位置
            for (int i = 0; i < entry.getColumnItemList().size(); i++) {
                try {
                    List<DataValueVO> dataEntityList = getData(entry.getColumnItemList().get(i),pointMap,planMap);
                    List<DataValueVO> dataEntityList = getData(entry.getColumnItemList().get(i), pointMap, planMap);
                    //补全数据
                    ColumnItem columnItem = entry.getColumnItemList().get(i);
                    dataEntityList = super.completionData(matrix.length, dataEntityList, columnItem.startTime, columnItem.endTime,
                            columnItem.paramId, columnItem.getParamType(),pointMap,planMap);
                            columnItem.paramId, columnItem.getParamType(), pointMap, planMap);
                    /** 如果数据取不满,把缺失的数据点放在后面 */
                    if (dataEntityList != null && dataEntityList.size() != 0) {
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mdk/sample/PredictSampleInfoConstructor.java
@@ -62,7 +62,7 @@
     * @return
     */
    @Override
    protected SampleInfo getColumnInfo(String modelId, Date predictTime) {
    protected SampleInfo getColumnInfo(String modelId, Date predictTime, Map<Integer, Integer> dynamicDataLength) {
        SampleInfo sampleInfo = new SampleInfo();
        List<ColumnItemPort> resultList = new ArrayList<>();
        List<ColumnItem> columnItemList = new ArrayList<>();
@@ -75,28 +75,28 @@
        //设置当前端口号,初始值为最小端口(查询结果按端口号从小到达排列)
        int curPortOrder = modelInputParamEntityList.get(0).getModelparamportorder();
        //设置当前查询数据长度,初始值为最小端口数据长度
        int curDataLength = modelInputParamEntityList.get(0).getDatalength();
        int curDataLength = super.getDataLength(dynamicDataLength, curPortOrder, modelInputParamEntityList.get(0).getDatalength());
        // 统一获取测点的信息
        Set<String> pointIds = modelInputParamEntityList.stream().filter(e -> ModelParamType.getEumByCode(e.getModelparamtype()).equals(ModelParamType.DATAPOINT)).map(MmModelParamEntity::getModelparamid).collect(Collectors.toSet());
        List<ApiPointDTO> points = dataPointApi.getInfoByIds(pointIds);
        Map<String, ApiPointDTO> pointMap = points.stream().collect(Collectors.toMap(ApiPointDTO::getId, Function.identity(), (e1,e2) -> e1));
        Map<String, ApiPointDTO> pointMap = points.stream().collect(Collectors.toMap(ApiPointDTO::getId, Function.identity(), (e1, e2) -> e1));
        // 统一获取计划数据的信息
        Set<String> planIds = modelInputParamEntityList.stream().filter(e -> ModelParamType.getEumByCode(e.getModelparamtype()).equals(ModelParamType.PLAN)).map(MmModelParamEntity::getModelparamid).collect(Collectors.toSet());
        List<ApiPlanItemDTO> plans = planItemApi.getInfoByIds(planIds);
        Map<String, ApiPlanItemDTO> planMap = plans.stream().collect(Collectors.toMap(ApiPlanItemDTO::getId, Function.identity(), (e1,e2) -> e1));
        Map<String, ApiPlanItemDTO> planMap = plans.stream().collect(Collectors.toMap(ApiPlanItemDTO::getId, Function.identity(), (e1, e2) -> e1));
        for (MmModelParamEntity entry : modelInputParamEntityList) {
            columnInfo.setParamType(entry.getModelparamtype());
            columnInfo.setParamId(entry.getModelparamid());
            columnInfo.setDataLength(entry.getDatalength());
            columnInfo.setDataLength(super.getDataLength(dynamicDataLength, entry.getModelparamportorder(), entry.getDatalength()));
            columnInfo.setModelParamOrder(entry.getModelparamorder());
            columnInfo.setModelParamPortOrder(entry.getModelparamportorder());
            columnInfo.setStartTime(getStartTime(columnInfo, predictTime,pointMap,planMap));
            columnInfo.setEndTime(getEndTime(columnInfo, predictTime,pointMap,planMap));
            columnInfo.setGranularity(super.getGranularity(columnInfo,pointMap,planMap));
            columnInfo.setStartTime(getStartTime(columnInfo, predictTime, pointMap, planMap));
            columnInfo.setEndTime(getEndTime(columnInfo, predictTime, pointMap, planMap));
            columnInfo.setGranularity(super.getGranularity(columnInfo, pointMap, planMap));
            //对每一个爪进行数据项归并
            if (curPortOrder != entry.getModelparamportorder()){
            if (curPortOrder != entry.getModelparamportorder()) {
                //当数据项端口号不为当前端口号时,封装上一个端口类,操作下一个端口类
                curPort.setColumnItemList(columnItemList);
                curPort.setDataLength(curDataLength);
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mdk/sample/SampleConstructor.java
@@ -28,21 +28,11 @@
    @Autowired
    private SampleFactory sampleFactory;
    public List<SampleData> constructSample(String typeA, String modelId, Date runTime,String itemName, Map<Integer, Integer> dynamicDataLength) throws ModelInvokeException {
    public List<SampleData> constructSample(String typeA, String modelId, Date runTime,String itemName,
                                            Map<Integer, Integer> dynamicDataLength) throws ModelInvokeException {
        try {
            SampleInfoConstructor sampleInfoConstructor = sampleFactory.createSampleInfo(typeA, modelId);
            SampleInfo sampleInfo = sampleInfoConstructor.prepareSampleInfo(modelId, runTime);
            if (!CollectionUtils.isEmpty(dynamicDataLength) && sampleInfo.getColumnInfo() != null) {
                 for(ColumnItemPort columnInfo : sampleInfo.getColumnInfo()) {
                     if (!dynamicDataLength.containsKey(columnInfo.getPortOrder())) {
                         continue;
                     }
                     columnInfo.setDataLength(dynamicDataLength.get(columnInfo.getPortOrder()));
                     columnInfo.getColumnItemList().forEach(columnItem -> {
                         columnItem.setDataLength(columnInfo.getDataLength());
                     });
                 }
            }
            SampleInfo sampleInfo = sampleInfoConstructor.prepareSampleInfo(modelId, runTime, dynamicDataLength);
            SampleDataConstructor sampleDataConstructor = sampleFactory.createSampleData(typeA);
            return sampleDataConstructor.prepareSampleData(sampleInfo);
        } catch (Exception e) {
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mdk/sample/SampleInfoConstructor.java
@@ -11,6 +11,7 @@
import com.iailab.module.model.mdk.sample.dto.ColumnItem;
import com.iailab.module.model.mdk.sample.dto.SampleInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import java.util.Calendar;
import java.util.Date;
@@ -36,9 +37,9 @@
     * @param predictTime
     * @return
     */
    protected SampleInfo prepareSampleInfo(String modelId, Date predictTime) {
    protected SampleInfo prepareSampleInfo(String modelId, Date predictTime, Map<Integer, Integer> dynamicDataLength) {
        //样本的列信息
        return getColumnInfo(modelId, predictTime);
        return getColumnInfo(modelId, predictTime, dynamicDataLength);
    }
    /**
@@ -57,7 +58,7 @@
     * @param predictTime
     * @return
     */
    protected abstract SampleInfo getColumnInfo(String modelId, Date predictTime);
    protected abstract SampleInfo getColumnInfo(String modelId, Date predictTime, Map<Integer, Integer> dynamicDataLength);
    /**
     * 样本的采样周期
@@ -196,4 +197,14 @@
        calendar.add(Calendar.SECOND, timeLength * granularity);
        return calendar.getTime();
    }
    protected int getDataLength(Map<Integer, Integer> dynamicDataLength, Integer port, Integer dataLength) {
        if (CollectionUtils.isEmpty(dynamicDataLength)) {
            return dataLength;
        }
        if (dynamicDataLength.containsKey(port)) {
            return dynamicDataLength.get(port);
        }
        return dataLength;
    }
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mdk/sample/ScheduleSampleInfoConstructor.java
@@ -36,7 +36,7 @@
    }
    @Override
    protected SampleInfo getColumnInfo(String modelId, Date predictTime) {
    protected SampleInfo getColumnInfo(String modelId, Date predictTime, Map<Integer, Integer> dynamicDataLength) {
        SampleInfo sampleInfo = new SampleInfo();
        List<ColumnItemPort> resultList = new ArrayList<>();
        List<ColumnItem> columnItemList = new ArrayList<>();
@@ -49,7 +49,7 @@
        //设置当前端口号,初始值为最小端口(查询结果按端口号从小到达排列)
        int curPortOrder = modelInputParamEntityList.get(0).getModelparamportorder();
        //设置当前查询数据长度,初始值为最小端口数据长度
        int curDataLength = modelInputParamEntityList.get(0).getDatalength();
        int curDataLength = super.getDataLength(dynamicDataLength, curPortOrder, modelInputParamEntityList.get(0).getDatalength());
        // 统一获取测点的信息
        Set<String> pointIds = modelInputParamEntityList.stream().filter(e -> ModelParamType.getEumByCode(e.getModelparamtype()).equals(ModelParamType.DATAPOINT)).map(StScheduleModelParamEntity::getModelparamid).collect(Collectors.toSet());
        List<ApiPointDTO> points = dataPointApi.getInfoByIds(pointIds);
@@ -62,7 +62,7 @@
        for (StScheduleModelParamEntity entry : modelInputParamEntityList) {
            columnInfo.setParamType(entry.getModelparamtype());
            columnInfo.setParamId(entry.getModelparamid());
            columnInfo.setDataLength(entry.getDatalength());
            columnInfo.setDataLength(super.getDataLength(dynamicDataLength, entry.getModelparamportorder(), entry.getDatalength()));
            columnInfo.setModelParamOrder(entry.getModelparamorder());
            columnInfo.setModelParamPortOrder(entry.getModelparamportorder());
            columnInfo.setStartTime(getStartTime(columnInfo, predictTime,pointMap,planMap));
@@ -79,7 +79,7 @@
                curPort = new ColumnItemPort(); //对象重新初始化,防止引用拷贝导致数据覆盖
                //封装上一个端口类后更新当前的各个参数
                columnItemList = new ArrayList<>();
                curDataLength = entry.getDatalength();
                curDataLength = super.getDataLength(dynamicDataLength, entry.getModelparamportorder(), entry.getDatalength());
                curPortOrder = entry.getModelparamportorder();
            }
            columnItemList.add(columnInfo);
@@ -101,4 +101,6 @@
        return null;
    }
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/controller/admin/ChartController.java
@@ -3,9 +3,7 @@
import com.iailab.framework.common.page.PageData;
import com.iailab.framework.common.pojo.CommonResult;
import com.iailab.module.model.mpk.dto.ChartDTO;
import com.iailab.module.model.mpk.dto.ChartParamDTO;
import com.iailab.module.model.mpk.entity.ChartEntity;
import com.iailab.module.model.mpk.entity.ChartParamEntity;
import com.iailab.module.model.mpk.service.ChartService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/controller/admin/ChartParamController.java
@@ -2,22 +2,14 @@
import com.iailab.framework.common.page.PageData;
import com.iailab.framework.common.pojo.CommonResult;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.object.BeanUtils;
import com.iailab.module.model.mpk.dto.ChartParamDTO;
import com.iailab.module.model.mpk.dto.MpkFileDTO;
import com.iailab.module.model.api.mcs.dto.ChartParamDTO;
import com.iailab.module.model.mpk.entity.ChartParamEntity;
import com.iailab.module.model.mpk.entity.IconEntity;
import com.iailab.module.model.mpk.service.ChartParamService;
import com.iailab.module.model.mpk.vo.IconPageReqVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import static com.iailab.framework.common.pojo.CommonResult.success;
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/controller/admin/MdkController.java
@@ -201,7 +201,7 @@
                return CommonResult.error(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),"模型运行失败!");
            }
        }
        return CommonResult.success();
        return CommonResult.success("");
    }
    @PostMapping("/import")
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/dto/ChartDTO.java
@@ -1,7 +1,7 @@
package com.iailab.module.model.mpk.dto;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.iailab.module.model.api.mcs.dto.ChartParamDTO;
import lombok.Data;
import java.io.Serializable;
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/ChartParamService.java
@@ -1,11 +1,8 @@
package com.iailab.module.model.mpk.service;
import com.iailab.framework.common.page.PageData;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.module.model.mpk.dto.ChartParamDTO;
import com.iailab.module.model.api.mcs.dto.ChartParamDTO;
import com.iailab.module.model.mpk.entity.ChartParamEntity;
import com.iailab.module.model.mpk.entity.IconEntity;
import com.iailab.module.model.mpk.vo.IconPageReqVO;
import java.util.List;
import java.util.Map;
@@ -28,4 +25,6 @@
    void delete(String id);
    Map<String, String> getByChartId(String chartId);
    List<com.iailab.module.model.api.mcs.dto.ChartParamDTO> list(String chartCode);
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/ChartParamServiceImpl.java
@@ -4,14 +4,13 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.iailab.framework.common.page.PageData;
import com.iailab.framework.common.service.impl.BaseServiceImpl;
import com.iailab.framework.common.util.object.ConvertUtils;
import com.iailab.module.model.api.mcs.dto.ChartParamDTO;
import com.iailab.module.model.mpk.dao.ChartParamDao;
import com.iailab.module.model.mpk.dto.ChartParamDTO;
import com.iailab.module.model.mpk.entity.ChartEntity;
import com.iailab.module.model.mpk.entity.ChartParamEntity;
import com.iailab.module.model.mpk.service.ChartParamService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@@ -70,6 +69,12 @@
    }
    @Override
    public List<ChartParamDTO> list(String chartCode) {
        List<ChartParamEntity> list = baseDao.selectList(new QueryWrapper<ChartParamEntity>().eq("chart_code", chartCode));
        return ConvertUtils.sourceToTarget(list,ChartParamDTO.class);
    }
    @Override
    public void delete(String id) {
        baseDao.deleteById(id);
    }
iailab-module-model/iailab-module-model-biz/src/main/resources/template/cpp.vm
@@ -22,10 +22,16 @@
        jmethodID getMID = env->GetMethodID(hashmapClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
        jstring keyJString = env->NewStringUTF("pyFile");
        jobject javaValueObj = env->CallObjectMethod(settings, getMID, keyJString);
        const char* strValue = env->GetStringUTFChars((jstring)javaValueObj, NULL);
        jstring javaStringValue = (jstring)javaValueObj;
        const char* strValue = env->GetStringUTFChars(javaStringValue, NULL);
        //*************导入模型******************
        PyObject* pModule = create_py_module(strValue);
        /*PyObject* pModule = create_py_module("${pyModule}.${pyName}");*/
        // 释放java占用内存
        env->ReleaseStringUTFChars(javaStringValue, strValue);
        env->DeleteLocalRef(javaStringValue);
        env->DeleteLocalRef(javaValueObj);
        env->DeleteLocalRef(keyJString);
        env->DeleteLocalRef(hashmapClass);
        if (pModule == NULL)
        {
            cout << "model error" << endl;
@@ -88,39 +94,17 @@
            cout << "ConvertPydictToJhmap error" << endl;
            throw "函数返回值异常!";
        }
        /*delete pModule;
        pModule = nullptr;
        delete pModule;
        delete pFunc;
        pFunc = nullptr;
        delete pFunc;
        cout << "clear_py_memory" << endl;
        Py_XDECREF(pFunc);
#{foreach} ($column in [1..$entity.dataLength])
        delete[] data_${column};
        data_${column} = nullptr;
        delete data_${column};
        Py_XDECREF(data_${column});
#{end}
#{if}($entity.model==1)
        delete model_path;
        model_path = nullptr;
        delete model_path;
        Py_XDECREF(model_path);
#{end}
        delete settings_1;
        settings_1 = nullptr;
        delete settings_1;
        delete[] pArg;
        pArg = nullptr;
        delete pArg;
        Py_DECREF(pReturn);
        Py_DECREF(pFunc);
        Py_DECREF(pModule);
        Py_CLEAR(pModule);*/
        Py_XDECREF(settings_1);
        Py_XDECREF(pArg);
        Py_XDECREF(pReturn);
        return result;
    }
    catch (const char* msg)
iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/enums/ErrorCodeConstants.java
@@ -110,6 +110,7 @@
    ErrorCode TENANT_PACKAGE_NOT_EXISTS = new ErrorCode(1_002_016_000, "租户套餐不存在");
    ErrorCode TENANT_PACKAGE_USED = new ErrorCode(1_002_016_001, "租户正在使用该套餐,请给租户重新设置套餐后再尝试删除");
    ErrorCode TENANT_PACKAGE_DISABLE = new ErrorCode(1_002_016_002, "名字为【{}】的租户套餐已被禁用");
    ErrorCode TENANT_PACKAGE_NAME_DUPLICATE = new ErrorCode(1_002_016_003, "已经存在该名字的租户套餐");
    // ========== 社交用户 1-002-018-000 ==========
    ErrorCode SOCIAL_USER_AUTH_FAILURE = new ErrorCode(1_002_018_000, "社交授权失败,原因是:{}");
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/tenant/TenantPackageMapper.java
@@ -29,4 +29,9 @@
    default List<TenantPackageDO> selectListByStatus(Integer status) {
        return selectList(TenantPackageDO::getStatus, status);
    }
    default TenantPackageDO selectByName(String name) {
        return selectOne(TenantPackageDO::getName, name);
    }
}
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantPackageServiceImpl.java
@@ -1,6 +1,8 @@
package com.iailab.module.system.service.tenant;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.annotations.VisibleForTesting;
import com.iailab.framework.common.enums.CommonStatusEnum;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.object.BeanUtils;
@@ -38,6 +40,8 @@
    @Override
    public Long createTenantPackage(TenantPackageSaveReqVO createReqVO) {
        // 校验套餐名是否重复
        validateTenantPackageNameUnique(null, createReqVO.getName());
        // 插入
        TenantPackageDO tenantPackage = BeanUtils.toBean(createReqVO, TenantPackageDO.class);
        tenantPackageMapper.insert(tenantPackage);
@@ -111,4 +115,22 @@
        return tenantPackageMapper.selectListByStatus(status);
    }
    @VisibleForTesting
    void validateTenantPackageNameUnique(Long id, String name) {
        if (StrUtil.isBlank(name)) {
            return;
        }
        TenantPackageDO tenantPackage = tenantPackageMapper.selectByName(name);
        if (tenantPackage == null) {
            return;
        }
        // 如果 id 为空,说明不用比较是否为相同 id 的用户
        if (id == null) {
            throw exception(TENANT_PACKAGE_NAME_DUPLICATE);
        }
        if (!tenantPackage.getId().equals(id)) {
            throw exception(TENANT_PACKAGE_NAME_DUPLICATE);
        }
    }
}