Ver código fonte

新增自定义流程属性-串行/并行会签

wangliang 3 anos atrás
pai
commit
ec25ea83f1
22 arquivos alterados com 1036 adições e 105 exclusões
  1. 25 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/config/ActivitiConfig.java
  2. 7 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/handler/EventHandler.java
  3. 16 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/listener/MultipleAllListener.java
  4. 158 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/listener/ProcessEventListener.java
  5. 21 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/listener/TaskCreateListener.java
  6. 67 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/service/AbstractMultiWorkFlowService.java
  7. 63 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/service/DefaultInstanceConvertToMultiInstance.java
  8. 21 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/service/MultiWorkFlow.java
  9. 21 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/service/impl/MultiWorkFlowService.java
  10. 1 1
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/flow/CustomFlowApproveRoleDto.java
  11. 1 1
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/flow/CustomFlowApproveUserDto.java
  12. 1 1
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/flow/CustomFlowCopyUserDto.java
  13. 11 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/flow/CustomFlowDto.java
  14. 281 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/flow/link/FlowTaskLink.java
  15. 68 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/flow/link/FlowTaskNode.java
  16. 8 0
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/ActivitiService.java
  17. 214 102
      distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/ActivitiServiceImpl.java
  18. 7 0
      distributed-print/src/main/java/com/qmth/distributed/print/api/TFCustomFlowController.java
  19. 8 0
      teachcloud-common/src/main/java/com/qmth/teachcloud/common/mapper/SysUserMapper.java
  20. 8 0
      teachcloud-common/src/main/java/com/qmth/teachcloud/common/service/SysUserService.java
  21. 11 0
      teachcloud-common/src/main/java/com/qmth/teachcloud/common/service/impl/SysUserServiceImpl.java
  22. 18 0
      teachcloud-common/src/main/resources/mapper/SysUserMapper.xml

+ 25 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/config/ActivitiConfig.java

@@ -0,0 +1,25 @@
+package com.qmth.distributed.print.business.activiti.custom.config;
+
+import com.qmth.distributed.print.business.activiti.custom.listener.ProcessEventListener;
+import org.activiti.engine.delegate.event.ActivitiEventListener;
+import org.activiti.spring.SpringProcessEngineConfiguration;
+import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class ActivitiConfig implements ProcessEngineConfigurationConfigurer {
+
+    @Resource
+    ProcessEventListener processEventListener;
+
+    @Override
+    public void configure(SpringProcessEngineConfiguration springProcessEngineConfiguration) {
+        List<ActivitiEventListener> activitiEventListener = new ArrayList<ActivitiEventListener>();
+        activitiEventListener.add(processEventListener);//配置全局监听器
+        springProcessEngineConfiguration.setEventListeners(activitiEventListener);
+    }
+}

+ 7 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/handler/EventHandler.java

@@ -0,0 +1,7 @@
+package com.qmth.distributed.print.business.activiti.custom.handler;
+
+import org.activiti.engine.delegate.event.ActivitiEvent;
+
+public interface EventHandler {
+    void handle(ActivitiEvent event);
+}

+ 16 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/listener/MultipleAllListener.java

@@ -0,0 +1,16 @@
+package com.qmth.distributed.print.business.activiti.custom.listener;
+
+import org.activiti.engine.delegate.DelegateTask;
+import org.activiti.engine.delegate.TaskListener;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+@Component
+public class MultipleAllListener implements TaskListener {
+
+    @Override
+    public void notify(DelegateTask delegateTask) {
+        List<String> userIdList = (List<String>) delegateTask.getVariable("ALL");
+        delegateTask.setVariable("ALL", userIdList);
+    }
+}

+ 158 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/listener/ProcessEventListener.java

@@ -0,0 +1,158 @@
+package com.qmth.distributed.print.business.activiti.custom.listener;
+
+import com.qmth.distributed.print.business.activiti.custom.handler.EventHandler;
+import org.activiti.engine.delegate.event.ActivitiEvent;
+import org.activiti.engine.delegate.event.ActivitiEventListener;
+import org.activiti.engine.delegate.event.ActivitiEventType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+@Component
+public class ProcessEventListener implements ActivitiEventListener, Serializable {
+    private final static Logger log = LoggerFactory.getLogger(ProcessEventListener.class);
+
+    /**
+     * 各类 Event 的处理器
+     */
+    private Map<ActivitiEventType, EventHandler> handlers = new HashMap<ActivitiEventType, EventHandler>();
+
+    //简单的完成一下监听器的效果
+    @Override
+    public void onEvent(ActivitiEvent event) {
+        EventHandler eventHandler = handlers.get(event.getType());
+        if (Objects.nonNull(eventHandler)) {
+            eventHandler.handle(event);
+        }
+        switch (event.getType()) {
+            case PROCESS_STARTED:
+                // 流程启动
+                log.info("流程启动_PROCESS_STARTED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+                break;
+            case PROCESS_COMPLETED:
+                // 流程结束
+                log.info("流程结束_PROCESS_COMPLETED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+                break;
+//            case ACTIVITY_COMPENSATE:
+//                // 一个节点将要被补偿。事件包含了将要执行补偿的节点id。
+//                log.info("ACTIVITY_COMPENSATE:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+            case ACTIVITY_COMPLETED:
+                // 一个节点成功结束
+                log.info("ACTIVITY_COMPLETED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+                break;
+//            case ACTIVITY_ERROR_RECEIVED:
+//                // 一个节点收到了一个错误事件。在节点实际处理错误之前触发。 事件的activityId对应着处理错误的节点。 这个事件后续会是ACTIVITY_SIGNALLED或ACTIVITY_COMPLETE, 如果错误发送成功的话。
+//                log.info("ACTIVITY_ERROR_RECEIVED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case ACTIVITY_MESSAGE_RECEIVED:
+//                // 一个节点收到了一个消息。在节点收到消息之前触发。收到后,会触发ACTIVITY_SIGNAL或ACTIVITY_STARTED,这会根据节点的类型(边界事件,事件子流程开始事件)
+//                log.info("ACTIVITY_MESSAGE_RECEIVED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case ACTIVITY_SIGNALED:
+//                // 一个节点收到了一个信号
+//                log.info("ACTIVITY_SIGNALED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case ACTIVITY_STARTED:
+//                // 一个节点开始执行
+//                log.info("ACTIVITY_STARTED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case CUSTOM:
+//                log.info("CUSTOM:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case ENGINE_CLOSED:
+//                // 监听器监听的流程引擎已经关闭,不再接受API调用。
+//                log.info("ENGINE_CLOSED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case ENGINE_CREATED:
+//                // 监听器监听的流程引擎已经创建完毕,并准备好接受API调用。
+//                log.info("ENGINE_CREATED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case ENTITY_ACTIVATED:
+//                // 激活了已存在的实体,实体包含在事件中。会被ProcessDefinitions, ProcessInstances 和 Tasks抛出。
+//                log.info("ENTITY_ACTIVATED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case ENTITY_CREATED:
+//                // 创建了一个新实体。实体包含在事件中。
+//                log.info("ENTITY_CREATED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case ENTITY_DELETED:
+//                // 删除了已存在的实体。实体包含在事件中
+//                log.info("ENTITY_DELETED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case ENTITY_INITIALIZED:
+//                // 创建了一个新实体,初始化也完成了。如果这个实体的创建会包含子实体的创建,这个事件会在子实体都创建/初始化完成后被触发,这是与ENTITY_CREATED的区别。
+//                log.info("ENTITY_INITIALIZED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case ENTITY_SUSPENDED:
+//                // 暂停了已存在的实体。实体包含在事件中。会被ProcessDefinitions, ProcessInstances 和 Tasks抛出。
+//                log.info("ENTITY_SUSPENDED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case ENTITY_UPDATED:
+//                // 更新了已存在的实体。实体包含在事件中。
+//                log.info("ENTITY_UPDATED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case JOB_EXECUTION_FAILURE:
+//                // 作业执行失败。作业和异常信息包含在事件中。
+//                log.info("JOB_EXECUTION_FAILURE:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case JOB_EXECUTION_SUCCESS:
+//                // 作业执行成功。job包含在事件中。
+//                log.info("JOB_EXECUTION_SUCCESS:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case JOB_RETRIES_DECREMENTED:
+//                // 因为作业执行失败,导致重试次数减少。作业包含在事件中。
+//                log.info("JOB_RETRIES_DECREMENTED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case MEMBERSHIPS_DELETED:
+//                // 所有成员被从一个组中删除。在成员删除之前触发这个事件,所以他们都是可以访问的。 因为性能方面的考虑,不会为每个成员触发单独的MEMBERSHIP_DELETED事件。
+//                log.info("MEMBERSHIPS_DELETED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case MEMBERSHIP_CREATED:
+//                // 用户被添加到一个组里。事件包含了用户和组的id。
+//                log.info("MEMBERSHIP_CREATED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case MEMBERSHIP_DELETED:
+//                // 用户被从一个组中删除。事件包含了用户和组的id。
+//                log.info("MEMBERSHIP_DELETED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case TASK_ASSIGNED:
+//                // 任务被分配给了一个人员。事件包含任务。
+//                log.info("TASK_ASSIGNED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+            case TASK_COMPLETED:
+                // 任务被完成了。它会在ENTITY_DELETE事件之前触发。当任务是流程一部分时,事件会在流程继续运行之前, 后续事件将是ACTIVITY_COMPLETE,对应着完成任务的节点。
+                log.info("TASK_COMPLETED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+                break;
+//            case TIMER_FIRED:
+//                // 触发了定时器。job包含在事件中。
+//                log.info("TIMER_FIRED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case UNCAUGHT_BPMN_ERROR:
+//                log.info("UNCAUGHT_BPMN_ERROR:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case VARIABLE_CREATED:
+//                log.info("VARIABLE_CREATED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case VARIABLE_DELETED:
+//                log.info("VARIABLE_DELETED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+//            case VARIABLE_UPDATED:
+//                log.info("VARIABLE_UPDATED:ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+//                break;
+            default:
+                log.info("default->type:{},ProcessInstanceId:{},ExecutionId:{},ProcessDefinitionId:{}", event.getType(), event.getProcessInstanceId(), event.getExecutionId(), event.getProcessDefinitionId());
+                break;
+        }
+    }
+
+    @Override
+    public boolean isFailOnException() {
+        return false;
+    }
+}

+ 21 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/listener/TaskCreateListener.java

@@ -0,0 +1,21 @@
+package com.qmth.distributed.print.business.activiti.custom.listener;
+
+import com.qmth.distributed.print.business.activiti.custom.handler.EventHandler;
+import org.activiti.engine.delegate.event.ActivitiEvent;
+import org.activiti.engine.delegate.event.impl.ActivitiEntityEventImpl;
+import org.activiti.engine.impl.persistence.entity.TaskEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class TaskCreateListener implements EventHandler {
+    private final static Logger log = LoggerFactory.getLogger(TaskCreateListener.class);
+
+    @Override
+    public void handle(ActivitiEvent event) {
+        ActivitiEntityEventImpl eventImpl = (ActivitiEntityEventImpl) event;
+        TaskEntity taskEntity = (TaskEntity) eventImpl.getEntity();
+    }
+}
+

+ 67 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/service/AbstractMultiWorkFlowService.java

@@ -0,0 +1,67 @@
+package com.qmth.distributed.print.business.activiti.custom.service;
+
+import org.activiti.bpmn.model.MultiInstanceLoopCharacteristics;
+import org.activiti.bpmn.model.UserTask;
+import org.activiti.engine.ProcessEngine;
+import org.activiti.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
+import org.activiti.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
+import org.activiti.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
+import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
+import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
+import org.activiti.engine.impl.el.ExpressionManager;
+
+import javax.annotation.Resource;
+import java.text.MessageFormat;
+import java.util.StringJoiner;
+
+/**
+ * @Description: 并行/串行业务service
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2022/2/8
+ */
+public abstract class AbstractMultiWorkFlowService implements DefaultInstanceConvertToMultiInstance {
+
+    @Resource
+    protected ProcessEngine processEngine;
+
+    @Override
+    public MultiInstanceLoopCharacteristics createMultiInstanceLoopCharacteristics(String id, boolean isSequential) {
+        return createMultiInstanceLoopCharacteristics(isSequential, new StringJoiner("")
+                .add(MessageFormat.format("{0}{1}{2}", DEFAULT_ASSIGNEE_LIST_EXP, id, EXP_SUFFIX)).toString(), new StringJoiner("")
+                .add(MessageFormat.format("{0}{1}", ASSIGNEE_USER, id)).toString());
+    }
+
+    @Override
+    public MultiInstanceLoopCharacteristics createMultiInstanceLoopCharacteristics(boolean isSequential, String assigneeListExp, String assignee) {
+        MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = new MultiInstanceLoopCharacteristics();
+        multiInstanceLoopCharacteristics.setSequential(isSequential);
+        multiInstanceLoopCharacteristics.setInputDataItem(assigneeListExp);
+        multiInstanceLoopCharacteristics.setElementVariable(assignee);
+        return multiInstanceLoopCharacteristics;
+    }
+
+    @Override
+    public UserTask createMultiInstanceBehavior(UserTask userTask, boolean sequential) {
+        String id = userTask.getId().substring(userTask.getId().length() - 1, userTask.getId().length());
+        MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = this.createMultiInstanceLoopCharacteristics(id, sequential);
+        userTask.setAssignee(new StringJoiner("").add(MessageFormat.format("{0}{1}{2}", ASSIGNEE_USER_EXP, id, EXP_SUFFIX)).toString());
+        userTask.setLoopCharacteristics(multiInstanceLoopCharacteristics);
+        return createMultiInstanceBehavior(userTask, sequential, new StringJoiner("").add(MessageFormat.format("{0}{1}{2}", DEFAULT_ASSIGNEE_LIST_EXP, id, EXP_SUFFIX)).toString(), new StringJoiner("").add(MessageFormat.format("{0}{1}", ASSIGNEE_USER, id)).toString());
+    }
+
+    @Override
+    public UserTask createMultiInstanceBehavior(UserTask userTask, boolean sequential, String assigneeListExp, String assignee) {
+        ProcessEngineConfigurationImpl processEngineConfiguration = (ProcessEngineConfigurationImpl) processEngine.getProcessEngineConfiguration();
+        //创建解释器
+        UserTaskActivityBehavior userTaskActivityBehavior = processEngineConfiguration.getActivityBehaviorFactory().createUserTaskActivityBehavior(userTask);
+        MultiInstanceActivityBehavior behavior = sequential ? new SequentialMultiInstanceBehavior(userTask, userTaskActivityBehavior) : new ParallelMultiInstanceBehavior(userTask, userTaskActivityBehavior);
+        //注入表达式 解释器
+        ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager();
+        //设置表达式变量
+        behavior.setCollectionExpression(expressionManager.createExpression(assigneeListExp));
+        behavior.setCollectionElementVariable(assignee);
+        return userTask;
+    }
+}

+ 63 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/service/DefaultInstanceConvertToMultiInstance.java

@@ -0,0 +1,63 @@
+package com.qmth.distributed.print.business.activiti.custom.service;
+
+import org.activiti.bpmn.model.MultiInstanceLoopCharacteristics;
+import org.activiti.bpmn.model.UserTask;
+
+/**
+ * @Description: 创建并行/串行业务
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2022/2/8
+ */
+public interface DefaultInstanceConvertToMultiInstance extends MultiWorkFlow {
+
+    public static final String ASSIGNEE_USER = "assignee";
+
+    public static final String DEFAULT_ASSIGNEE_LIST = "assigneeList";
+
+    public static final String DEFAULT_ASSIGNEE_LIST_EXP = "${assigneeList";
+
+    public static final String ASSIGNEE_USER_EXP = "${" + ASSIGNEE_USER;
+
+    public static final String EXP_SUFFIX = "}";
+
+    /**
+     * 创建 多实例 行为解释器
+     *
+     * @param userTask
+     * @param sequential
+     * @return
+     */
+    UserTask createMultiInstanceBehavior(UserTask userTask, boolean sequential);
+
+    /**
+     * 创建多实例行为解释器
+     *
+     * @param userTask        流程节点
+     * @param sequential      是否串行
+     * @param assigneeListExp 用户组表达
+     * @param assigneeExp     用户标识
+     * @return
+     */
+    UserTask createMultiInstanceBehavior(UserTask userTask, boolean sequential, String assigneeListExp, String assigneeExp);
+
+    /**
+     * 创建多实例 循环解释器
+     *
+     * @param isSequential    是否串行
+     * @param assigneeListExp 用户组表达
+     * @param assignee        用户标识
+     * @return
+     */
+    MultiInstanceLoopCharacteristics createMultiInstanceLoopCharacteristics(boolean isSequential, String assigneeListExp, String assignee);
+
+    /**
+     * 创建多实例 循环解释器
+     *
+     * @param id
+     * @param isSequential 是否 串行
+     * @return
+     */
+    MultiInstanceLoopCharacteristics createMultiInstanceLoopCharacteristics(String id, boolean isSequential);
+}

+ 21 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/service/MultiWorkFlow.java

@@ -0,0 +1,21 @@
+package com.qmth.distributed.print.business.activiti.custom.service;
+
+import org.activiti.bpmn.model.UserTask;
+
+/**
+ * @Description: 并行/串行业务
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2022/2/8
+ */
+public interface MultiWorkFlow {
+
+    /**
+     * 创建并行/串行会签节点
+     *
+     * @param userTask
+     * @param sequential
+     */
+    UserTask createMultiInstanceLoopCharacteristics(UserTask userTask, boolean sequential);
+}

+ 21 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/activiti/custom/service/impl/MultiWorkFlowService.java

@@ -0,0 +1,21 @@
+package com.qmth.distributed.print.business.activiti.custom.service.impl;
+
+import com.qmth.distributed.print.business.activiti.custom.service.AbstractMultiWorkFlowService;
+import org.activiti.bpmn.model.UserTask;
+import org.springframework.stereotype.Service;
+
+/**
+ * @Description: 并行/串行业务impl
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2022/2/8
+ */
+@Service
+public class MultiWorkFlowService extends AbstractMultiWorkFlowService {
+
+    @Override
+    public UserTask createMultiInstanceLoopCharacteristics(UserTask userTask, boolean sequential) {
+        return createMultiInstanceBehavior(userTask, sequential);
+    }
+}

+ 1 - 1
distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/flow/CustomFlowApproveRoleDto.java

@@ -19,7 +19,7 @@ public class CustomFlowApproveRoleDto implements Serializable {
     @JsonSerialize(using = ToStringSerializer.class)
     Long id;
 
-    @ApiModelProperty(value = "id")
+    @ApiModelProperty(value = "角色名称")
     String name;
 
     public Long getId() {

+ 1 - 1
distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/flow/CustomFlowApproveUserDto.java

@@ -19,7 +19,7 @@ public class CustomFlowApproveUserDto implements Serializable {
     @JsonSerialize(using = ToStringSerializer.class)
     Long id;
 
-    @ApiModelProperty(value = "id")
+    @ApiModelProperty(value = "用户名称")
     String name;
 
     public Long getId() {

+ 1 - 1
distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/flow/CustomFlowCopyUserDto.java

@@ -19,7 +19,7 @@ public class CustomFlowCopyUserDto implements Serializable {
     @JsonSerialize(using = ToStringSerializer.class)
     Long id;
 
-    @ApiModelProperty(value = "id")
+    @ApiModelProperty(value = "用户名称")
     String name;
 
     public Long getId() {

+ 11 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/flow/CustomFlowDto.java

@@ -42,6 +42,17 @@ public class CustomFlowDto implements Serializable {
     @ApiModelProperty(value = "属性")
     CustomFlowPropertyDto property;
 
+    @ApiModelProperty(value = "流程属性id")
+    String flowTaskId;
+
+    public String getFlowTaskId() {
+        return flowTaskId;
+    }
+
+    public void setFlowTaskId(String flowTaskId) {
+        this.flowTaskId = flowTaskId;
+    }
+
     public String getId() {
         return id;
     }

+ 281 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/flow/link/FlowTaskLink.java

@@ -0,0 +1,281 @@
+package com.qmth.distributed.print.business.bean.flow.link;
+
+import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * @Description: 流程节点链表
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2022/1/26
+ */
+public class FlowTaskLink implements Serializable {
+
+    @ApiModelProperty(name = "头节点")
+    FlowTaskNode first;
+
+    @ApiModelProperty(name = "尾节点")
+    FlowTaskNode last;
+
+    @ApiModelProperty(name = "长度")
+    volatile int size;
+
+    public FlowTaskLink() {
+        this.size = 0;
+    }
+
+    public FlowTaskLink(FlowTaskNode first) {
+        this.size = 1;
+        this.first = first;
+    }
+
+    /**
+     * 增加头节点
+     *
+     * @param first
+     * @return
+     */
+    public boolean addFirst(FlowTaskNode first) {
+        first.next = this.first;
+        this.first.before = first;
+        this.first = first;
+        this.size++;
+        return true;
+    }
+
+    /**
+     * 增加尾节点
+     *
+     * @param last
+     * @return
+     */
+    public boolean addLast(FlowTaskNode last) {
+        this.last.next = last;
+        last.before = this.last;
+        this.last = last;
+        this.size++;
+        return true;
+    }
+
+    /**
+     * 新增节点
+     *
+     * @param node
+     * @return
+     */
+    public boolean add(FlowTaskNode node) {
+        if (Objects.isNull(this.first)) {
+            this.first = node;
+        } else {
+            FlowTaskNode flowTaskNode = this.first.next;
+            if (Objects.isNull(flowTaskNode)) {
+                this.first.next = node;
+                node.before = this.first;
+            } else {
+                boolean next = true;
+                while (next) {
+                    if (Objects.nonNull(flowTaskNode.next)) {
+                        flowTaskNode = flowTaskNode.next;
+                    } else {
+                        flowTaskNode.next = node;
+                        node.before = flowTaskNode;
+                        next = false;
+                    }
+                }
+            }
+            this.last = node;
+        }
+        this.size++;
+        return true;
+    }
+
+    /**
+     * 删除节点
+     *
+     * @param node
+     * @return
+     */
+    public boolean remove(FlowTaskNode node) {
+        if (this.size == 0) {
+            throw ExceptionResultEnum.ERROR.exception("没有数据");
+        } else if (this.first.getTask().getType() == node.getTask().getType() &&
+                Objects.equals(this.first.getTask().getId(), node.getTask().getId())) {
+            if (Objects.nonNull(this.first.next)) {
+                this.first = this.first.next;
+                this.first.before = null;
+            } else {
+                this.first = null;
+            }
+        } else if (this.last.getTask().getType() == node.getTask().getType() &&
+                Objects.equals(this.last.getTask().getId(), node.getTask().getId())) {
+            if (Objects.nonNull(this.last.before)) {
+                this.last = this.last.before;
+                this.last.next = null;
+            }
+        } else {
+            FlowTaskNode flowTaskNode = this.first.next;
+            boolean next = true;
+            while (next) {
+                if (flowTaskNode.getTask().getType() == node.getTask().getType() &&
+                        Objects.equals(flowTaskNode.getTask().getId(), node.getTask().getId())) {
+                    flowTaskNode.before.next = flowTaskNode.next;
+                    flowTaskNode.next.before = flowTaskNode.before;
+                    next = false;
+                }
+            }
+        }
+        this.size--;
+        return true;
+    }
+
+    /**
+     * 删除节点
+     *
+     * @param index
+     * @return
+     */
+    public boolean remove(int index) {
+        if (index < 0) {
+            throw ExceptionResultEnum.ERROR.exception("index必须大于0");
+        } else if (index >= this.size) {
+            throw ExceptionResultEnum.ERROR.exception("index已超过链表最大值");
+        } else if (index == 0) {
+            if (Objects.nonNull(this.first.next)) {
+                this.first = this.first.next;
+                this.first.before = null;
+            } else {
+                this.first = null;
+            }
+        } else if (index == this.size - 1) {
+            if (Objects.nonNull(this.last.before)) {
+                this.last = this.last.before;
+                this.last.next = null;
+            }
+        } else {
+            FlowTaskNode flowTaskNode = this.first.next;
+            for (int i = 1; i < this.size; i++) {
+                if (i == index) {
+                    flowTaskNode.before.next = flowTaskNode.next;
+                    flowTaskNode.next.before = flowTaskNode.before;
+                    break;
+                }
+                flowTaskNode = flowTaskNode.next;
+            }
+        }
+        this.size--;
+        return true;
+    }
+
+    /**
+     * 根据下标新增节点
+     *
+     * @param index
+     * @param node
+     * @return
+     */
+    public boolean add(int index, FlowTaskNode node) {
+        if (index < 0) {
+            throw ExceptionResultEnum.ERROR.exception("index必须大于0");
+        } else if (index > this.size) {
+            throw ExceptionResultEnum.ERROR.exception("index已超过链表最大值");
+        } else if (index == 0) {
+            node.next = this.first;
+            this.first.before = node;
+            this.first = node;
+        } else {
+            if (this.size == index) {
+                this.last.next = node;
+                node.before = this.last;
+                this.last = node;
+            } else {
+                FlowTaskNode flowTaskNode = first.next;
+                for (int i = 1; i < this.size; i++) {
+                    if (i == index) {
+                        node.before = flowTaskNode.before;
+                        node.next = flowTaskNode;
+                        flowTaskNode.before.next = node;
+                        flowTaskNode.before = node;
+                        break;
+                    }
+                    flowTaskNode = flowTaskNode.next;
+                }
+            }
+        }
+        this.size++;
+        return true;
+    }
+
+    /**
+     * 是否为空
+     *
+     * @return
+     */
+    public boolean isEmpty() {
+        return this.size == 0;
+    }
+
+    /**
+     * 根据下标获取
+     *
+     * @param index
+     * @return
+     */
+    public FlowTaskNode get(int index) {
+        FlowTaskNode flowTaskNode = null;
+        if (index < 0) {
+            throw ExceptionResultEnum.ERROR.exception("index必须大于0");
+        } else if (index > this.size) {
+            throw ExceptionResultEnum.ERROR.exception("index已超过链表最大值");
+        } else if (index == 0) {
+            flowTaskNode = this.first;
+        } else if (index == this.size - 1) {
+            flowTaskNode = this.last;
+        } else {
+            flowTaskNode = first.next;
+            for (int i = 1; i < this.size; i++) {
+                if (i == index) {
+                    break;
+                }
+                flowTaskNode = flowTaskNode.next;
+            }
+        }
+        return flowTaskNode;
+    }
+
+    /**
+     * 大小
+     *
+     * @return
+     */
+    public int size() {
+        return this.size <= 0 ? 0 : this.size;
+    }
+
+    public FlowTaskNode getFirst() {
+        return first;
+    }
+
+    public void setFirst(FlowTaskNode first) {
+        this.first = first;
+    }
+
+    public FlowTaskNode getLast() {
+        return last;
+    }
+
+    public void setLast(FlowTaskNode last) {
+        this.last = last;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public void setSize(int size) {
+        this.size = size;
+    }
+}

+ 68 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/bean/flow/link/FlowTaskNode.java

@@ -0,0 +1,68 @@
+package com.qmth.distributed.print.business.bean.flow.link;
+
+import com.qmth.distributed.print.business.bean.flow.CustomFlowDto;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+/**
+ * @Description: 流程节点链表元素
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2022/1/26
+ */
+public class FlowTaskNode implements Serializable {
+
+    @ApiModelProperty(name = "节点")
+    CustomFlowDto task;
+
+    @ApiModelProperty(name = "上一个节点")
+    FlowTaskNode next;
+
+    @ApiModelProperty(name = "下一个节点")
+    FlowTaskNode before;
+
+    public FlowTaskNode() {
+
+    }
+
+    public FlowTaskNode(CustomFlowDto task) {
+        this.task = task;
+    }
+
+    public FlowTaskNode(CustomFlowDto task, FlowTaskNode next) {
+        this.task = task;
+        this.next = next;
+    }
+
+    public FlowTaskNode(CustomFlowDto task, FlowTaskNode next, FlowTaskNode before) {
+        this.task = task;
+        this.next = next;
+        this.before = before;
+    }
+
+    public CustomFlowDto getTask() {
+        return task;
+    }
+
+    public void setTask(CustomFlowDto task) {
+        this.task = task;
+    }
+
+    public FlowTaskNode getNext() {
+        return next;
+    }
+
+    public void setNext(FlowTaskNode next) {
+        this.next = next;
+    }
+
+    public FlowTaskNode getBefore() {
+        return before;
+    }
+
+    public void setBefore(FlowTaskNode before) {
+        this.before = before;
+    }
+}

+ 8 - 0
distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/ActivitiService.java

@@ -8,6 +8,7 @@ import com.qmth.teachcloud.common.bean.params.ApproveUserResult;
 import org.activiti.engine.runtime.ProcessInstance;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
@@ -35,6 +36,13 @@ public interface ActivitiService {
      */
     void uploadDeployment(MultipartFile file) throws IOException;
 
+    /**
+     * 上传流程文件
+     *
+     * @param file
+     */
+    void uploadDeployment(File file) throws IOException;
+
     /**
      * 启动流程
      *

+ 214 - 102
distributed-print-business/src/main/java/com/qmth/distributed/print/business/service/impl/ActivitiServiceImpl.java

@@ -3,14 +3,16 @@ package com.qmth.distributed.print.business.service.impl;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.qmth.boot.api.exception.ApiException;
+import com.qmth.distributed.print.business.activiti.custom.service.DefaultInstanceConvertToMultiInstance;
+import com.qmth.distributed.print.business.activiti.custom.service.MultiWorkFlow;
 import com.qmth.distributed.print.business.bean.flow.CustomFlowDto;
+import com.qmth.distributed.print.business.bean.flow.CustomFlowPropertyDto;
 import com.qmth.distributed.print.business.bean.flow.CustomFlowSaveDto;
+import com.qmth.distributed.print.business.bean.flow.link.FlowTaskLink;
+import com.qmth.distributed.print.business.bean.flow.link.FlowTaskNode;
 import com.qmth.distributed.print.business.bean.result.*;
 import com.qmth.distributed.print.business.entity.*;
-import com.qmth.distributed.print.business.enums.CustomFlowDynamicBuildEnum;
-import com.qmth.distributed.print.business.enums.CustomFlowTypeEnum;
-import com.qmth.distributed.print.business.enums.ExamStatusEnum;
-import com.qmth.distributed.print.business.enums.FlowModelEnum;
+import com.qmth.distributed.print.business.enums.*;
 import com.qmth.distributed.print.business.service.*;
 import com.qmth.teachcloud.common.bean.params.ApproveUserResult;
 import com.qmth.teachcloud.common.contant.SystemConstant;
@@ -18,7 +20,6 @@ import com.qmth.teachcloud.common.entity.BasicSchool;
 import com.qmth.teachcloud.common.entity.SysOrg;
 import com.qmth.teachcloud.common.entity.SysUser;
 import com.qmth.teachcloud.common.enums.*;
-import com.qmth.teachcloud.common.service.CommonCacheService;
 import com.qmth.teachcloud.common.service.SysOrgService;
 import com.qmth.teachcloud.common.service.SysUserService;
 import com.qmth.teachcloud.common.util.RedisUtil;
@@ -46,6 +47,7 @@ import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.*;
@@ -108,7 +110,7 @@ public class ActivitiServiceImpl implements ActivitiService {
     TExamTaskFlowService tExamTaskFlowService;
 
     @Resource
-    CommonCacheService commonCacheService;
+    MultiWorkFlow multiWorkFlow;
 
     /**
      * 注册流程
@@ -134,6 +136,18 @@ public class ActivitiServiceImpl implements ActivitiService {
         builder.deploy();
     }
 
+    /**
+     * 上传流程文件
+     *
+     * @param file
+     */
+    @Override
+    public void uploadDeployment(File file) throws IOException {
+        DeploymentBuilder builder = repositoryService.createDeployment();
+        builder.addInputStream(file.getName(), new FileInputStream(file));
+        builder.deploy();
+    }
+
     /**
      * 根据流程key开启一个流程
      *
@@ -326,6 +340,9 @@ public class ActivitiServiceImpl implements ActivitiService {
 
             //流程审批
             TFFlowApprove tfFlowApprove = tfFlowApproveService.findByFlowId(SystemConstant.convertIdToLong(processInstanceId), examTask.getSchoolId());
+            if (Objects.isNull(tfFlowApprove)) {
+                tfFlowApprove = new TFFlowApprove(sysUser.getSchoolId(), sysUser.getOrgId(), SystemConstant.convertIdToLong(processInstanceId), sysUser.getId(), FlowStatusEnum.END, sysUser.getId(), FlowModelEnum.SYSTEM);
+            }
 
             //流程审批记录
             TFFlowApproveLog tfFlowApproveLog = tfFlowApproveLogService.findByFlowId(SystemConstant.convertIdToLong(processInstanceId));
@@ -341,31 +358,36 @@ public class ActivitiServiceImpl implements ActivitiService {
                 if (Objects.isNull(tfFlowApproveLog)) {
                     tfFlowApproveLog = new TFFlowApproveLog(sysUser.getSchoolId(), sysUser.getOrgId(), SystemConstant.convertIdToLong(processInstanceId), examTask.getId(), sysUser.getId(), sysUser.getId());
                 }
-                //广东医科大学流程
-                if (Objects.nonNull(processDefinitionEntity) && (processDefinitionEntity.getKey().contains(SystemConstant.GDYKDX_FLOW_KEY)
-                        || processDefinitionEntity.getKey().contains(SystemConstant.GDYKDX_SUB_FLOW_KEY))) {
-                    if (setupEnum == FlowApproveSetupEnum.SUBMIT) {//命题提交
-                        this.assignSubmit(task, sysUser, tfFlowApprove, tfFlowLog, map);
-                    } else if (setupEnum == FlowApproveSetupEnum.PRIMARY_APPROVE) {//主任提交
-                        this.directorApprove(task, sysUser, tfFlowApprove, tfFlowApproveLog, tfFlowLog, remark, processDefinitionEntity, map);
-                    } else if (setupEnum == FlowApproveSetupEnum.SECOND_APPROVE) {//院长提交
-                        this.presidentApprove(task, sysUser, tfFlowApprove, tfFlowApproveLog, tfFlowLog, remark, map, objectMap, processInstanceId);
-                    } else if (setupEnum == FlowApproveSetupEnum.THREE_APPROVE) {//命题提交
-                        this.teacherApprove(task, sysUser, tfFlowApprove, tfFlowApproveLog, tfFlowLog, remark, map);
-                    } else if (setupEnum == FlowApproveSetupEnum.FOUR_APPROVE) {//印刷员提交
-                        this.printApprove(task, sysUser, tfFlowApprove, tfFlowApproveLog, tfFlowLog, remark, map);
-                    }
-                }//江西中医药大学
-                else if (Objects.nonNull(processDefinitionEntity) && processDefinitionEntity.getKey().contains(SystemConstant.JXZYY_FLOW_KEY)) {
-                    if (setupEnum == FlowApproveSetupEnum.SUBMIT) {//命题提交
-                        this.assignSubmit(task, sysUser, tfFlowApprove, tfFlowLog, map);
-                    } else if (setupEnum == FlowApproveSetupEnum.PRIMARY_APPROVE) {//主任提交
-                        this.directorApprove(task, sysUser, tfFlowApprove, tfFlowApproveLog, tfFlowLog, remark, processDefinitionEntity, map);
-                    }
-                } else {
-                    throw ExceptionResultEnum.ERROR.exception("未配置流程学校code");
-                }
+//                //广东医科大学流程
+//                if (Objects.nonNull(processDefinitionEntity) && (processDefinitionEntity.getKey().contains(SystemConstant.GDYKDX_FLOW_KEY)
+//                        || processDefinitionEntity.getKey().contains(SystemConstant.GDYKDX_SUB_FLOW_KEY))) {
+//                    if (setupEnum == FlowApproveSetupEnum.SUBMIT) {//命题提交
+//                        this.assignSubmit(task, sysUser, tfFlowApprove, tfFlowLog, map);
+//                    } else if (setupEnum == FlowApproveSetupEnum.PRIMARY_APPROVE) {//主任提交
+//                        this.directorApprove(task, sysUser, tfFlowApprove, tfFlowApproveLog, tfFlowLog, remark, processDefinitionEntity, map);
+//                    } else if (setupEnum == FlowApproveSetupEnum.SECOND_APPROVE) {//院长提交
+//                        this.presidentApprove(task, sysUser, tfFlowApprove, tfFlowApproveLog, tfFlowLog, remark, map, objectMap, processInstanceId);
+//                    } else if (setupEnum == FlowApproveSetupEnum.THREE_APPROVE) {//命题提交
+//                        this.teacherApprove(task, sysUser, tfFlowApprove, tfFlowApproveLog, tfFlowLog, remark, map);
+//                    } else if (setupEnum == FlowApproveSetupEnum.FOUR_APPROVE) {//印刷员提交
+//                        this.printApprove(task, sysUser, tfFlowApprove, tfFlowApproveLog, tfFlowLog, remark, map);
+//                    }
+//                }//江西中医药大学
+//                else if (Objects.nonNull(processDefinitionEntity) && processDefinitionEntity.getKey().contains(SystemConstant.JXZYY_FLOW_KEY)) {
+//                    if (setupEnum == FlowApproveSetupEnum.SUBMIT) {//命题提交
+//                        this.assignSubmit(task, sysUser, tfFlowApprove, tfFlowLog, map);
+//                    } else if (setupEnum == FlowApproveSetupEnum.PRIMARY_APPROVE) {//主任提交
+//                        this.directorApprove(task, sysUser, tfFlowApprove, tfFlowApproveLog, tfFlowLog, remark, processDefinitionEntity, map);
+//                    }
+//                } else {
+//                    throw ExceptionResultEnum.ERROR.exception("未配置流程学校code");
+//                }
             }
+            tfFlowApprove.setStatus(FlowStatusEnum.AUDITING);
+            tfFlowApprove.setSetup(FlowApproveSetupEnum.PRIMARY_APPROVE.getSetup());
+            tfFlowLog.setApproveSetup(FlowApproveSetupEnum.PRIMARY_APPROVE.getSetup());
+            tfFlowLog.setApproveOperation(FlowApproveOperationEnum.SUBMIT);
+
             tfFlowApprove.updateInfo(sysUser.getId());
             tfFlowApproveLog.updateInfo(sysUser.getId());
             tfFlowApprove.setApproveId(sysUser.getId());
@@ -1413,112 +1435,118 @@ public class ActivitiServiceImpl implements ActivitiService {
     @Override
     @Transactional
     public void dynamicBuildBpmn(CustomFlowSaveDto customFlowSaveDto, String id) throws IOException {
-        BasicSchool basicSchool = commonCacheService.schoolCache(customFlowSaveDto.getSchoolId());
-
         BpmnModel model = new BpmnModel();
         Process process = new Process();
         model.addProcess(process);
-        id = basicSchool.getCode() + "_" + id;
         process.setId(id);
 
-        // 判断是否仅为一个节点任务
-//        List<String> taskList = new ArrayList<String>();
-//        taskList.add("报销申请");
-////        taskList.add("主管审批");
-////        taskList.add("经理审批");
-////        taskList.add("总经理审批");
-//        //单节点任务
-//        if (taskList.size() == 1) {
-//            process.addFlowElement(createStartEvent());
-//            process.addFlowElement(createUserTask("task1", taskList.get(0), null));
-//            process.addFlowElement(createEndEvent());
-//            process.addFlowElement(createSequenceFlow("start", "task1", "", ""));
-//            process.addFlowElement(createSequenceFlow("task1", "end", "", ""));
-//        } else {
-//            // 多节点任务
-//            // 构造开始节点任务
-//            process.addFlowElement(createStartEvent());
-//            // 构造首个节点任务
-//            process.addFlowElement(createUserTask("task1", taskList.get(0), null));
-//            // 构造除去首尾节点的任务
-//            for (int i = 1; i < taskList.size() - 1; i++) {
-//                process.addFlowElement(createExclusiveGateway("createExclusiveGateway" + i));
-//                process.addFlowElement(createUserTask("task" + (i + 1), taskList.get(i), null));
-//            }
-//            // 构造尾节点任务
-//            process.addFlowElement(createExclusiveGateway("createExclusiveGateway" + (taskList.size() - 1)));
-//            process.addFlowElement(createUserTask("task" + taskList.size(), taskList.get(taskList.size() - 1), null));
-//            // 构造结束节点任务
-//            process.addFlowElement(createEndEvent());
-//
-//            // 构造连线(加网关)
-//            process.addFlowElement(createSequenceFlow("start", "task1", "", ""));
-//            // 第一个节点任务到第二个百分百通过的,因此不存在网关
-//            process.addFlowElement(createSequenceFlow("task1", "task2", "", ""));
-//            for (int i = 1; i < taskList.size(); i++) {
-//                process.addFlowElement(createSequenceFlow("task" + (i + 1), "createExclusiveGateway" + i, "", ""));
-//                // 判断网关走向(同意则直接到下一节点即可,不同意需要判断回退层级,决定回退到哪个节点,returnLevel等于0,即回退到task1)
-//                // i等于几,即意味着回退的线路有几种可能,例如i等于1,即是task2,那么只能回退 到task1
-//                // 如果i等于2,即是task3,那么此时可以回退到task1和task2;returnLevel =1 ,即回退到task1,所以这里我是扩展了可以驳回到任意阶段节点任务
-//                for (int j = 1; j <= i; j++) {
-//                    process.addFlowElement(createSequenceFlow("createExclusiveGateway" + i, "task" + j, "不通过",
-//                            "${result == '0' && returnLevel== '" + j + "'}"));
-//                }
-//                // 操作结果为通过时,需要判断是否为最后一个节点任务,若是则直接到end
-//                if (i == taskList.size() - 1) {
-//                    process.addFlowElement(
-//                            createSequenceFlow("createExclusiveGateway" + i, "end", "通过", "${result == '1'} "));
-//
-//                } else {
-//                    process.addFlowElement(createSequenceFlow("createExclusiveGateway" + i, "task" + (i + 2), "通过",
-//                            "${result == '1'}"));
-//                }
-//            }
-//        }
         List<CustomFlowDto> customFlowLists = customFlowSaveDto.getCustomFlowLists();
         Map<CustomFlowTypeEnum, CustomFlowDto> customFlowTypeEnumCustomFlowDtoMap = new HashMap<>();
-        String firstProcessId = null, lastProcessId = null;
-        LinkedList processLinkedList = new LinkedList();
+        FlowTaskLink flowTaskLink = new FlowTaskLink();
+        Map<String, Object> flowVarMap = new HashMap<>();
         for (int i = 0; i < customFlowLists.size(); i++) {
             CustomFlowDto customFlowDto = customFlowLists.get(i);
+            FlowTaskNode node = new FlowTaskNode(customFlowDto);
+            flowTaskLink.add(node);
             switch (customFlowDto.getType()) {
-                case START:
+                case START://开始节点
                     if (customFlowTypeEnumCustomFlowDtoMap.containsKey(CustomFlowTypeEnum.START)) {
                         throw ExceptionResultEnum.ERROR.exception("流程只能有一个开始节点!");
                     } else {
                         customFlowTypeEnumCustomFlowDtoMap.computeIfAbsent(CustomFlowTypeEnum.START, v -> customFlowDto);
                     }
+                    customFlowDto.setFlowTaskId(CustomFlowDynamicBuildEnum.START.getId());
                     process.addFlowElement(createStartEvent());
                     break;
-                case PROCESS:
-                    if (Objects.isNull(firstProcessId)) {
-                        firstProcessId = CustomFlowDynamicBuildEnum.USER_TASK.getId() + i;
+                case PROCESS://过程节点
+                    customFlowDto.setFlowTaskId(CustomFlowDynamicBuildEnum.USER_TASK.getId() + i);
+                    CustomFlowPropertyDto customFlowPropertyDto = customFlowDto.getProperty();
+                    List<String> approveUserIds = new ArrayList<>();//审批用户
+                    List<String> copyUserIds = new ArrayList<>();//抄送用户
+                    if (Objects.nonNull(customFlowDto.getProperty())) {
+                        //选人属性
+                        switch (customFlowPropertyDto.getApproveUserType()) {
+                            case USER://用户
+                                if (Objects.nonNull(customFlowPropertyDto.getApproveUsers()) && customFlowPropertyDto.getApproveUsers().size() > 0) {
+                                    approveUserIds.addAll(customFlowPropertyDto.getApproveUsers().stream().map(x -> String.valueOf(x.getId())).collect(Collectors.toList()));
+                                }
+                                break;
+                            case ROLE://角色
+                                if (Objects.nonNull(customFlowPropertyDto.getApproveRoles()) && customFlowPropertyDto.getApproveRoles().size() > 0) {
+                                    List<SysUser> sysUserList = sysUserService.findByRoleIds(customFlowPropertyDto.getApproveRoles().stream().map(x -> x.getId()).collect(Collectors.toList()));
+                                    approveUserIds.addAll(sysUserList.stream().map(x -> String.valueOf(x.getId())).collect(Collectors.toList()));
+                                }
+                                break;
+                            default:
+                                break;
+                        }
+                        //审批属性
+                        switch (customFlowPropertyDto.getMultipleUserApproveType()) {
+                            case ORDER://依次审批
+                            case ALL://会签审批
+                                flowVarMap.computeIfAbsent(DefaultInstanceConvertToMultiInstance.DEFAULT_ASSIGNEE_LIST + i, v -> approveUserIds);
+                                break;
+                            default:
+                                break;
+                        }
+                        //驳回属性
+                        switch (customFlowPropertyDto.getRejectType()) {
+                            case PREV://上一节点
+                                break;
+                            case START://发起人节点
+                                break;
+                            case PREV_ALL://该节点前全部节点
+                                break;
+                            default:
+                                break;
+                        }
+                        //驳回再提交属性
+                        switch (customFlowPropertyDto.getRejectResubmitType()) {
+                            case NORMAL://按正常流程提交
+                                break;
+                            case PREV_STEP://提交到驳回节点
+                                break;
+                            default:
+                                break;
+                        }
+                        //抄送用户
+                        if (Objects.nonNull(customFlowPropertyDto.getCopyForUsers())) {
+                            copyUserIds.addAll(customFlowPropertyDto.getCopyForUsers().stream().map(x -> String.valueOf(x.getId())).collect(Collectors.toList()));
+                        }
                     }
-                    lastProcessId = CustomFlowDynamicBuildEnum.USER_TASK.getId() + i;
-                    process.addFlowElement(createUserTask(lastProcessId, customFlowDto.getContent(), null));
+//                    process.addFlowElement(createUserTask(CustomFlowDynamicBuildEnum.USER_TASK.getId() + i, customFlowDto.getContent(), null));
+                    process.addFlowElement(createUserTask(CustomFlowDynamicBuildEnum.USER_TASK.getId() + i, "审批人", approveUserIds, customFlowPropertyDto.getMultipleUserApproveType()));
+                    //提取过程节点属性
                     break;
-                case END:
+                case END://结束节点
                     if (customFlowTypeEnumCustomFlowDtoMap.containsKey(CustomFlowTypeEnum.END)) {
                         throw ExceptionResultEnum.ERROR.exception("流程只能有一个结束节点!");
                     } else {
                         customFlowTypeEnumCustomFlowDtoMap.computeIfAbsent(CustomFlowTypeEnum.END, v -> customFlowDto);
                     }
+                    customFlowDto.setFlowTaskId(CustomFlowDynamicBuildEnum.END.getId());
                     process.addFlowElement(createEndEvent());
                     break;
                 default:
                     break;
             }
         }
-        process.addFlowElement(createSequenceFlow(CustomFlowTypeEnum.START.name(), firstProcessId, "", ""));
-        process.addFlowElement(createSequenceFlow(lastProcessId, CustomFlowTypeEnum.END.name(), "", ""));
+
+        for (int i = 1; i < flowTaskLink.size() - 1; i++) {
+            FlowTaskNode flowTaskNode = flowTaskLink.get(i);
+            process.addFlowElement(createSequenceFlow(flowTaskNode.getBefore().getTask().getFlowTaskId(), flowTaskNode.getTask().getFlowTaskId(), "", ""));
+        }
+        process.addFlowElement(createSequenceFlow(flowTaskLink.getLast().getBefore().getTask().getFlowTaskId(), flowTaskLink.getLast().getTask().getFlowTaskId(), "", ""));
 
         //生成图像信息
         new BpmnAutoLayout(model).execute();
         //部署流程
         Deployment deployment = repositoryService.createDeployment().addBpmnModel(id + "-dynamic-model.bpmn", model)
                 .name(id + "-multiple process deployment").deploy();
+
         //启动流程
-        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(id);
+        ProcessInstance processInstance = flowVarMap.size() > 0 ? runtimeService.startProcessInstanceByKey(id, flowVarMap) : runtimeService.startProcessInstanceByKey(id);
+
         //保存png图片和xml文件(这一步可做可不做)
         InputStream processDiagram = repositoryService.getProcessDiagram(processInstance.getProcessDefinitionId());
         FileUtils.copyInputStreamToFile(processDiagram, new File("target" + File.separator + id + "-multiple-process-diagram.png"));
@@ -1526,8 +1554,9 @@ public class ActivitiServiceImpl implements ActivitiService {
 //        InputStream processBpmn = repositoryService.getResourceAsStream(deployment.getId(), id + "-dynamic-model.bpmn");
 //        FileUtils.copyInputStreamToFile(processBpmn, new File("target" + File.separator + id + "-multiple-process.bpmn20.xml"));
 
+        File file = new File("target" + File.separator + id + "-multiple-process.bpmn");
         InputStream processBpmn = repositoryService.getResourceAsStream(deployment.getId(), id + "-dynamic-model.bpmn");
-        FileUtils.copyInputStreamToFile(processBpmn, new File("target" + File.separator + id + "-multiple-process.bpmn"));
+        FileUtils.copyInputStreamToFile(processBpmn, file);
     }
 
     /**
@@ -1537,7 +1566,8 @@ public class ActivitiServiceImpl implements ActivitiService {
      */
     protected StartEvent createStartEvent() {
         StartEvent startEvent = new StartEvent();
-        startEvent.setId(CustomFlowTypeEnum.START.name());
+        startEvent.setId(CustomFlowDynamicBuildEnum.START.getId());
+        startEvent.setName(CustomFlowDynamicBuildEnum.START.getTitle());
         return startEvent;
     }
 
@@ -1548,7 +1578,8 @@ public class ActivitiServiceImpl implements ActivitiService {
      */
     protected EndEvent createEndEvent() {
         EndEvent endEvent = new EndEvent();
-        endEvent.setId(CustomFlowTypeEnum.END.name());
+        endEvent.setId(CustomFlowDynamicBuildEnum.END.getId());
+        endEvent.setName(CustomFlowDynamicBuildEnum.END.getTitle());
         return endEvent;
     }
 
@@ -1566,6 +1597,87 @@ public class ActivitiServiceImpl implements ActivitiService {
         return userTask;
     }
 
+    /**
+     * @param id                                    对应我们画流程图中节点任务id
+     * @param name                                  节点任务名称
+     * @param assignees                             任务的执行者(这一块自行决定是否添加每一环节的执行者,若是动态分配的话,可以不用传值)
+     * @param customFlowMultipleUserApproveTypeEnum
+     * @return
+     */
+    protected UserTask createUserTask(String id, String name, List<String> assignees, CustomFlowMultipleUserApproveTypeEnum customFlowMultipleUserApproveTypeEnum) {
+        UserTask userTask = new UserTask();
+        userTask.setName(name);
+        userTask.setId(id);
+//        userTask.setAssignee("${assignee}");
+
+//        // 多实例
+//        MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = new MultiInstanceLoopCharacteristics();
+////        multiInstanceLoopCharacteristics.setSequential(false);
+//        multiInstanceLoopCharacteristics.setSequential(true);
+//        multiInstanceLoopCharacteristics.setInputDataItem("${assigneeList}");
+//        multiInstanceLoopCharacteristics.setElementVariable("assignee");
+//
+//        // 注入循环控制
+//        userTask.setLoopCharacteristics(multiInstanceLoopCharacteristics);
+//
+//        ProcessEngineConfigurationImpl processEngineConfiguration = (ProcessEngineConfigurationImpl) processEngine.getProcessEngineConfiguration();
+//        // 创建任务实例
+//        UserTaskActivityBehavior userTaskActivityBehavior = processEngineConfiguration.getActivityBehaviorFactory().createUserTaskActivityBehavior(userTask);
+//        // 创建behavior
+////        ParallelMultiInstanceBehavior behavior = new ParallelMultiInstanceBehavior(userTask, userTaskActivityBehavior);
+//        SequentialMultiInstanceBehavior behavior = new SequentialMultiInstanceBehavior(userTask, userTaskActivityBehavior);
+//        // 获取表达式解析工具
+//        behavior.setCollectionElementVariable("assignee");
+//
+//        // 注入表达式
+//        ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager();
+//        behavior.setCollectionExpression(expressionManager.createExpression("${assigneeList}"));
+
+        boolean isSequential = false;
+        if (customFlowMultipleUserApproveTypeEnum == CustomFlowMultipleUserApproveTypeEnum.ORDER) {
+            userTask = multiWorkFlow.createMultiInstanceLoopCharacteristics(userTask, !isSequential);
+        } else if (customFlowMultipleUserApproveTypeEnum == CustomFlowMultipleUserApproveTypeEnum.ALL) {
+            userTask = multiWorkFlow.createMultiInstanceLoopCharacteristics(userTask, isSequential);
+        } else {
+            userTask.setCandidateUsers(assignees);
+        }
+        return userTask;
+    }
+
+//    /**
+//     * @param id                                    对应我们画流程图中节点任务id
+//     * @param name                                  节点任务名称
+//     * @param assignees
+//     * @param customFlowMultipleUserApproveTypeEnum
+//     * @return
+//     */
+//    protected UserTask createUserTask(String id, String name, List<String> assignees, CustomFlowMultipleUserApproveTypeEnum customFlowMultipleUserApproveTypeEnum) {
+//        UserTask userTask = new UserTask();
+//        userTask.setName(name);
+//        userTask.setId(id);
+//        switch (customFlowMultipleUserApproveTypeEnum) {
+//            case ORDER:
+//            case ALL:
+//                MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = new MultiInstanceLoopCharacteristics();
+//                multiInstanceLoopCharacteristics.setLoopCardinality(String.valueOf(assignees.size()));
+////                multiInstanceLoopCharacteristics.setInputDataItem(assignees.toString().replaceAll("\\[", "").replaceAll("\\]", ""));
+//                multiInstanceLoopCharacteristics.setCompletionCondition("${nrOfActiveInstances == nrOfInstances}");
+////                multiInstanceLoopCharacteristics.setElementVariable("assignee");
+//                if (customFlowMultipleUserApproveTypeEnum == CustomFlowMultipleUserApproveTypeEnum.ORDER) {
+//                    multiInstanceLoopCharacteristics.setSequential(true);
+//                } else {
+//                    multiInstanceLoopCharacteristics.setSequential(false);
+//                }
+//                userTask.setLoopCharacteristics(multiInstanceLoopCharacteristics);
+//                break;
+//            default:
+//                break;
+//        }
+//        //设置审批人
+////        userTask.setCandidateUsers(assignees);
+//        return userTask;
+//    }
+
     /**
      * @param id 网关id
      * @return

+ 7 - 0
distributed-print/src/main/java/com/qmth/distributed/print/api/TFCustomFlowController.java

@@ -10,8 +10,10 @@ import com.qmth.distributed.print.business.entity.TFCustomFlow;
 import com.qmth.distributed.print.business.service.ActivitiService;
 import com.qmth.distributed.print.business.service.TFCustomFlowService;
 import com.qmth.teachcloud.common.contant.SystemConstant;
+import com.qmth.teachcloud.common.entity.BasicSchool;
 import com.qmth.teachcloud.common.entity.SysUser;
 import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
+import com.qmth.teachcloud.common.service.CommonCacheService;
 import com.qmth.teachcloud.common.util.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -58,6 +60,9 @@ public class TFCustomFlowController {
     @Resource
     ActivitiService activitiService;
 
+    @Resource
+    CommonCacheService commonCacheService;
+
     @ApiOperation(value = "保存和发布流程")
     @ApiResponses({@ApiResponse(code = 200, message = "常规信息", response = ResultUtil.class)})
     @RequestMapping(value = "/save", method = RequestMethod.POST)
@@ -70,6 +75,8 @@ public class TFCustomFlowController {
         customFlowSaveDto.setSchoolAndOrgInfo(sysUser.getSchoolId(), sysUser.getOrgId());
         log.info("customFlowSaveDto:{}", JacksonUtil.parseJson(customFlowSaveDto));
         String flowBpmnId = MD5Util.encoder(customFlowSaveDto.toString());
+        BasicSchool basicSchool = commonCacheService.schoolCache(customFlowSaveDto.getSchoolId());
+        flowBpmnId = basicSchool.getCode() + "_" + flowBpmnId;
         boolean lock = redisUtil.lock(SystemConstant.REDIS_LOCK_CUSTOM_FLOW_PREFIX + flowBpmnId, SystemConstant.REDIS_LOCK_CUSTOM_FLOW_TIME_OUT);
         if (!lock) {
             throw ExceptionResultEnum.ERROR.exception("正在发布中,请稍候再试!");

+ 8 - 0
teachcloud-common/src/main/java/com/qmth/teachcloud/common/mapper/SysUserMapper.java

@@ -83,4 +83,12 @@ public interface SysUserMapper extends BaseMapper<SysUser> {
      * @return
      */
     List<UserRoleNameResult> selectRoleNames(@Param("userIds") List<Long> userIds);
+
+    /**
+     * 根据角色ids查找用户
+     *
+     * @param roleIds
+     * @return
+     */
+    List<SysUser> findByRoleIds(@Param("roleIds") List<Long> roleIds);
 }

+ 8 - 0
teachcloud-common/src/main/java/com/qmth/teachcloud/common/service/SysUserService.java

@@ -252,4 +252,12 @@ public interface SysUserService extends IService<SysUser> {
      * @return
      */
     List<UserRoleNameResult> selectRoleNames(List<Long> userIds);
+
+    /**
+     * 根据角色ids查找用户
+     *
+     * @param roleIds
+     * @return
+     */
+    List<SysUser> findByRoleIds(List<Long> roleIds);
 }

+ 11 - 0
teachcloud-common/src/main/java/com/qmth/teachcloud/common/service/impl/SysUserServiceImpl.java

@@ -1201,6 +1201,17 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
         return sysUserMapper.selectRoleNames(userIds);
     }
 
+    /**
+     * 根据角色ids查找用户
+     *
+     * @param roleIds
+     * @return
+     */
+    @Override
+    public List<SysUser> findByRoleIds(List<Long> roleIds) {
+        return sysUserMapper.findByRoleIds(roleIds);
+    }
+
     /**
      * 批量处理用户信息帮助类
      *

+ 18 - 0
teachcloud-common/src/main/resources/mapper/SysUserMapper.xml

@@ -211,6 +211,24 @@
         group by sur.user_id
     </select>
 
+    <select id="findByRoleIds" resultType="com.qmth.teachcloud.common.entity.SysUser">
+        select distinct su.* from sys_user su
+        join sys_user_role sur on
+            sur.user_id = su.id
+        join sys_role sr on
+            sr.id = sur.role_id
+        <where>
+            <if test="roleIds != null">
+                AND sr.id IN
+                <foreach collection="roleIds" item="item" index="index" open="(" separator="," close=")">
+                    #{item}
+                </foreach>
+            </if>
+            and su.enable = 1
+            and sr.enable = 1
+        </where>
+    </select>
+
     <select id="findVerifyCodeByUser" resultType="com.qmth.teachcloud.common.bean.dto.VerifyCodeCheckDto">
         SELECT
             id,