diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/action/BaseAction.java b/flow-engine-framework/src/main/java/com/codingapi/flow/action/BaseAction.java index 24c6c90c..d297fd61 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/action/BaseAction.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/action/BaseAction.java @@ -1,20 +1,15 @@ package com.codingapi.flow.action; -import com.codingapi.flow.node.IAuditNode; -import com.codingapi.flow.node.manager.OperatorManager; -import com.codingapi.flow.node.manager.StrategyManager; -import com.codingapi.flow.operator.IFlowOperator; +import com.codingapi.flow.node.IFlowNode; import com.codingapi.flow.record.FlowRecord; import com.codingapi.flow.session.FlowSession; -import com.codingapi.flow.strategy.MultiOperatorAuditStrategy; -import com.codingapi.flow.utils.RandomUtils; import lombok.Getter; import lombok.SneakyThrows; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; @Getter public abstract class BaseAction implements IFlowAction { @@ -72,14 +67,10 @@ public Map toMap() { } @Override - public List trigger(FlowSession flowSession, FlowRecord currentRecord) { - return null; + public List generateRecords(FlowSession flowSession) { + return List.of(); } - @Override - public boolean isDone(FlowSession session, FlowRecord currentRecord, List currentRecords) { - return true; - } @SneakyThrows @SuppressWarnings("unchecked") @@ -96,47 +87,25 @@ protected static T fromMap(Map data, Clas } + @Override + public void run(FlowSession flowSession) {} + /** - * 生成下一节点的记录 - * - * @param currentNode 当前节点 - * @param triggerSession 触发会话 - * @param currentRecord 当前记录 - * @return 下一节节点的记录 + * 触发并执行后续节点 + * @param flowSession 当前会话 + * @param consumer 节点处理 */ - protected List generateNextRecords(IAuditNode currentNode, FlowSession triggerSession, FlowRecord currentRecord) { - List records = new ArrayList<>(); - OperatorManager operatorManager = currentNode.operators(triggerSession); - List operators = operatorManager.getOperators(); - for (int order = 0; order < operators.size(); order++) { - IFlowOperator operator = operators.get(order); - FlowRecord flowRecord = new FlowRecord(triggerSession.updateSession(operator), this.id, currentRecord.getProcessId(), currentRecord.getId(), order); - records.add(flowRecord); - } - if (operators.size() > 1) { - StrategyManager strategyManager = currentNode.strategies(); - MultiOperatorAuditStrategy.Type multiOperatorAuditStrategyType = strategyManager.getMultiOperatorAuditStrategyType(); - // 如果是顺序审批,则隐藏掉后续的人员的审批记录 - if (multiOperatorAuditStrategyType == MultiOperatorAuditStrategy.Type.SEQUENCE) { - for (int i = 1; i < records.size(); i++) { - FlowRecord record = records.get(i); - record.hidden(); - } - } - // 如果是随机审批,则隐藏掉后续的人员的审批记录 - if (multiOperatorAuditStrategyType == MultiOperatorAuditStrategy.Type.RANDOM_ONE) { - int index = RandomUtils.randomInt(operators.size()); - - List newRecords = new ArrayList<>(); - for (FlowRecord record : records) { - if (record.getNodeOrder() == index) { - record.resetNodeOrder(0); - newRecords.add(record); - } + public void triggerNode(FlowSession flowSession, Consumer consumer) { + List nextNodes = flowSession.matchNextNodes(); + for (IFlowNode node : nextNodes) { + FlowSession triggerSession = flowSession.updateSession(node); + if (node.continueTrigger(triggerSession)) { + this.triggerNode(triggerSession,consumer); + }else { + if (consumer != null) { + consumer.accept(triggerSession); } - return newRecords; } } - return records; } } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/action/IFlowAction.java b/flow-engine-framework/src/main/java/com/codingapi/flow/action/IFlowAction.java index bb3bc6da..fe82c326 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/action/IFlowAction.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/action/IFlowAction.java @@ -1,7 +1,6 @@ package com.codingapi.flow.action; -import com.codingapi.flow.node.IFlowNode; import com.codingapi.flow.record.FlowRecord; import com.codingapi.flow.session.FlowSession; @@ -36,20 +35,20 @@ public interface IFlowAction { /** * 执行动作 */ - List trigger(FlowSession flowSession, FlowRecord currentRecord); + List generateRecords(FlowSession flowSession); /** * 转换为map */ Map toMap(); + /** - * 流程是否结束 - * - * @param session session - * @param currentRecord 当前审批记录 - * @param currentRecords 当前节点所有人提交的记录 - * @return 是否结束 + * 执行动作 + * 业务流程的处理入口时通过run函数触发开启的流程 + * @param flowSession 会话 */ - boolean isDone(FlowSession session, FlowRecord currentRecord, List currentRecords); + void run(FlowSession flowSession); + + } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/action/PassAction.java b/flow-engine-framework/src/main/java/com/codingapi/flow/action/PassAction.java index 05fb9d1b..38e64d59 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/action/PassAction.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/action/PassAction.java @@ -1,12 +1,16 @@ package com.codingapi.flow.action; -import com.codingapi.flow.node.IAuditNode; +import com.codingapi.flow.context.RepositoryContext; +import com.codingapi.flow.event.FlowRecordDoneEvent; +import com.codingapi.flow.event.FlowRecordTodoEvent; +import com.codingapi.flow.event.IFlowEvent; +import com.codingapi.flow.node.BaseAuditNode; import com.codingapi.flow.node.IFlowNode; import com.codingapi.flow.node.manager.StrategyManager; import com.codingapi.flow.record.FlowRecord; import com.codingapi.flow.session.FlowSession; -import com.codingapi.flow.strategy.MultiOperatorAuditStrategy; import com.codingapi.flow.utils.RandomUtils; +import com.codingapi.springboot.framework.event.EventPusher; import java.util.ArrayList; import java.util.List; @@ -14,7 +18,7 @@ /** - * 通过 + * 通过 */ public class PassAction extends BaseAction { @@ -29,53 +33,25 @@ public static PassAction fromMap(Map data) { return BaseAction.fromMap(data, PassAction.class); } - @Override - public boolean isDone(FlowSession session, FlowRecord currentRecord, List currentRecords) { - // 多人审批 - if (currentRecords.size() > 1) { - StrategyManager strategyManager = session.getCurrentNode().strategies(); - MultiOperatorAuditStrategy.Type multiOperatorAuditStrategyType = strategyManager.getMultiOperatorAuditStrategyType(); - // 顺序审批 - if (multiOperatorAuditStrategyType == MultiOperatorAuditStrategy.Type.SEQUENCE) { - int currentOrder = currentRecord.getNodeOrder(); - int maxNodeOrder = currentRecords.size() - 1; - return currentOrder >= maxNodeOrder; - } - // 或签 - if (multiOperatorAuditStrategyType == MultiOperatorAuditStrategy.Type.ANY) { - return true; - } - // 并签 - if (multiOperatorAuditStrategyType == MultiOperatorAuditStrategy.Type.MERGE) { - float percent = strategyManager.getMultiOperatorAuditMergePercent(); - long total = currentRecords.size(); - // 尚未办理的数量为所有待办数-1,1是当前办理的这条记录 - long todoCount = currentRecords.stream().filter(FlowRecord::isTodo).count() - 1; - long doneCount = total - todoCount; - return doneCount >= total * percent; - } - } - return true; - } @Override - public List trigger(FlowSession flowSession, FlowRecord currentRecord) { + public List generateRecords(FlowSession flowSession) { + FlowRecord currentRecord = flowSession.getCurrentRecord(); List records = new ArrayList<>(); if (currentRecord.isReturnRecord()) { // 退回后的流程重新提交 - IAuditNode currentNode = flowSession.getWorkflow().getAuditNode(currentRecord.getReturnNodeId()); + BaseAuditNode currentNode = (BaseAuditNode) flowSession.getWorkflow().getFlowNode(currentRecord.getReturnNodeId()); StrategyManager strategyManager = currentNode.strategies(); // 是否退回到退回节点 if (strategyManager.isResume()) { FlowSession triggerSession = flowSession.updateSession(currentNode); - List nextRecords = this.generateNextRecords(currentNode, triggerSession.updateSession(currentNode), currentRecord); + List nextRecords = currentNode.generateCurrentRecords(triggerSession.updateSession(currentNode)); records.addAll(nextRecords); } } else { - List nextNodes = flowSession.nextNodes(); - for (IAuditNode node : nextNodes) { - //TODO 如果是条件节点,则自动完成当前记录,并构建下一个记录 - List nextRecords = this.generateNextRecords(node, flowSession.updateSession(node), currentRecord); + IFlowNode currentNode = flowSession.getCurrentNode(); + List nextRecords = currentNode.generateCurrentRecords(flowSession); + if (!nextRecords.isEmpty()) { records.addAll(nextRecords); } } @@ -83,4 +59,34 @@ public List trigger(FlowSession flowSession, FlowRecord currentRecor } + @Override + public void run(FlowSession flowSession) { + List flowEvents = new ArrayList<>(); + List recordList = new ArrayList<>(); + FlowRecord flowRecord = flowSession.getCurrentRecord(); + IFlowNode currentNode = flowSession.getCurrentNode(); + boolean done = currentNode.isDone(flowSession); + flowRecord.update(flowSession.getFormData().toMapData(), flowSession.getAdvice().getAdvice(), flowSession.getAdvice().getSignKey(), done); + // 添加流程结束事件 + flowEvents.add(new FlowRecordDoneEvent(flowRecord)); + recordList.add(flowRecord); + + if (done) { + this.triggerNode(flowSession,(triggerSession)->{ + List records = this.generateRecords(triggerSession); + if (!records.isEmpty()) { + for (FlowRecord record : records) { + if (record.isShow()) { + flowEvents.add(new FlowRecordTodoEvent(record)); + } + } + recordList.addAll(records); + } + }); + } + + RepositoryContext.getInstance().saveRecords(recordList); + + flowEvents.forEach(EventPusher::push); + } } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/action/RejectAction.java b/flow-engine-framework/src/main/java/com/codingapi/flow/action/RejectAction.java index 5fa00f66..79b6b359 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/action/RejectAction.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/action/RejectAction.java @@ -1,14 +1,17 @@ package com.codingapi.flow.action; -import com.codingapi.flow.node.IAuditNode; +import com.codingapi.flow.context.RepositoryContext; +import com.codingapi.flow.event.FlowRecordTodoEvent; +import com.codingapi.flow.event.IFlowEvent; import com.codingapi.flow.node.IFlowNode; import com.codingapi.flow.record.FlowRecord; import com.codingapi.flow.script.action.RejectActionScript; -import com.codingapi.flow.script.runtime.FlowScriptContext; import com.codingapi.flow.session.FlowSession; import com.codingapi.flow.utils.RandomUtils; +import com.codingapi.springboot.framework.event.EventPusher; import lombok.Getter; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -26,7 +29,7 @@ public RejectAction() { this.title = "拒绝"; this.type = ActionType.REJECT; this.display = new ActionDisplay(this.title); - this.script = RejectActionScript.defaultScript(); + this.script = RejectActionScript.startScript(); } public void setScript(String script) { @@ -48,32 +51,45 @@ public static RejectAction fromMap(Map data) { } @Override - public List trigger(FlowSession flowSession, FlowRecord currentRecord) { + public List generateRecords(FlowSession flowSession) { RejectActionScript.RejectResult rejectResult = script.execute(flowSession); - IAuditNode currentNode = null; + IFlowNode currentNode = null; // 返回指定节点 if (rejectResult.isReturnNode()) { String nodeId = rejectResult.getNodeId(); - currentNode = flowSession.getWorkflow().getAuditNode(nodeId); + currentNode = flowSession.getWorkflow().getFlowNode(nodeId); } // 流程结束(非正常) if (rejectResult.isTerminate()) { currentNode = flowSession.getWorkflow().getEndNode(); } - // 退回上级节点 - if (rejectResult.isReturnPrev()) { - long fromId = currentRecord.getFromId(); - FlowRecord preRecord = FlowScriptContext.getInstance().getRecordById(fromId); - if (preRecord == null) { - throw new IllegalArgumentException("preRecord is null"); - } - currentNode = flowSession.getWorkflow().getAuditNode(preRecord.getNodeId()); - } if (currentNode == null) { throw new IllegalArgumentException("currentNode is null"); } + flowSession = flowSession.updateSession(currentNode); + return currentNode.generateCurrentRecords(flowSession); + } + + @Override + public void run(FlowSession flowSession) { + List flowEvents = new ArrayList<>(); + List recordList = new ArrayList<>(); + + FlowRecord flowRecord = flowSession.getCurrentRecord(); + flowRecord.update(flowSession.getFormData().toMapData(), flowSession.getAdvice().getAdvice(), flowSession.getAdvice().getSignKey(), true); + recordList.add(flowRecord); + + List records = this.generateRecords(flowSession); + if(!records.isEmpty()) { + recordList.addAll(records); + for (FlowRecord record : records) { + if (record.isShow()) { + flowEvents.add(new FlowRecordTodoEvent(record)); + } + } + } + RepositoryContext.getInstance().saveRecords(recordList); + flowEvents.forEach(EventPusher::push); - FlowSession triggerSession = flowSession.updateSession(currentNode); - return this.generateNextRecords(currentNode, triggerSession, currentRecord); } } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/action/SaveAction.java b/flow-engine-framework/src/main/java/com/codingapi/flow/action/SaveAction.java index 427741d7..207dcffb 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/action/SaveAction.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/action/SaveAction.java @@ -1,10 +1,7 @@ package com.codingapi.flow.action; -import com.codingapi.flow.record.FlowRecord; -import com.codingapi.flow.session.FlowSession; import com.codingapi.flow.utils.RandomUtils; -import java.util.List; import java.util.Map; /** @@ -24,9 +21,4 @@ public static SaveAction fromMap(Map data) { } - @Override - public boolean isDone(FlowSession session, FlowRecord currentRecord, List currentRecords) { - // 保存按钮,不创建后续流程 - return false; - } } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/context/RepositoryContext.java b/flow-engine-framework/src/main/java/com/codingapi/flow/context/RepositoryContext.java new file mode 100644 index 00000000..5a1db55e --- /dev/null +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/context/RepositoryContext.java @@ -0,0 +1,63 @@ +package com.codingapi.flow.context; + +import com.codingapi.flow.gateway.FlowOperatorGateway; +import com.codingapi.flow.operator.IFlowOperator; +import com.codingapi.flow.record.FlowRecord; +import com.codingapi.flow.repository.FlowRecordRepository; +import com.codingapi.flow.repository.ParallelBranchRepository; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +public class RepositoryContext { + + @Getter + private final static RepositoryContext instance = new RepositoryContext(); + + private RepositoryContext() { + } + + @Setter + private FlowRecordRepository flowRecordRepository; + @Setter + private FlowOperatorGateway flowOperatorGateway; + @Setter + private ParallelBranchRepository parallelBranchRepository; + + public FlowRecord getRecordById(long id) { + return flowRecordRepository.get(id); + } + + public List findOperatorByIds(List ids) { + return flowOperatorGateway.findByIds(ids); + } + + public List findOperatorByIds(long... ids) { + return flowOperatorGateway.findByIds(ids); + } + + public void saveRecord(FlowRecord flowRecord) { + flowRecordRepository.save(flowRecord); + } + + public void saveRecords(List flowRecords) { + flowRecordRepository.saveAll(flowRecords); + } + + public List findRecordsByFromIdAndNodeId(long fromId, String nodeId) { + return flowRecordRepository.findRecordsByFromIdAndNodeId(fromId, nodeId); + } + + public List findRecordsByProcessId(String processId) { + return flowRecordRepository.findRecordsByProcessId(processId); + } + + public int getParallelBranchTriggerCount(String parallelId) { + return parallelBranchRepository.getTriggerCount(parallelId); + } + + public void addParallelTriggerCount(String parallelId) { + parallelBranchRepository.addTriggerCount(parallelId); + } +} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/form/FormMeta.java b/flow-engine-framework/src/main/java/com/codingapi/flow/form/FormMeta.java index 797109d3..63790c07 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/form/FormMeta.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/form/FormMeta.java @@ -36,7 +36,10 @@ public class FormMeta { public boolean isSubForm(String formCode){ - return subForms.stream().anyMatch(form -> form.getCode().equals(formCode)); + if(subForms!=null) { + return subForms.stream().anyMatch(form -> form.getCode().equals(formCode)); + } + return false; } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseAuditNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseAuditNode.java index 6bccb652..24b0819d 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseAuditNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseAuditNode.java @@ -1,26 +1,28 @@ package com.codingapi.flow.node; -import com.codingapi.flow.action.*; -import com.codingapi.flow.action.factory.FlowActionFactory; +import com.codingapi.flow.action.IFlowAction; +import com.codingapi.flow.action.PassAction; import com.codingapi.flow.error.ErrorThrow; import com.codingapi.flow.form.FormMeta; import com.codingapi.flow.form.permission.FormFieldPermission; -import com.codingapi.flow.form.permission.PermissionType; -import com.codingapi.flow.node.fixed.EndNode; +import com.codingapi.flow.node.builder.IFormFieldPermissionsNode; import com.codingapi.flow.node.manager.ActionManager; import com.codingapi.flow.node.manager.FieldPermissionManager; import com.codingapi.flow.node.manager.OperatorManager; import com.codingapi.flow.node.manager.StrategyManager; +import com.codingapi.flow.operator.IFlowOperator; +import com.codingapi.flow.record.FlowRecord; import com.codingapi.flow.script.node.ErrorTriggerScript; import com.codingapi.flow.script.node.NodeTitleScript; import com.codingapi.flow.script.node.OperatorLoadScript; import com.codingapi.flow.session.FlowAdvice; import com.codingapi.flow.session.FlowSession; import com.codingapi.flow.strategy.INodeStrategy; -import com.codingapi.flow.strategy.NodeStrategyFactory; +import com.codingapi.flow.strategy.MultiOperatorAuditStrategy; +import com.codingapi.flow.utils.RandomUtils; +import com.codingapi.flow.workflow.Workflow; import lombok.Getter; import lombok.Setter; -import lombok.SneakyThrows; import org.springframework.util.StringUtils; import java.util.ArrayList; @@ -28,7 +30,7 @@ import java.util.List; import java.util.Map; -public abstract class BaseAuditNode extends BaseFlowNode implements IAuditNode{ +public abstract class BaseAuditNode extends BaseFlowNode implements IFlowNode, IFormFieldPermissionsNode { public static final String DEFAULT_VIEW = "default"; @@ -60,12 +62,6 @@ public abstract class BaseAuditNode extends BaseFlowNode implements IAuditNode{ @Setter private List formFieldPermissions; - /** - * 节点操作 - */ - @Setter - private List actions; - /** * 节点策略 */ @@ -73,23 +69,22 @@ public abstract class BaseAuditNode extends BaseFlowNode implements IAuditNode{ private List nodeStrategies; - public BaseAuditNode(String id, String name, String view, OperatorLoadScript operatorScript, NodeTitleScript nodeTitleScript, ErrorTriggerScript errorTriggerScript, List formFieldPermissions, List actions, List nodeStrategies) { - super(id, name); + public BaseAuditNode(String id, String name, List actions, String view, OperatorLoadScript operatorScript, NodeTitleScript nodeTitleScript, ErrorTriggerScript errorTriggerScript, List formFieldPermissions, List nodeStrategies) { + super(id, name, actions); this.view = view; this.operatorScript = operatorScript; this.nodeTitleScript = nodeTitleScript; this.errorTriggerScript = errorTriggerScript; this.formFieldPermissions = formFieldPermissions; - this.actions = actions; this.nodeStrategies = nodeStrategies; } @Override public Map toMap() { Map map = new HashMap<>(); - map.put("view", view); map.put("name", name); map.put("id", id); + map.put("view", view); map.put("operatorScript", operatorScript.getScript()); map.put("nodeTitleScript", nodeTitleScript.getScript()); map.put("errorTriggerScript", errorTriggerScript.getScript()); @@ -100,51 +95,7 @@ public Map toMap() { return map; } - @SneakyThrows - @SuppressWarnings("unchecked") - public static T formMap(Map map, Class clazz) { - T node = clazz.getDeclaredConstructor().newInstance(); - node.setId((String) map.get("id")); - node.setName((String) map.get("name")); - node.setView((String) map.get("view")); - node.setOperatorScript((String) map.get("operatorScript")); - node.setNodeTitleScript((String) map.get("nodeTitleScript")); - node.setErrorTriggerScript((String) map.get("errorTriggerScript")); - List> permissions = (List>) map.get("formFieldsPermissions"); - if (permissions != null) { - List permissionList = new ArrayList<>(); - for (Map item : permissions) { - FormFieldPermission permission = new FormFieldPermission(); - permission.setFormCode((String) item.get("formCode")); - permission.setFieldName((String) item.get("fieldName")); - permission.setType(PermissionType.valueOf((String) item.get("type"))); - permissionList.add(permission); - } - node.setFormFieldPermissions(permissionList); - } - - List> actions = (List>) map.get("actions"); - if (actions != null) { - List actionList = new ArrayList<>(); - for (Map item : actions) { - IFlowAction action = FlowActionFactory.getInstance().createAction(item); - actionList.add(action); - } - node.setActions(actionList); - } - - List> nodeStrategies = (List>) map.get("nodeStrategies"); - if (nodeStrategies != null) { - List strategyList = new ArrayList<>(); - for (Map item : nodeStrategies) { - INodeStrategy strategy = NodeStrategyFactory.getInstance().createStrategy(item); - strategyList.add(strategy); - } - node.setNodeStrategies(strategyList); - } - return node; - } /** * 设置审批人配置脚本 @@ -174,86 +125,177 @@ public void setErrorTriggerScript(String errorTriggerScript) { } - @Override - public FieldPermissionManager formFieldsPermissions() { + public FieldPermissionManager formFieldsPermissionsManager() { return new FieldPermissionManager(formFieldPermissions); } - @Override - public ActionManager actions() { + public ActionManager actionManager() { return new ActionManager(actions); } - @Override public OperatorManager operators(FlowSession flowSession) { return new OperatorManager(operatorScript.execute(flowSession)); } - @Override public String generateTitle(FlowSession flowSession) { return nodeTitleScript.execute(flowSession); } - @Override public ErrorThrow errorTrigger(FlowSession flowSession) { return errorTriggerScript.execute(flowSession); } - @Override public void addAction(IFlowAction action) { - if(this.actions == null){ + if (this.actions == null) { this.actions = new ArrayList<>(); } this.actions.add(action); } - @Override - public void verify(FormMeta form) { - this.verifyFields(); - if (!(this instanceof EndNode)) { - FieldPermissionManager fieldPermissionManager = this.formFieldsPermissions(); - fieldPermissionManager.verifyPermissions(form); + public void verifyNode(FormMeta form) { + if (!StringUtils.hasText(view)) { + throw new IllegalArgumentException("view can not be null"); + } + if (!StringUtils.hasText(name)) { + throw new IllegalArgumentException("name can not be null"); + } + if (!StringUtils.hasText(id)) { + throw new IllegalArgumentException("id can not be null"); + } + if (actions == null || actions.isEmpty()) { + throw new IllegalArgumentException("actions can not be null"); } + if (operatorScript == null) { + throw new IllegalArgumentException("operator can not be null"); + } + if (nodeTitleScript == null) { + throw new IllegalArgumentException("nodeTitle can not be null"); + } + FieldPermissionManager fieldPermissionManager = this.formFieldsPermissionsManager(); + fieldPermissionManager.verifyPermissions(form); } - @Override public StrategyManager strategies() { return new StrategyManager(nodeStrategies); } + + @Override + public boolean continueTrigger(FlowSession session) { + return false; + } + @Override - public void verifyFlowAdvice(FlowAdvice flowAdvice) { + public void fillNewRecord(FlowSession session, FlowRecord flowRecord) { StrategyManager strategyManager = this.strategies(); - IFlowAction flowAction = flowAdvice.getAction(); + flowRecord.setTitle(this.generateTitle(session)); + flowRecord.setTimeoutTime(strategyManager.getTimeoutTime()); + flowRecord.setMergeable(strategyManager.isMergeable()); + } - // 保存操作,不做检查 - if(flowAction instanceof SaveAction){ - return; + @Override + public boolean isDone(FlowSession session) { + List currentRecords = session.getCurrentNodeRecords(); + FlowRecord currentRecord = session.getCurrentRecord(); + // 多人审批 + if (currentRecords.size() > 1) { + StrategyManager strategyManager = this.strategies(); + MultiOperatorAuditStrategy.Type multiOperatorAuditStrategyType = strategyManager.getMultiOperatorAuditStrategyType(); + // 顺序审批 + if (multiOperatorAuditStrategyType == MultiOperatorAuditStrategy.Type.SEQUENCE) { + int currentOrder = currentRecord.getNodeOrder(); + int maxNodeOrder = currentRecords.size() - 1; + return currentOrder >= maxNodeOrder; + } + // 或签 + if (multiOperatorAuditStrategyType == MultiOperatorAuditStrategy.Type.ANY) { + return true; + } + // 并签 + if (multiOperatorAuditStrategyType == MultiOperatorAuditStrategy.Type.MERGE) { + float percent = strategyManager.getMultiOperatorAuditMergePercent(); + long total = currentRecords.size(); + // 尚未办理的数量为所有待办数-1,1是当前办理的这条记录 + long todoCount = currentRecords.stream().filter(FlowRecord::isTodo).count() - 1; + long doneCount = total - todoCount; + return doneCount >= total * percent; + } } + return true; + } - // 转办操作 - if(flowAction instanceof TransferAction){ - if(flowAdvice.getTransferOperators()==null || flowAdvice.getTransferOperators().isEmpty()){ - throw new IllegalArgumentException("transferOperators can not be null"); - } + + /** + * 生成当前节点的记录 + * + * @param session 触发会话 + * @return 生成当前节点的记录 + */ + @Override + public List generateCurrentRecords(FlowSession session) { + + if(this.isWaitParallelRecord(session)){ + return List.of(); } - // 退回操作 - if(flowAction instanceof ReturnAction){ - if(flowAdvice.getBackNode()==null ){ - throw new IllegalArgumentException("backNode can not be null"); + List records = new ArrayList<>(); + FlowRecord currentRecord = session.getCurrentRecord(); + OperatorManager operatorManager = this.operators(session); + List operators = operatorManager.getOperators(); + for (int order = 0; order < operators.size(); order++) { + IFlowOperator operator = operators.get(order); + FlowRecord flowRecord = new FlowRecord(session.updateSession(operator), this.id, order); + records.add(flowRecord); + } + if (operators.size() > 1) { + StrategyManager strategyManager = this.strategies(); + MultiOperatorAuditStrategy.Type multiOperatorAuditStrategyType = strategyManager.getMultiOperatorAuditStrategyType(); + // 如果是顺序审批,则隐藏掉后续的人员的审批记录 + if (multiOperatorAuditStrategyType == MultiOperatorAuditStrategy.Type.SEQUENCE) { + for (int i = 1; i < records.size(); i++) { + FlowRecord record = records.get(i); + record.hidden(); + } + } + // 如果是随机审批,则隐藏掉后续的人员的审批记录 + if (multiOperatorAuditStrategyType == MultiOperatorAuditStrategy.Type.RANDOM_ONE) { + int index = RandomUtils.randomInt(operators.size()); + + List newRecords = new ArrayList<>(); + for (FlowRecord record : records) { + if (record.getNodeOrder() == index) { + record.resetNodeOrder(0); + newRecords.add(record); + } + } + return newRecords; } } + return records; + } + @Override + public void verifySession(FlowSession session) { + super.verifySession(session); + FlowRecord flowRecord = session.getCurrentRecord(); + Workflow workflow = session.getWorkflow(); + // 数据验证 + FieldPermissionManager fieldPermissionManager = this.formFieldsPermissionsManager(); + fieldPermissionManager.verifyFormData(workflow.getForm(), flowRecord.getFormData(), session.getFormData().toMapData()); + + FlowAdvice flowAdvice = session.getAdvice(); + IFlowAction flowAction = flowAdvice.getAction(); + + StrategyManager strategyManager = this.strategies(); // 是否必须填写审批意见 - if(strategyManager.isEnableAdvice()){ - if(!StringUtils.hasText(flowAdvice.getAdvice())){ + if (strategyManager.isEnableAdvice()) { + if (!StringUtils.hasText(flowAdvice.getAdvice())) { throw new IllegalArgumentException("advice can not be null"); } } // 通过操作 - if(flowAction instanceof PassAction) { + if (flowAction instanceof PassAction) { // 是否必须签名 if (strategyManager.isEnableSignable()) { if (!StringUtils.hasText(flowAdvice.getSignKey())) { @@ -263,24 +305,4 @@ public void verifyFlowAdvice(FlowAdvice flowAdvice) { } } - private void verifyFields() { - if (!StringUtils.hasText(view)) { - throw new IllegalArgumentException("view can not be null"); - } - if (!StringUtils.hasText(name)) { - throw new IllegalArgumentException("name can not be null"); - } - if (!StringUtils.hasText(id)) { - throw new IllegalArgumentException("id can not be null"); - } - if (actions == null || actions.isEmpty()) { - throw new IllegalArgumentException("actions can not be null"); - } - if (operatorScript == null) { - throw new IllegalArgumentException("operator can not be null"); - } - if (nodeTitleScript == null) { - throw new IllegalArgumentException("nodeTitle can not be null"); - } - } } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseBranchNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseBranchNode.java deleted file mode 100644 index d6716f5e..00000000 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseBranchNode.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.codingapi.flow.node; - -import com.codingapi.flow.form.FormMeta; -import com.codingapi.flow.session.FlowSession; -import lombok.Getter; -import lombok.Setter; -import lombok.SneakyThrows; -import org.springframework.util.StringUtils; - -import java.util.HashMap; -import java.util.Map; - -public abstract class BaseBranchNode extends BaseFlowNode implements IBranchNode { - - public BaseBranchNode(String id, String name) { - super(id, name); - } - - /** - * 条件顺序,越小则优先级越高 - */ - @Getter - @Setter - private int order; - - @Override - public Map toMap() { - Map map = new HashMap<>(); - map.put("name", name); - map.put("id", id); - map.put("type", getType()); - map.put("order", String.valueOf(order)); - return map; - } - - - @SneakyThrows - public static T formMap(Map map, Class clazz) { - T node = clazz.getDeclaredConstructor().newInstance(); - node.setId((String) map.get("id")); - node.setName((String) map.get("name")); - node.setOrder(Integer.parseInt((String) map.get("order"))); - return node; - } - - @Override - public boolean match(FlowSession flowSession) { - return true; - } - - @Override - public int order() { - return order; - } - - @Override - public void verify(FormMeta form) { - this.verifyFields(); - } - - private void verifyFields () { - if (!StringUtils.hasText(name)) { - throw new IllegalArgumentException("name can not be null"); - } - if (!StringUtils.hasText(id)) { - throw new IllegalArgumentException("id can not be null"); - } - } - -} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseConfigNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseConfigNode.java deleted file mode 100644 index 39ff2756..00000000 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseConfigNode.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.codingapi.flow.node; - -import com.codingapi.flow.form.FormMeta; -import com.codingapi.flow.session.FlowSession; -import lombok.SneakyThrows; -import org.springframework.util.StringUtils; - -import java.util.HashMap; -import java.util.Map; - -public abstract class BaseConfigNode extends BaseFlowNode implements IConfigNode { - - public BaseConfigNode(String id, String name) { - super(id, name); - } - - @Override - public void verify(FormMeta form) { - this.verifyFields(); - } - - private void verifyFields() { - if (!StringUtils.hasText(name)) { - throw new IllegalArgumentException("name can not be null"); - } - if (!StringUtils.hasText(id)) { - throw new IllegalArgumentException("id can not be null"); - } - } - - @Override - public void execute(FlowSession flowSession) { - - } - - @Override - public Map toMap() { - Map map = new HashMap<>(); - map.put("name", name); - map.put("id", id); - map.put("type", getType()); - return map; - } - - - @SneakyThrows - public static T formMap(Map map, Class clazz) { - T node = clazz.getDeclaredConstructor().newInstance(); - node.setId((String) map.get("id")); - node.setName((String) map.get("name")); - return node; - } - -} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseFlowNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseFlowNode.java index 889a5354..a7a48b18 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseFlowNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/BaseFlowNode.java @@ -1,15 +1,25 @@ package com.codingapi.flow.node; -import com.codingapi.flow.node.branch.RouterBranchNode; +import com.codingapi.flow.action.IFlowAction; +import com.codingapi.flow.action.ReturnAction; +import com.codingapi.flow.action.SaveAction; +import com.codingapi.flow.action.TransferAction; +import com.codingapi.flow.context.RepositoryContext; +import com.codingapi.flow.form.FormMeta; +import com.codingapi.flow.node.builder.NodeMapBuilder; +import com.codingapi.flow.node.manager.ActionManager; +import com.codingapi.flow.record.FlowRecord; +import com.codingapi.flow.session.FlowAdvice; import com.codingapi.flow.session.FlowSession; -import com.codingapi.flow.workflow.Workflow; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import lombok.SneakyThrows; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; -@AllArgsConstructor public abstract class BaseFlowNode implements IFlowNode { /** @@ -25,14 +35,137 @@ public abstract class BaseFlowNode implements IFlowNode { @Setter protected String name; + /** + * 条件顺序,越小则优先级越高 + */ + @Getter + @Setter + protected int order; + + /** + * 节点操作 + */ + @Setter + @Getter + protected List actions; + + public BaseFlowNode(String name, String id) { + this(name, id, 0, new ArrayList<>()); + } + + public BaseFlowNode(String id, String name, int order) { + this(id, name, order, new ArrayList<>()); + } + + public BaseFlowNode(String id, String name, List actions) { + this(id, name, 0, actions); + } + + public BaseFlowNode(String id, String name, int order, List actions) { + this.id = id; + this.name = name; + this.order = order; + this.actions = actions; + } + + @Override + public Map toMap() { + Map map = new HashMap<>(); + map.put("id", id); + map.put("name", name); + map.put("type", getType()); + map.put("order", String.valueOf(order)); + map.put("actions", actions.stream().map(IFlowAction::toMap).toList()); + return map; + } + + + @SneakyThrows + public static T loadFromMap(Map map, Class clazz) { + T node = clazz.getDeclaredConstructor().newInstance(); + node.setId((String) map.get("id")); + node.setName((String) map.get("name")); + node.setOrder(Integer.parseInt((String) map.get("order"))); + node.setActions(NodeMapBuilder.loadActions(map)); + return node; + } + @Override - public List nextNodes(FlowSession session) { - Workflow workflow = session.getWorkflow(); - if(this instanceof RouterBranchNode routerBranchNode){ - return routerBranchNode.matchRouters(session); - }else { - return workflow.edgeNext(this); + public void verifyNode(FormMeta form) { + + } + + /** + * 是否等待并行节点的汇聚 + */ + public boolean isWaitParallelRecord(FlowSession session) { + FlowRecord currentRecord = session.getCurrentRecord(); + if (currentRecord != null && this.getId().equals(currentRecord.getParallelBranchNodeId())) { + RepositoryContext.getInstance().addParallelTriggerCount(currentRecord.getParallelId()); + int parallelBranchTotal = currentRecord.getParallelBranchTotal(); + int parallelBranchCount = RepositoryContext.getInstance().getParallelBranchTriggerCount(currentRecord.getParallelId()); + if(parallelBranchCount == parallelBranchTotal){ + // 清空并行节点,防止数据继续继承到后续节点 + currentRecord.clearParallel(); + } + return parallelBranchCount != parallelBranchTotal; } + return false; } + + + @Override + public boolean continueTrigger(FlowSession session) { + return true; + } + + @Override + public void verifySession(FlowSession session) { + FlowAdvice flowAdvice = session.getAdvice(); + + IFlowAction flowAction = flowAdvice.getAction(); + // 保存操作,不做检查 + if (flowAction instanceof SaveAction) { + return; + } + // 转办操作 + if (flowAction instanceof TransferAction) { + if (flowAdvice.getTransferOperators() == null || flowAdvice.getTransferOperators().isEmpty()) { + throw new IllegalArgumentException("transferOperators can not be null"); + } + } + // 退回操作 + if (flowAction instanceof ReturnAction) { + if (flowAdvice.getBackNode() == null) { + throw new IllegalArgumentException("backNode can not be null"); + } + } + } + + @Override + public boolean isDone(FlowSession session) { + return true; + } + + @Override + public void fillNewRecord(FlowSession session, FlowRecord flowRecord) { + + } + + @Override + public List filterBranches(List nodeList, FlowSession flowSession) { + return nodeList; + } + + @Override + public List generateCurrentRecords(FlowSession session) { + return List.of(); + } + + @Override + public ActionManager actionManager() { + return new ActionManager(actions); + } + } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/IAuditNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/IAuditNode.java deleted file mode 100644 index 65060492..00000000 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/IAuditNode.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.codingapi.flow.node; - -import com.codingapi.flow.action.IFlowAction; -import com.codingapi.flow.error.ErrorThrow; -import com.codingapi.flow.node.manager.ActionManager; -import com.codingapi.flow.node.manager.FieldPermissionManager; -import com.codingapi.flow.node.manager.OperatorManager; -import com.codingapi.flow.node.manager.StrategyManager; -import com.codingapi.flow.session.FlowAdvice; -import com.codingapi.flow.session.FlowSession; - -/** - * 审批节点 - */ -public interface IAuditNode extends IFlowNode { - - /** - * 节点视图 - */ - String getView(); - - /** - * 添加节点动作 - */ - void addAction(IFlowAction action); - - /** - * 节点动作 - */ - ActionManager actions(); - - /** - * 表单字段权限设置 - */ - FieldPermissionManager formFieldsPermissions(); - - /** - * 节点参与用户 - */ - OperatorManager operators(FlowSession flowSession); - - /** - * 构建待办标题z - */ - String generateTitle(FlowSession flowSession); - - /** - * 错误异常处理 - */ - ErrorThrow errorTrigger(FlowSession flowSession); - - /** - * 节点策略 - */ - StrategyManager strategies(); - - /** - * 校验提交参数 - * @param flowAdvice 请求参数 - */ - void verifyFlowAdvice(FlowAdvice flowAdvice); - -} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/IBranchNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/IBranchNode.java deleted file mode 100644 index 7ac7ffad..00000000 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/IBranchNode.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.codingapi.flow.node; - -import com.codingapi.flow.session.FlowSession; - -/** - * 条件节点 - */ -public interface IBranchNode extends IFlowNode{ - - /** - * 匹配节点 - * @param flowSession 流程会话 - * @return 是否匹配 - */ - boolean match(FlowSession flowSession); - - /** - * 顺序 - */ - int order(); - -} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/IConfigNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/IConfigNode.java deleted file mode 100644 index c7e76641..00000000 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/IConfigNode.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.codingapi.flow.node; - -import com.codingapi.flow.session.FlowSession; - -/** - * 配置节点 - * 包括:子流程节点、延迟节点、触发节点 - */ -public interface IConfigNode extends IFlowNode { - - /** - * 执行配置任务,子流程节点、触发节点 执行任务以后要继续执行下一环节 - * @param flowSession 当前会话 - */ - void execute(FlowSession flowSession); - -} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/IFlowNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/IFlowNode.java index 051b2980..77a2c181 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/IFlowNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/IFlowNode.java @@ -1,6 +1,8 @@ package com.codingapi.flow.node; import com.codingapi.flow.form.FormMeta; +import com.codingapi.flow.node.manager.ActionManager; +import com.codingapi.flow.record.FlowRecord; import com.codingapi.flow.session.FlowSession; import java.util.List; @@ -8,6 +10,7 @@ /** * 流程节点 + * 流程执行的生命周期 */ public interface IFlowNode { @@ -26,6 +29,12 @@ public interface IFlowNode { */ String getType(); + /** + * 节点顺序,同一层级下的节点顺序,越小则优先级越高 + * @return 节点顺序 + */ + int getOrder(); + /** * 转化为map */ @@ -33,15 +42,62 @@ public interface IFlowNode { /** * 节点验证 + * 用于流程配置完成以后的验证时触发 + */ + void verifyNode(FormMeta form); + + /** + * 是否执行节点 + * 当前流程节点执行完成以后,触发下一环节时执行的函数,当返回true时则将继续执行后续流程,当返回false时则不继续执行后续流程,将执行当前节点的创建流程记录函数 {@link IFlowNode#generateCurrentRecords(FlowSession)} + * 同时 continueTrigger 函数也是条件分支的触发判定依据。{@link FlowSession#matchNextNodes()} 将会调用 {@link IFlowNode#filterBranches(List, FlowSession)} 匹配过滤条件 + * @param session 会话 + * @return true: 继续执行下一个节点 + */ + boolean continueTrigger(FlowSession session); + + /** + * 节点验证会话 + * 流程执行continueTrigger之前需要先对判断请求会话的参数是否满足节点参数要求 + */ + void verifySession(FlowSession session); + + /** + * 构建当前节点下的流程记录,不需要创建记录的返回 空集合 + * @param session 会话 + * @return 流程记录 + */ + List generateCurrentRecords(FlowSession session); + + /** + * 获取节点操作对象管理器 + * @return 节点操作对象管理器 */ - void verify(FormMeta form); + ActionManager actionManager(); + /** + * 节点是否完成 + * 当前节点是否完成,由于IFlowAction无法判断节点是否完成,是否完成需要根据节点配置的多人审批规则来判定,因此在提交通过节点时 + * {@link com.codingapi.flow.action.PassAction#run(FlowSession)} 函数中会判断当前节点是否完成 + * 如果完成则将执行当前节点的生成流程记录函数 {@link IFlowNode#continueTrigger(FlowSession)} + * @param session 会话 + * @return true: 节点完成 + */ + boolean isDone(FlowSession session); /** - * 获取下一个节点列表 + * 填充流程记录,在保存流程记录时将会触发当前节点的填充流程记录函数。由于不同节点存储的流程数据会存在差异。 * @param session 会话 - * @return 下一个节点列表 + * @param flowRecord 流程记录 + */ + void fillNewRecord(FlowSession session,FlowRecord flowRecord); + + + /** + * 过滤条件分支 + * @param nodeList 当前节点下的所有条件 + * @param flowSession 当前会话 + * @return 匹配的节点 */ - List nextNodes(FlowSession session); + List filterBranches(List nodeList, FlowSession flowSession); } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/audit/StartNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/audit/StartNode.java deleted file mode 100644 index 52435594..00000000 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/audit/StartNode.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.codingapi.flow.node.audit; - -import com.codingapi.flow.action.IFlowAction; -import com.codingapi.flow.action.PassAction; -import com.codingapi.flow.form.permission.FormFieldPermission; -import com.codingapi.flow.node.builder.AuditNodeBuilder; -import com.codingapi.flow.node.BaseAuditNode; -import com.codingapi.flow.script.node.ErrorTriggerScript; -import com.codingapi.flow.script.node.NodeTitleScript; -import com.codingapi.flow.script.node.OperatorLoadScript; -import com.codingapi.flow.strategy.*; -import com.codingapi.flow.utils.RandomUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * 开始节点 - */ -public class StartNode extends BaseAuditNode { - - public static final String NODE_TYPE = "start"; - public static final String DEFAULT_NAME = "开始节点"; - - @Override - public String getType() { - return NODE_TYPE; - } - - public StartNode(String id, String name, String view, OperatorLoadScript operatorScript, NodeTitleScript nodeTitleScript, ErrorTriggerScript errorTriggerScript, List formFieldsPermissions, List actions, List nodeStrategies) { - super(id, name, view, operatorScript, nodeTitleScript, errorTriggerScript, formFieldsPermissions, actions, nodeStrategies); - } - - public StartNode() { - this(RandomUtils.generateStringId(), DEFAULT_NAME, DEFAULT_VIEW, OperatorLoadScript.creator(), NodeTitleScript.defaultScript(), ErrorTriggerScript.defaultNodeScript(), new ArrayList<>(), defaultActions(), defaultStrategies()); - } - - private static List defaultStrategies() { - List strategies = new ArrayList<>(); - strategies.add(TimeoutStrategy.defaultStrategy()); - strategies.add(MultiOperatorAuditStrategy.defaultStrategy()); - strategies.add(SameOperatorAuditStrategy.defaultStrategy()); - strategies.add(RecordMergeStrategy.defaultStrategy()); - strategies.add(ResubmitStrategy.defaultStrategy()); - strategies.add(AdviceStrategy.defaultStrategy()); - return strategies; - } - - private static List defaultActions() { - List actions = new ArrayList<>(); - actions.add(new PassAction()); - return actions; - } - - public static StartNode formMap(Map map) { - return BaseAuditNode.formMap(map, StartNode.class); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder extends AuditNodeBuilder { - - public Builder() { - super(new StartNode()); - } - } -} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/branch/ParallelBranchNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/branch/ParallelBranchNode.java deleted file mode 100644 index 6fcf86c0..00000000 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/branch/ParallelBranchNode.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.codingapi.flow.node.branch; - -import com.codingapi.flow.node.BaseBranchNode; -import com.codingapi.flow.node.builder.BranchNodeBuilder; -import com.codingapi.flow.utils.RandomUtils; - -import java.util.Map; - -/** - * 并行节点 - */ -public class ParallelBranchNode extends BaseBranchNode { - - public static final String NODE_TYPE = "parallel_branch"; - public static final String DEFAULT_NAME = "并行节点"; - - @Override - public String getType() { - return NODE_TYPE; - } - - public ParallelBranchNode(String id, String name) { - super(id, name); - } - - public ParallelBranchNode() { - this(RandomUtils.generateStringId(), DEFAULT_NAME); - } - - - public static ParallelBranchNode formMap(Map map) { - return BaseBranchNode.formMap(map, ParallelBranchNode.class); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder extends BranchNodeBuilder { - public Builder() { - super(new ParallelBranchNode()); - } - } -} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/AuditNodeBuilder.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/AuditNodeBuilder.java index 50ee1347..91f4c3dc 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/AuditNodeBuilder.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/AuditNodeBuilder.java @@ -2,11 +2,9 @@ import com.codingapi.flow.action.IFlowAction; import com.codingapi.flow.form.permission.FormFieldPermission; -import com.codingapi.flow.form.permission.PermissionType; import com.codingapi.flow.node.BaseAuditNode; import com.codingapi.flow.strategy.INodeStrategy; -import java.util.ArrayList; import java.util.List; public abstract class AuditNodeBuilder, N extends BaseAuditNode> { @@ -19,87 +17,57 @@ public AuditNodeBuilder(N node) { public B id(String id) { node.setId(id); - return (B)this; + return (B) this; } public B actions(List actions) { node.setActions(actions); - return (B)this; + return (B) this; } public B addAction(IFlowAction action) { node.addAction(action); - return (B)this; + return (B) this; } public B name(String name) { node.setName(name); - return (B)this; + return (B) this; } public B view(String view) { node.setView(view); - return (B)this; + return (B) this; } public B nodeStrategies(List nodeStrategies) { node.setNodeStrategies(nodeStrategies); - return (B)this; + return (B) this; } public B operatorScript(String operatorScript) { node.setOperatorScript(operatorScript); - return (B)this; + return (B) this; } public B nodeTitleScript(String nodeTitleScript) { node.setNodeTitleScript(nodeTitleScript); - return (B)this; + return (B) this; } public B errorTriggerScript(String errorTriggerScript) { node.setErrorTriggerScript(errorTriggerScript); - return (B)this; + return (B) this; } public B formFieldsPermissions(List permissions) { node.setFormFieldPermissions(permissions); - return (B)this; - } - - public FormFieldPermissionsBuilder formFieldPermissionsBuilder() { - return new FormFieldPermissionsBuilder<>(this, node); + return (B) this; } public N build() { return node; } - - public static class FormFieldPermissionsBuilder,N extends BaseAuditNode> { - - private final N node; - private final AuditNodeBuilder baseBuilder; - private final List permissions; - - public FormFieldPermissionsBuilder(AuditNodeBuilder baseBuilder, N node) { - this.baseBuilder = baseBuilder; - this.node = node; - this.permissions = new ArrayList<>(); - } - - public FormFieldPermissionsBuilder addPermission(String form, String name, PermissionType type) { - FormFieldPermission permission = new FormFieldPermission(); - permission.setFormCode(form); - permission.setFieldName(name); - permission.setType(type); - permissions.add(permission); - return this; - } - - public AuditNodeBuilder build() { - node.setFormFieldPermissions(this.permissions); - return baseBuilder; - } - } } + diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/BaseNodeBuilder.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/BaseNodeBuilder.java new file mode 100644 index 00000000..866b1f29 --- /dev/null +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/BaseNodeBuilder.java @@ -0,0 +1,41 @@ +package com.codingapi.flow.node.builder; + +import com.codingapi.flow.action.IFlowAction; +import com.codingapi.flow.node.BaseFlowNode; + +import java.util.List; + +public abstract class BaseNodeBuilder, N extends BaseFlowNode> { + + protected final N node; + + public BaseNodeBuilder(N node) { + this.node = node; + } + + public B id(String id) { + node.setId(id); + return (B) this; + } + + public B actions(List actions) { + node.setActions(actions); + return (B) this; + } + + public B name(String name) { + node.setName(name); + return (B) this; + } + + + public B order(int order) { + node.setOrder(order); + return (B) this; + } + + + public N build() { + return node; + } +} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/BranchNodeBuilder.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/BranchNodeBuilder.java deleted file mode 100644 index 00550dc2..00000000 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/BranchNodeBuilder.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.codingapi.flow.node.builder; - -import com.codingapi.flow.node.BaseBranchNode; - -public abstract class BranchNodeBuilder, N extends BaseBranchNode> { - - protected final N node; - - public BranchNodeBuilder(N node) { - this.node = node; - } - - public B id(String id) { - node.setId(id); - return (B)this; - } - - public B name(String name) { - node.setName(name); - return (B)this; - } - - public B order(int order) { - node.setOrder(order); - return (B)this; - } - public N build() { - return node; - } - -} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/ConfigNodeBuilder.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/ConfigNodeBuilder.java deleted file mode 100644 index 27ef1d48..00000000 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/ConfigNodeBuilder.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.codingapi.flow.node.builder; - -import com.codingapi.flow.node.BaseConfigNode; - -public abstract class ConfigNodeBuilder, N extends BaseConfigNode> { - - protected final N node; - - public ConfigNodeBuilder(N node) { - this.node = node; - } - - public B id(String id) { - node.setId(id); - return (B)this; - } - - public B name(String name) { - node.setName(name); - return (B)this; - } - - public N build() { - return node; - } - -} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/FormFieldPermissionsBuilder.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/FormFieldPermissionsBuilder.java new file mode 100644 index 00000000..e7ea957f --- /dev/null +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/FormFieldPermissionsBuilder.java @@ -0,0 +1,34 @@ +package com.codingapi.flow.node.builder; + +import com.codingapi.flow.form.permission.FormFieldPermission; +import com.codingapi.flow.form.permission.PermissionType; + +import java.util.ArrayList; +import java.util.List; + +public class FormFieldPermissionsBuilder { + + private final List permissions; + + private FormFieldPermissionsBuilder() { + this.permissions = new ArrayList<>(); + } + + public static FormFieldPermissionsBuilder builder() { + return new FormFieldPermissionsBuilder(); + } + + public FormFieldPermissionsBuilder addPermission(String form, String name, PermissionType type) { + FormFieldPermission permission = new FormFieldPermission(); + permission.setFormCode(form); + permission.setFieldName(name); + permission.setType(type); + permissions.add(permission); + return this; + } + + public List build() { + return permissions; + } + +} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/IFormFieldPermissionsNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/IFormFieldPermissionsNode.java new file mode 100644 index 00000000..9c27ebb6 --- /dev/null +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/IFormFieldPermissionsNode.java @@ -0,0 +1,13 @@ +package com.codingapi.flow.node.builder; + +import com.codingapi.flow.form.permission.FormFieldPermission; +import com.codingapi.flow.node.manager.FieldPermissionManager; + +import java.util.List; + +public interface IFormFieldPermissionsNode { + + void setFormFieldPermissions(List formFieldPermissions); + + FieldPermissionManager formFieldsPermissionsManager(); +} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/NodeMapBuilder.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/NodeMapBuilder.java new file mode 100644 index 00000000..3bc2b7f2 --- /dev/null +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/builder/NodeMapBuilder.java @@ -0,0 +1,81 @@ +package com.codingapi.flow.node.builder; + +import com.codingapi.flow.action.IFlowAction; +import com.codingapi.flow.action.factory.FlowActionFactory; +import com.codingapi.flow.form.permission.FormFieldPermission; +import com.codingapi.flow.form.permission.PermissionType; +import com.codingapi.flow.node.BaseAuditNode; +import com.codingapi.flow.strategy.INodeStrategy; +import com.codingapi.flow.strategy.NodeStrategyFactory; +import lombok.SneakyThrows; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class NodeMapBuilder { + + @SuppressWarnings("unchecked") + public static List loadFormFieldPermissions(Map data) { + List> permissions = (List>) data.get("formFieldPermissions"); + if (permissions != null) { + List permissionList = new ArrayList<>(); + for (Map item : permissions) { + FormFieldPermission permission = new FormFieldPermission(); + permission.setFormCode((String) item.get("formCode")); + permission.setFieldName((String) item.get("fieldName")); + permission.setType(PermissionType.valueOf((String) item.get("type"))); + permissionList.add(permission); + } + return permissionList; + } + return null; + } + + @SuppressWarnings("unchecked") + public static List loadActions(Map data) { + List> actions = (List>) data.get("actions"); + if (actions != null) { + List actionList = new ArrayList<>(); + for (Map item : actions) { + IFlowAction action = FlowActionFactory.getInstance().createAction(item); + actionList.add(action); + } + return actionList; + } + return null; + } + + @SuppressWarnings("unchecked") + public static List loadNodeStrategies(Map data) { + List> nodeStrategies = (List>) data.get("nodeStrategies"); + if (nodeStrategies != null) { + List strategyList = new ArrayList<>(); + for (Map item : nodeStrategies) { + INodeStrategy strategy = NodeStrategyFactory.getInstance().createStrategy(item); + strategyList.add(strategy); + } + return strategyList; + } + return null; + } + + + @SneakyThrows + public static T formMap(Map map, Class clazz) { + T node = clazz.getDeclaredConstructor().newInstance(); + node.setId((String) map.get("id")); + node.setName((String) map.get("name")); + node.setView((String) map.get("view")); + node.setOperatorScript((String) map.get("operatorScript")); + node.setNodeTitleScript((String) map.get("nodeTitleScript")); + node.setErrorTriggerScript((String) map.get("errorTriggerScript")); + List permissionList = NodeMapBuilder.loadFormFieldPermissions(map); + node.setFormFieldPermissions(permissionList); + List actionList = NodeMapBuilder.loadActions(map); + node.setActions(actionList); + List strategyList = NodeMapBuilder.loadNodeStrategies(map); + node.setNodeStrategies(strategyList); + return node; + } +} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/factory/NodeFactory.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/factory/NodeFactory.java index 88650ee1..451e3074 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/factory/NodeFactory.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/factory/NodeFactory.java @@ -1,15 +1,18 @@ package com.codingapi.flow.node.factory; import com.codingapi.flow.node.IFlowNode; -import com.codingapi.flow.node.audit.*; -import com.codingapi.flow.node.branch.BranchNodeBranchNode; -import com.codingapi.flow.node.branch.InclusiveBranchNode; -import com.codingapi.flow.node.branch.ParallelBranchNode; -import com.codingapi.flow.node.branch.RouterBranchNode; -import com.codingapi.flow.node.config.DelayNode; -import com.codingapi.flow.node.config.SubProcessNode; -import com.codingapi.flow.node.config.TriggerNode; -import com.codingapi.flow.node.fixed.EndNode; +import com.codingapi.flow.node.nodes.BranchNodeBranchNode; +import com.codingapi.flow.node.nodes.InclusiveBranchNode; +import com.codingapi.flow.node.nodes.ParallelBranchNode; +import com.codingapi.flow.node.nodes.RouterBranchNode; +import com.codingapi.flow.node.nodes.DelayNode; +import com.codingapi.flow.node.nodes.SubProcessNode; +import com.codingapi.flow.node.nodes.TriggerNode; +import com.codingapi.flow.node.nodes.EndNode; +import com.codingapi.flow.node.nodes.ApprovalNode; +import com.codingapi.flow.node.nodes.HandleNode; +import com.codingapi.flow.node.nodes.NotifyNode; +import com.codingapi.flow.node.nodes.StartNode; import lombok.Getter; import lombok.SneakyThrows; diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/fixed/EndNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/fixed/EndNode.java deleted file mode 100644 index 16906663..00000000 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/fixed/EndNode.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.codingapi.flow.node.fixed; - -import com.codingapi.flow.action.DefaultAction; -import com.codingapi.flow.action.IFlowAction; -import com.codingapi.flow.form.permission.FormFieldPermission; -import com.codingapi.flow.node.builder.AuditNodeBuilder; -import com.codingapi.flow.node.BaseAuditNode; -import com.codingapi.flow.script.node.ErrorTriggerScript; -import com.codingapi.flow.script.node.NodeTitleScript; -import com.codingapi.flow.script.node.OperatorLoadScript; -import com.codingapi.flow.strategy.*; -import com.codingapi.flow.utils.RandomUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * 结束节点 - */ -public class EndNode extends BaseAuditNode { - - public static final String NODE_TYPE = "end"; - public static final String DEFAULT_NAME = "结束节点"; - - @Override - public String getType() { - return NODE_TYPE; - } - - - public EndNode(String id, String name, String view, OperatorLoadScript operatorScript, NodeTitleScript nodeTitleScript, ErrorTriggerScript errorTriggerScript, List formFieldsPermissions, List actions, List nodeStrategies) { - super(id, name, view, operatorScript, nodeTitleScript, errorTriggerScript, formFieldsPermissions, actions, nodeStrategies); - } - - public EndNode() { - this(RandomUtils.generateStringId(), DEFAULT_NAME, DEFAULT_VIEW, OperatorLoadScript.creator(), NodeTitleScript.defaultScript(), ErrorTriggerScript.defaultNodeScript(), new ArrayList<>(), defaultActions(), defaultStrategies()); - } - - private static List defaultStrategies() { - List strategies = new ArrayList<>(); - strategies.add(TimeoutStrategy.defaultStrategy()); - strategies.add(MultiOperatorAuditStrategy.defaultStrategy()); - strategies.add(SameOperatorAuditStrategy.defaultStrategy()); - strategies.add(RecordMergeStrategy.defaultStrategy()); - strategies.add(ResubmitStrategy.defaultStrategy()); - strategies.add(AdviceStrategy.defaultStrategy()); - return strategies; - } - - private static List defaultActions() { - List actions = new ArrayList<>(); - actions.add(new DefaultAction()); - return actions; - } - - public static EndNode formMap(Map map) { - return BaseAuditNode.formMap(map, EndNode.class); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder extends AuditNodeBuilder { - - public Builder() { - super(new EndNode()); - } - } -} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/audit/ApprovalNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/ApprovalNode.java similarity index 73% rename from flow-engine-framework/src/main/java/com/codingapi/flow/node/audit/ApprovalNode.java rename to flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/ApprovalNode.java index d179358c..5fee2511 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/audit/ApprovalNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/ApprovalNode.java @@ -1,4 +1,4 @@ -package com.codingapi.flow.node.audit; +package com.codingapi.flow.node.nodes; import com.codingapi.flow.action.IFlowAction; import com.codingapi.flow.action.PassAction; @@ -7,6 +7,7 @@ import com.codingapi.flow.form.permission.FormFieldPermission; import com.codingapi.flow.node.builder.AuditNodeBuilder; import com.codingapi.flow.node.BaseAuditNode; +import com.codingapi.flow.node.builder.NodeMapBuilder; import com.codingapi.flow.script.node.ErrorTriggerScript; import com.codingapi.flow.script.node.NodeTitleScript; import com.codingapi.flow.script.node.OperatorLoadScript; @@ -30,12 +31,12 @@ public String getType() { return NODE_TYPE; } - public ApprovalNode(String id, String name, String view, OperatorLoadScript operatorScript, NodeTitleScript nodeTitleScript, ErrorTriggerScript errorTriggerScript, List formFieldsPermissions, List actions, List nodeStrategies) { - super(id, name, view, operatorScript, nodeTitleScript, errorTriggerScript, formFieldsPermissions, actions, nodeStrategies); + public ApprovalNode(String id, String name,List actions, String view, OperatorLoadScript operatorScript, NodeTitleScript nodeTitleScript, ErrorTriggerScript errorTriggerScript, List formFieldsPermissions, List nodeStrategies) { + super(id, name, actions, view, operatorScript, nodeTitleScript, errorTriggerScript, formFieldsPermissions, nodeStrategies); } public ApprovalNode() { - this(RandomUtils.generateStringId(), DEFAULT_NAME, DEFAULT_VIEW, OperatorLoadScript.creator(), NodeTitleScript.defaultScript(), ErrorTriggerScript.defaultNodeScript(), new ArrayList<>(), defaultActions(), defaultStrategies()); + this(RandomUtils.generateStringId(), DEFAULT_NAME,defaultActions(), DEFAULT_VIEW, OperatorLoadScript.creator(), NodeTitleScript.defaultScript(), ErrorTriggerScript.defaultNodeScript(), new ArrayList<>(), defaultStrategies()); } private static List defaultStrategies() { @@ -58,8 +59,9 @@ private static List defaultActions() { } + public static ApprovalNode formMap(Map map) { - return BaseAuditNode.formMap(map, ApprovalNode.class); + return NodeMapBuilder.formMap(map, ApprovalNode.class); } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/branch/BranchNodeBranchNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/BranchNodeBranchNode.java similarity index 52% rename from flow-engine-framework/src/main/java/com/codingapi/flow/node/branch/BranchNodeBranchNode.java rename to flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/BranchNodeBranchNode.java index 908a77da..a72bc6ce 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/branch/BranchNodeBranchNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/BranchNodeBranchNode.java @@ -1,17 +1,22 @@ -package com.codingapi.flow.node.branch; +package com.codingapi.flow.node.nodes; -import com.codingapi.flow.node.BaseBranchNode; -import com.codingapi.flow.node.builder.BranchNodeBuilder; +import com.codingapi.flow.node.BaseFlowNode; +import com.codingapi.flow.node.IFlowNode; +import com.codingapi.flow.node.builder.BaseNodeBuilder; import com.codingapi.flow.script.node.ConditionScript; import com.codingapi.flow.session.FlowSession; import com.codingapi.flow.utils.RandomUtils; +import lombok.Setter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.Map; /** * 分支节点 */ -public class BranchNodeBranchNode extends BaseBranchNode { +public class BranchNodeBranchNode extends BaseFlowNode { public static final String NODE_TYPE = "condition_branch"; public static final String DEFAULT_NAME = "分支节点"; @@ -19,6 +24,7 @@ public class BranchNodeBranchNode extends BaseBranchNode { /** * 条件脚本 */ + @Setter private ConditionScript conditionScript; @Override @@ -26,23 +32,39 @@ public String getType() { return NODE_TYPE; } - public BranchNodeBranchNode(String id, String name) { - super(id, name); + public BranchNodeBranchNode(String id, String name, int order) { + super(id, name, order); this.conditionScript = ConditionScript.defaultScript(); } public BranchNodeBranchNode() { - this(RandomUtils.generateStringId(), DEFAULT_NAME); + this(RandomUtils.generateStringId(), DEFAULT_NAME, 0); } /** * 匹配条件 */ @Override - public boolean match(FlowSession request) { + public boolean continueTrigger(FlowSession request) { return conditionScript.execute(request); } + @Override + public List filterBranches(List nodeList, FlowSession flowSession) { + List nodes = new ArrayList<>(); + for (IFlowNode node: nodeList){ + if (node.continueTrigger(flowSession)){ + nodes.add(node); + } + } + // 获取最小order的节点 + nodes.sort(Comparator.comparingInt(IFlowNode::getOrder)); + if(!nodes.isEmpty()){ + return nodes.subList(0,1); + } + return nodes; + } + @Override public Map toMap() { Map map = super.toMap(); @@ -51,7 +73,7 @@ public Map toMap() { } public static BranchNodeBranchNode formMap(Map map) { - BranchNodeBranchNode branchNode = BaseBranchNode.formMap(map, BranchNodeBranchNode.class); + BranchNodeBranchNode branchNode = BaseFlowNode.loadFromMap(map, BranchNodeBranchNode.class); branchNode.conditionScript = new ConditionScript((String) map.get("script")); return branchNode; } @@ -60,7 +82,8 @@ public static Builder builder() { return new Builder(); } - public static class Builder extends BranchNodeBuilder { + public static class Builder extends BaseNodeBuilder { + public Builder() { super(new BranchNodeBranchNode()); } @@ -69,8 +92,5 @@ public Builder conditionScript(String script) { node.conditionScript = new ConditionScript(script); return this; } - - - } } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/config/DelayNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/DelayNode.java similarity index 67% rename from flow-engine-framework/src/main/java/com/codingapi/flow/node/config/DelayNode.java rename to flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/DelayNode.java index 5a6bf5eb..4a5e78fc 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/config/DelayNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/DelayNode.java @@ -1,7 +1,7 @@ -package com.codingapi.flow.node.config; +package com.codingapi.flow.node.nodes; -import com.codingapi.flow.node.BaseConfigNode; -import com.codingapi.flow.node.builder.ConfigNodeBuilder; +import com.codingapi.flow.node.BaseFlowNode; +import com.codingapi.flow.node.builder.BaseNodeBuilder; import com.codingapi.flow.utils.RandomUtils; import java.util.Map; @@ -9,7 +9,7 @@ /** * 延迟节点 */ -public class DelayNode extends BaseConfigNode { +public class DelayNode extends BaseFlowNode { public static final String NODE_TYPE = "delay"; public static final String DEFAULT_NAME = "延迟节点"; @@ -29,14 +29,14 @@ public DelayNode() { } public static DelayNode formMap(Map map) { - return BaseConfigNode.formMap(map, DelayNode.class); + return BaseFlowNode.loadFromMap(map, DelayNode.class); } public static Builder builder() { return new Builder(); } - public static class Builder extends ConfigNodeBuilder { + public static class Builder extends BaseNodeBuilder { public Builder() { super(new DelayNode()); } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/EndNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/EndNode.java new file mode 100644 index 00000000..9807fcf3 --- /dev/null +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/EndNode.java @@ -0,0 +1,87 @@ +package com.codingapi.flow.node.nodes; + +import com.codingapi.flow.action.IFlowAction; +import com.codingapi.flow.action.PassAction; +import com.codingapi.flow.context.RepositoryContext; +import com.codingapi.flow.event.FlowRecordFinishEvent; +import com.codingapi.flow.node.BaseFlowNode; +import com.codingapi.flow.node.builder.BaseNodeBuilder; +import com.codingapi.flow.record.FlowRecord; +import com.codingapi.flow.session.FlowSession; +import com.codingapi.flow.utils.RandomUtils; +import com.codingapi.springboot.framework.event.EventPusher; + +import java.util.List; +import java.util.Map; + +/** + * 结束节点 + */ +public class EndNode extends BaseFlowNode { + + public static final String NODE_TYPE = "end"; + public static final String DEFAULT_NAME = "结束节点"; + + @Override + public String getType() { + return NODE_TYPE; + } + + + @Override + public List generateCurrentRecords(FlowSession session) { + if(this.isWaitParallelRecord(session)){ + return List.of(); + } + // 构建结束记录 + FlowRecord finishRecord = new FlowRecord(session, session.getCurrentAction().id(), 0); + finishRecord.finish(true); + return List.of(finishRecord); + } + + @Override + public void fillNewRecord(FlowSession session, FlowRecord flowRecord) { + flowRecord.setTitle("over"); + flowRecord.setCurrentOperatorId(-1); + + IFlowAction currentAction = session.getCurrentAction(); + // 标记当前流程结束 + FlowRecord latestRecord = session.getCurrentRecord(); + // 添加历史记录到记录中 + List historyRecords = RepositoryContext.getInstance().findRecordsByProcessId(latestRecord.getProcessId()); + // 设置状态为完成 + historyRecords.forEach(item -> { + item.finish(currentAction instanceof PassAction); + }); + RepositoryContext.getInstance().saveRecords(historyRecords); + // 流程是否正常结束 + EventPusher.push(new FlowRecordFinishEvent(latestRecord)); + } + + @Override + public boolean continueTrigger(FlowSession session) { + return false; + } + + public EndNode(String id, String name) { + super(id, name); + } + + public EndNode() { + this(RandomUtils.generateStringId(), DEFAULT_NAME); + } + + public static EndNode formMap(Map map) { + return BaseFlowNode.loadFromMap(map, EndNode.class); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends BaseNodeBuilder { + public Builder() { + super(new EndNode()); + } + } +} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/audit/HandleNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/HandleNode.java similarity index 71% rename from flow-engine-framework/src/main/java/com/codingapi/flow/node/audit/HandleNode.java rename to flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/HandleNode.java index 6ffa3549..3371bbf1 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/audit/HandleNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/HandleNode.java @@ -1,10 +1,11 @@ -package com.codingapi.flow.node.audit; +package com.codingapi.flow.node.nodes; import com.codingapi.flow.action.IFlowAction; import com.codingapi.flow.action.PassAction; import com.codingapi.flow.form.permission.FormFieldPermission; import com.codingapi.flow.node.builder.AuditNodeBuilder; import com.codingapi.flow.node.BaseAuditNode; +import com.codingapi.flow.node.builder.NodeMapBuilder; import com.codingapi.flow.script.node.ErrorTriggerScript; import com.codingapi.flow.script.node.NodeTitleScript; import com.codingapi.flow.script.node.OperatorLoadScript; @@ -29,12 +30,12 @@ public String getType() { } - public HandleNode(String id, String name, String view, OperatorLoadScript operatorScript, NodeTitleScript nodeTitleScript, ErrorTriggerScript errorTriggerScript, List formFieldsPermissions, List actions, List nodeStrategies) { - super(id, name, view, operatorScript, nodeTitleScript, errorTriggerScript, formFieldsPermissions, actions, nodeStrategies); + public HandleNode(String id, String name,List actions, String view, OperatorLoadScript operatorScript, NodeTitleScript nodeTitleScript, ErrorTriggerScript errorTriggerScript, List formFieldsPermissions, List nodeStrategies) { + super(id, name,actions, view, operatorScript, nodeTitleScript, errorTriggerScript, formFieldsPermissions, nodeStrategies); } public HandleNode() { - this(RandomUtils.generateStringId(), DEFAULT_NAME, DEFAULT_VIEW, OperatorLoadScript.creator(), NodeTitleScript.defaultScript(), ErrorTriggerScript.defaultNodeScript(), new ArrayList<>(), defaultActions(), defaultStrategies()); + this(RandomUtils.generateStringId(), DEFAULT_NAME, defaultActions(), DEFAULT_VIEW, OperatorLoadScript.creator(), NodeTitleScript.defaultScript(), ErrorTriggerScript.defaultNodeScript(),new ArrayList<>(), defaultStrategies()); } private static List defaultStrategies() { @@ -55,7 +56,7 @@ private static List defaultActions() { } public static HandleNode formMap(Map map) { - return BaseAuditNode.formMap(map, HandleNode.class); + return NodeMapBuilder.formMap(map, HandleNode.class); } public static Builder builder() { diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/branch/InclusiveBranchNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/InclusiveBranchNode.java similarity index 67% rename from flow-engine-framework/src/main/java/com/codingapi/flow/node/branch/InclusiveBranchNode.java rename to flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/InclusiveBranchNode.java index 287e28b6..8368ae3e 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/branch/InclusiveBranchNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/InclusiveBranchNode.java @@ -1,7 +1,7 @@ -package com.codingapi.flow.node.branch; +package com.codingapi.flow.node.nodes; -import com.codingapi.flow.node.BaseBranchNode; -import com.codingapi.flow.node.builder.BranchNodeBuilder; +import com.codingapi.flow.node.BaseFlowNode; +import com.codingapi.flow.node.builder.BaseNodeBuilder; import com.codingapi.flow.utils.RandomUtils; import java.util.Map; @@ -9,7 +9,7 @@ /** * 包容分支节点 */ -public class InclusiveBranchNode extends BaseBranchNode { +public class InclusiveBranchNode extends BaseFlowNode { public static final String NODE_TYPE = "inclusive_branch"; public static final String DEFAULT_NAME = "包容分支节点"; @@ -29,14 +29,14 @@ public InclusiveBranchNode() { } public static InclusiveBranchNode formMap(Map map) { - return BaseBranchNode.formMap(map, InclusiveBranchNode.class); + return BaseFlowNode.loadFromMap(map, InclusiveBranchNode.class); } public static Builder builder() { return new Builder(); } - public static class Builder extends BranchNodeBuilder { + public static class Builder extends BaseNodeBuilder { public Builder() { super(new InclusiveBranchNode()); diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/audit/NotifyNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/NotifyNode.java similarity index 71% rename from flow-engine-framework/src/main/java/com/codingapi/flow/node/audit/NotifyNode.java rename to flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/NotifyNode.java index f55d37d0..5831f62a 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/audit/NotifyNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/NotifyNode.java @@ -1,10 +1,11 @@ -package com.codingapi.flow.node.audit; +package com.codingapi.flow.node.nodes; import com.codingapi.flow.action.DefaultAction; import com.codingapi.flow.action.IFlowAction; import com.codingapi.flow.form.permission.FormFieldPermission; import com.codingapi.flow.node.builder.AuditNodeBuilder; import com.codingapi.flow.node.BaseAuditNode; +import com.codingapi.flow.node.builder.NodeMapBuilder; import com.codingapi.flow.script.node.ErrorTriggerScript; import com.codingapi.flow.script.node.NodeTitleScript; import com.codingapi.flow.script.node.OperatorLoadScript; @@ -28,12 +29,12 @@ public String getType() { return NODE_TYPE; } - public NotifyNode(String id, String name, String view, OperatorLoadScript operatorScript, NodeTitleScript nodeTitleScript, ErrorTriggerScript errorTriggerScript, List formFieldsPermissions, List actions, List nodeStrategies) { - super(id, name, view, operatorScript, nodeTitleScript, errorTriggerScript, formFieldsPermissions, actions, nodeStrategies); + public NotifyNode(String id, String name,List actions, String view, OperatorLoadScript operatorScript, NodeTitleScript nodeTitleScript, ErrorTriggerScript errorTriggerScript, List formFieldsPermissions, List nodeStrategies) { + super(id, name,actions, view, operatorScript, nodeTitleScript, errorTriggerScript, formFieldsPermissions, nodeStrategies); } public NotifyNode() { - this(RandomUtils.generateStringId(), DEFAULT_NAME, DEFAULT_VIEW, OperatorLoadScript.creator(), NodeTitleScript.defaultScript(), ErrorTriggerScript.defaultNodeScript(), new ArrayList<>(), defaultActions(), defaultStrategies()); + this(RandomUtils.generateStringId(), DEFAULT_NAME,defaultActions(), DEFAULT_VIEW, OperatorLoadScript.creator(), NodeTitleScript.defaultScript(), ErrorTriggerScript.defaultNodeScript(), new ArrayList<>(), defaultStrategies()); } private static List defaultStrategies() { @@ -54,7 +55,7 @@ private static List defaultActions() { } public static NotifyNode formMap(Map map) { - return BaseAuditNode.formMap(map, NotifyNode.class); + return NodeMapBuilder.formMap(map, NotifyNode.class); } public static Builder builder() { diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/ParallelBranchNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/ParallelBranchNode.java new file mode 100644 index 00000000..069d5a96 --- /dev/null +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/ParallelBranchNode.java @@ -0,0 +1,168 @@ +package com.codingapi.flow.node.nodes; + +import com.codingapi.flow.node.BaseFlowNode; +import com.codingapi.flow.node.IFlowNode; +import com.codingapi.flow.node.builder.BaseNodeBuilder; +import com.codingapi.flow.record.FlowRecord; +import com.codingapi.flow.session.FlowSession; +import com.codingapi.flow.utils.RandomUtils; +import com.codingapi.flow.workflow.Workflow; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.*; + +/** + * 并行节点 + */ +public class ParallelBranchNode extends BaseFlowNode { + + public static final String NODE_TYPE = "parallel_branch"; + public static final String DEFAULT_NAME = "并行节点"; + + @Override + public String getType() { + return NODE_TYPE; + } + + public ParallelBranchNode(String id, String name) { + super(id, name); + } + + public ParallelBranchNode() { + this(RandomUtils.generateStringId(), DEFAULT_NAME); + } + + /** + * 匹配条件分支 + * + * @param nodeList 当前节点下的所有条件 + * @param flowSession 当前会话 + * @return 匹配的节点 + */ + public List filterBranches(List nodeList, FlowSession flowSession) { + Workflow workflow = flowSession.getWorkflow(); + ParallelNodeRelationHelper helper = new ParallelNodeRelationHelper(nodeList, workflow); + // 分析并行分支的结束汇聚节点 + IFlowNode overNode = helper.fetchParallelEndNode(); + if(overNode==null){ + throw new IllegalArgumentException("parallel end node is null"); + } + + // 在流程记录中记录,合并的条件信息。 + FlowRecord flowRecord = flowSession.getCurrentRecord(); + flowRecord.parallelBranchNode(overNode.getId(), nodeList.size(),RandomUtils.generateStringId()); + + return nodeList; + } + + + private static class ParallelNodeRelationHelper { + private final Workflow workflow; + private final List parallelNodes; + + public ParallelNodeRelationHelper(List parallelNodes, Workflow workflow) { + this.parallelNodes = parallelNodes; + this.workflow = workflow; + } + + public IFlowNode fetchParallelEndNode() { + if (parallelNodes.isEmpty()) { + return null; + } + if (parallelNodes.size() > 1) { + LineManager lineManager = new LineManager(); + for (IFlowNode node : parallelNodes) { + List nodeLines = this.getNodeLines(node); + lineManager.addLine(nodeLines); + } + return lineManager.fetchEndNode(workflow); + } + return parallelNodes.get(0); + } + + private List getNodeLines(IFlowNode node) { + List lines = new ArrayList<>(); + lines.add(node.getId()); + IFlowNode currentNode = node; + NodeManger nodeManger = null; + do { + nodeManger = this.nextNodes(currentNode); + currentNode = nodeManger.getCurrentNode(); + lines.add(currentNode.getId()); + } while (nodeManger.next()); + return lines; + } + + private NodeManger nextNodes(IFlowNode node) { + return new NodeManger(workflow.nextNodes(node)); + } + + private static class LineManager { + + private final List> lines = new ArrayList<>(); + + public void addLine(List line) { + lines.add(line); + } + + + public IFlowNode fetchEndNode(Workflow workflow) { + // 对线进行倒叙 + List firstLine = lines.get(0); + Collections.reverse(firstLine); + + IFlowNode flowNode = null; + for (int i = 1; i < lines.size(); i++) { + List line = lines.get(i); + if (flowNode == null) { + for (String nodeId : firstLine) { + if (line.contains(nodeId)) { + flowNode = workflow.getFlowNode(nodeId); + } + } + } + } + return flowNode; + } + + } + + + @AllArgsConstructor + private static class NodeManger { + @Getter + private final List nodes; + + public IFlowNode getCurrentNode() { + return nodes.get(0); + } + + public boolean next() { + if (nodes.isEmpty()) { + return false; + } + IFlowNode currentNode = nodes.get(0); + if (currentNode instanceof EndNode) { + return false; + } + return true; + } + } + } + + + public static ParallelBranchNode formMap(Map map) { + return BaseFlowNode.loadFromMap(map, ParallelBranchNode.class); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends BaseNodeBuilder { + public Builder() { + super(new ParallelBranchNode()); + } + } +} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/branch/RouterBranchNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/RouterBranchNode.java similarity index 56% rename from flow-engine-framework/src/main/java/com/codingapi/flow/node/branch/RouterBranchNode.java rename to flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/RouterBranchNode.java index 6bf7d99b..1ccd23e0 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/branch/RouterBranchNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/RouterBranchNode.java @@ -1,18 +1,15 @@ -package com.codingapi.flow.node.branch; +package com.codingapi.flow.node.nodes; -import com.codingapi.flow.node.BaseBranchNode; -import com.codingapi.flow.node.IFlowNode; -import com.codingapi.flow.node.builder.BranchNodeBuilder; -import com.codingapi.flow.session.FlowSession; +import com.codingapi.flow.node.BaseFlowNode; +import com.codingapi.flow.node.builder.BaseNodeBuilder; import com.codingapi.flow.utils.RandomUtils; -import java.util.List; import java.util.Map; /** * 路由分支节点 */ -public class RouterBranchNode extends BaseBranchNode { +public class RouterBranchNode extends BaseFlowNode { public static final String NODE_TYPE = "router_branch"; public static final String DEFAULT_NAME = "路由节点"; @@ -31,21 +28,15 @@ public RouterBranchNode() { this(RandomUtils.generateStringId(), DEFAULT_NAME); } - - public List matchRouters(FlowSession session){ - return null; - } - - public static RouterBranchNode formMap(Map map) { - return BaseBranchNode.formMap(map, RouterBranchNode.class); + return BaseFlowNode.loadFromMap(map, RouterBranchNode.class); } public static Builder builder() { return new Builder(); } - public static class Builder extends BranchNodeBuilder { + public static class Builder extends BaseNodeBuilder { public Builder() { super(new RouterBranchNode()); } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/StartNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/StartNode.java new file mode 100644 index 00000000..20d46695 --- /dev/null +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/StartNode.java @@ -0,0 +1,149 @@ +package com.codingapi.flow.node.nodes; + +import com.codingapi.flow.action.IFlowAction; +import com.codingapi.flow.action.PassAction; +import com.codingapi.flow.context.GatewayContext; +import com.codingapi.flow.form.permission.FormFieldPermission; +import com.codingapi.flow.node.BaseFlowNode; +import com.codingapi.flow.node.builder.BaseNodeBuilder; +import com.codingapi.flow.node.builder.IFormFieldPermissionsNode; +import com.codingapi.flow.node.builder.NodeMapBuilder; +import com.codingapi.flow.node.manager.FieldPermissionManager; +import com.codingapi.flow.operator.IFlowOperator; +import com.codingapi.flow.record.FlowRecord; +import com.codingapi.flow.script.node.NodeTitleScript; +import com.codingapi.flow.session.FlowSession; +import com.codingapi.flow.utils.RandomUtils; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 开始节点 + */ +public class StartNode extends BaseFlowNode implements IFormFieldPermissionsNode { + + public static final String NODE_TYPE = "start"; + public static final String DEFAULT_NAME = "开始节点"; + + public static final String DEFAULT_VIEW = "default"; + + /** + * 渲染视图 + */ + @Getter + @Setter + private String view; + + /** + * 节点待办标题脚本 + */ + private NodeTitleScript nodeTitleScript; + + /** + * 表单字段权限 + */ + @Setter + private List formFieldPermissions; + + + @Override + public String getType() { + return NODE_TYPE; + } + + public String generateTitle(FlowSession flowSession) { + return nodeTitleScript.execute(flowSession); + } + + + public void setNodeTitleScript(String script) { + this.nodeTitleScript = new NodeTitleScript(script); + } + + public StartNode(String id, String name, List actions, String view, NodeTitleScript nodeTitleScript, List formFieldPermissions) { + super(id, name, actions); + this.view = view; + this.nodeTitleScript = nodeTitleScript; + this.formFieldPermissions = formFieldPermissions; + } + + public StartNode() { + this(RandomUtils.generateStringId(), DEFAULT_NAME, defaultActions(), DEFAULT_VIEW, NodeTitleScript.defaultScript(), new ArrayList<>()); + } + + + @Override + public List generateCurrentRecords(FlowSession session) { + List records = new ArrayList<>(); + FlowRecord currentRecord = session.getCurrentRecord(); + IFlowOperator operator = session.getCurrentOperator(); + IFlowAction action = session.getCurrentAction(); + if (currentRecord == null) { + FlowRecord flowRecord = new FlowRecord(session.updateSession(operator), action.id(), 0); + records.add(flowRecord); + } else { + // 获取流程创建者 + IFlowOperator creatorOperator = GatewayContext.getInstance().getFlowOperator(currentRecord.getCreateOperatorId()); + FlowRecord flowRecord = new FlowRecord(session.updateSession(creatorOperator), action.id(), 0); + records.add(flowRecord); + } + return records; + } + + @Override + public FieldPermissionManager formFieldsPermissionsManager() { + return new FieldPermissionManager(formFieldPermissions); + } + + private static List defaultActions() { + List actions = new ArrayList<>(); + actions.add(new PassAction()); + return actions; + } + + public static StartNode formMap(Map map) { + StartNode startNode = BaseFlowNode.loadFromMap(map, StartNode.class); + startNode.setNodeTitleScript((String) map.get("nodeTitleScript")); + startNode.setFormFieldPermissions(NodeMapBuilder.loadFormFieldPermissions(map)); + return startNode; + } + + @Override + public Map toMap() { + Map map = super.toMap(); + map.put("name", name); + map.put("id", id); + map.put("view", view); + map.put("nodeTitleScript", nodeTitleScript.getScript()); + map.put("formFieldPermissions", formFieldPermissions); + map.put("type", getType()); + map.put("actions", actions.stream().map(IFlowAction::toMap).toList()); + return map; + } + + @Override + public void fillNewRecord(FlowSession session, FlowRecord flowRecord) { + flowRecord.setTitle(this.generateTitle(session)); + } + + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends BaseNodeBuilder { + + public Builder() { + super(new StartNode()); + } + + public Builder formFieldsPermissions(List permissions) { + node.setFormFieldPermissions(permissions); + return this; + } + } +} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/config/SubProcessNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/SubProcessNode.java similarity index 62% rename from flow-engine-framework/src/main/java/com/codingapi/flow/node/config/SubProcessNode.java rename to flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/SubProcessNode.java index e2b030a4..0eb9df55 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/config/SubProcessNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/SubProcessNode.java @@ -1,15 +1,16 @@ -package com.codingapi.flow.node.config; +package com.codingapi.flow.node.nodes; -import com.codingapi.flow.node.BaseConfigNode; -import com.codingapi.flow.node.builder.ConfigNodeBuilder; +import com.codingapi.flow.node.BaseFlowNode; +import com.codingapi.flow.node.builder.BaseNodeBuilder; import com.codingapi.flow.utils.RandomUtils; +import java.util.ArrayList; import java.util.Map; /** * 子流程 */ -public class SubProcessNode extends BaseConfigNode { +public class SubProcessNode extends BaseFlowNode { public static final String NODE_TYPE = "sub_process"; public static final String DEFAULT_NAME = "子流程"; @@ -20,7 +21,7 @@ public String getType() { } public SubProcessNode(String id, String name) { - super(id, name); + super(id, name,new ArrayList<>()); } public SubProcessNode() { @@ -28,14 +29,14 @@ public SubProcessNode() { } public static SubProcessNode formMap(Map map) { - return BaseConfigNode.formMap(map, SubProcessNode.class); + return BaseFlowNode.loadFromMap(map, SubProcessNode.class); } public static Builder builder() { return new Builder(); } - public static class Builder extends ConfigNodeBuilder { + public static class Builder extends BaseNodeBuilder { public Builder() { super(new SubProcessNode()); } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/config/TriggerNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/TriggerNode.java similarity index 62% rename from flow-engine-framework/src/main/java/com/codingapi/flow/node/config/TriggerNode.java rename to flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/TriggerNode.java index edc8e8d3..dcf93abb 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/config/TriggerNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/TriggerNode.java @@ -1,15 +1,16 @@ -package com.codingapi.flow.node.config; +package com.codingapi.flow.node.nodes; -import com.codingapi.flow.node.BaseConfigNode; -import com.codingapi.flow.node.builder.ConfigNodeBuilder; +import com.codingapi.flow.node.BaseFlowNode; +import com.codingapi.flow.node.builder.BaseNodeBuilder; import com.codingapi.flow.utils.RandomUtils; +import java.util.ArrayList; import java.util.Map; /** * 触发节点 */ -public class TriggerNode extends BaseConfigNode { +public class TriggerNode extends BaseFlowNode { public static final String NODE_TYPE = "trigger"; public static final String DEFAULT_NAME = "触发节点"; @@ -20,7 +21,7 @@ public String getType() { } public TriggerNode(String id, String name) { - super(id, name); + super(id, name,new ArrayList<>()); } public TriggerNode() { @@ -28,14 +29,14 @@ public TriggerNode() { } public static TriggerNode formMap(Map map) { - return BaseConfigNode.formMap(map, TriggerNode.class); + return BaseFlowNode.loadFromMap(map, TriggerNode.class); } public static Builder builder() { return new Builder(); } - public static class Builder extends ConfigNodeBuilder { + public static class Builder extends BaseNodeBuilder { public Builder() { super(new TriggerNode()); } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowActionRequest.java b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowActionRequest.java index eb803390..8c302f18 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowActionRequest.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowActionRequest.java @@ -39,7 +39,7 @@ public FlowAdvice toFlowAdvice(Workflow workflow, IFlowAction flowAction) { flowAdvice.setTransferOperators(GatewayContext.getInstance().findByIds(advice.getTransferOperatorIds())); } if (StringUtils.hasText(advice.getBackNodeId())) { - flowAdvice.setBackNode(workflow.getAuditNode(advice.getBackNodeId())); + flowAdvice.setBackNode(workflow.getFlowNode(advice.getBackNodeId())); } return flowAdvice; diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/record/FlowRecord.java b/flow-engine-framework/src/main/java/com/codingapi/flow/record/FlowRecord.java index ec562abc..8b30008a 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/record/FlowRecord.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/record/FlowRecord.java @@ -1,7 +1,8 @@ package com.codingapi.flow.record; -import com.codingapi.flow.operator.IFlowOperator; +import com.codingapi.flow.context.RepositoryContext; import com.codingapi.flow.session.FlowSession; +import com.codingapi.flow.utils.RandomUtils; import lombok.Getter; import lombok.Setter; import org.springframework.util.StringUtils; @@ -9,6 +10,7 @@ import java.util.Map; @Getter +@Setter public class FlowRecord { // 待办、已办 @@ -152,29 +154,80 @@ public class FlowRecord { */ private long interferedOperatorId; - public FlowRecord(FlowSession flowSession, String actionId, String processId, long fromId, int nodeOrder) { + /** + * 并行id + */ + private String parallelId; + + /** + * 并行分支节点id + */ + private String parallelBranchNodeId; + + /** + * 并行分支数量 + */ + private int parallelBranchTotal; + + + public FlowRecord(FlowSession flowSession, String actionId, int nodeOrder) { this.workCode = flowSession.getWorkCode(); this.workBackupId = flowSession.getBackupId(); this.nodeId = flowSession.getCurrentNodeId(); this.nodeType = flowSession.getCurrentNodeType(); this.formData = flowSession.getFormData().toMapData(); - this.fromId = fromId; + this.fromId = 0; this.nodeOrder = nodeOrder; - this.title = flowSession.getCurrentNode().generateTitle(flowSession); - this.processId = processId; + this.processId = RandomUtils.generateStringId(); this.createOperatorId = flowSession.getCreatedOperator().getUserId(); this.recordState = SATE_RECORD_TODO; this.actionId = actionId; this.currentOperatorId = flowSession.getCurrentOperator().getUserId(); - this.interferedOperatorId = flowSession.getCurrentOperator().entrustOperator()!=null?flowSession.getCurrentOperator().entrustOperator().getUserId():0; + this.interferedOperatorId = flowSession.getCurrentOperator().entrustOperator() != null ? flowSession.getCurrentOperator().entrustOperator().getUserId() : 0; this.advice = flowSession.getAdvice().getAdvice(); this.signKey = flowSession.getAdvice().getSignKey(); this.flowState = SATE_FLOW_RUNNING; this.createTime = System.currentTimeMillis(); - this.timeoutTime = flowSession.getCurrentNode().strategies().getTimeoutTime(); - this.mergeable = flowSession.getCurrentNode().strategies().isMergeable(); this.isInterfere = flowSession.getWorkflow().isInterfere(); this.hidden = false; + + flowSession.getCurrentNode().fillNewRecord(flowSession, this); + this.extendsRecord(flowSession.getCurrentRecord()); + + this.verify(); + } + + + public void extendsRecord(FlowRecord record) { + if (record != null) { + this.parallelBranchNodeId = record.parallelBranchNodeId; + this.parallelBranchTotal = record.parallelBranchTotal; + this.parallelId = record.parallelId; + this.fromId = record.id; + this.processId = record.processId; + } + } + + /** + * 当满足条件以后需要清空并行的记录数据 + */ + public void clearParallel() { + this.parallelBranchNodeId = null; + this.parallelBranchTotal = 0; + this.parallelId = null; + } + + + /** + * 并行分支节点 + * + * @param parallelBranchNodeId 并行分支节点id + * @param parallelBranchCount 并行分支数量 + */ + public void parallelBranchNode(String parallelBranchNodeId, int parallelBranchCount, String parallelId) { + this.parallelBranchNodeId = parallelBranchNodeId; + this.parallelBranchTotal = parallelBranchCount; + this.parallelId = parallelId; } public void verify() { @@ -202,9 +255,6 @@ public void verify() { if (createOperatorId <= 0) { throw new IllegalArgumentException("createOperator is null"); } - if (currentOperatorId <= 0) { - throw new IllegalArgumentException("currentOperatorId is null"); - } if (actionId == null) { throw new IllegalArgumentException("actionId is null"); } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/repository/FlowRecordRepository.java b/flow-engine-framework/src/main/java/com/codingapi/flow/repository/FlowRecordRepository.java index ffb47332..172b5e0f 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/repository/FlowRecordRepository.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/repository/FlowRecordRepository.java @@ -14,7 +14,8 @@ public interface FlowRecordRepository { void delete(FlowRecord flowRecord); - List findRecordsByFromId(long fromId); + List findRecordsByFromIdAndNodeId(long fromId,String nodeId); List findRecordsByProcessId(String processId); + } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/repository/ParallelBranchRepository.java b/flow-engine-framework/src/main/java/com/codingapi/flow/repository/ParallelBranchRepository.java new file mode 100644 index 00000000..b49225d8 --- /dev/null +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/repository/ParallelBranchRepository.java @@ -0,0 +1,8 @@ +package com.codingapi.flow.repository; + +public interface ParallelBranchRepository { + + int getTriggerCount(String parallelId); + + void addTriggerCount(String parallelId); +} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/script/action/RejectActionScript.java b/flow-engine-framework/src/main/java/com/codingapi/flow/script/action/RejectActionScript.java index 44acd962..599f0429 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/script/action/RejectActionScript.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/script/action/RejectActionScript.java @@ -7,12 +7,13 @@ /** * 拒绝脚本 - * 拒绝,拒绝时需要根据拒绝的配置流程来设置,退回上级节点、退回指定节点、终止流程 + * 拒绝,拒绝时需要根据拒绝的配置流程来设置,退回指定节点、终止流程 */ @AllArgsConstructor public class RejectActionScript { - public static final String SCRIPT_DEFAULT = "def run(session){return new com.codingapi.flow.script.action.RejectActionScript.RejectResult('RETURN_PREV')}"; + public static final String SCRIPT_START = "def run(session){return new com.codingapi.flow.script.action.RejectActionScript.RejectResult(session.getStartNode().getId())}"; + public static final String SCRIPT_TERMINATE = "def run(session){return new com.codingapi.flow.script.action.RejectActionScript.RejectResult(\"TERMINATE\")}"; @Getter private final String script; @@ -22,16 +23,21 @@ public RejectResult execute(FlowSession session) { } /** - * 退回至上一流程 + * 退回至发起节点 */ - public static RejectActionScript defaultScript() { - return new RejectActionScript(SCRIPT_DEFAULT); + public static RejectActionScript startScript() { + return new RejectActionScript(SCRIPT_START); + } + + /** + * 终止流程 + */ + public static RejectActionScript terminateScript() { + return new RejectActionScript(SCRIPT_TERMINATE); } public enum RejectType { - // 退回上级节点 - RETURN_PREV, // 退回指定节点 RETURN_NODE, // 终止流程 @@ -43,11 +49,6 @@ public static class RejectResult { private final RejectType type; private String nodeId; - - public boolean isReturnPrev() { - return type == RejectType.RETURN_PREV; - } - public boolean isReturnNode() { return type == RejectType.RETURN_NODE; } @@ -57,9 +58,7 @@ public boolean isTerminate() { } public RejectResult(String result) { - if (result.equals("RETURN_PREV")) { - this.type = RejectType.RETURN_PREV; - } else if (result.equals("TERMINATE")) { + if (result.equals("TERMINATE")) { this.type = RejectType.TERMINATE; } else { this.type = RejectType.RETURN_NODE; diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/service/FlowService.java b/flow-engine-framework/src/main/java/com/codingapi/flow/service/FlowService.java index b3765e88..55ae6850 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/service/FlowService.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/service/FlowService.java @@ -4,15 +4,18 @@ import com.codingapi.flow.pojo.request.FlowActionRequest; import com.codingapi.flow.gateway.FlowOperatorGateway; import com.codingapi.flow.repository.FlowRecordRepository; +import com.codingapi.flow.repository.ParallelBranchRepository; import com.codingapi.flow.repository.WorkflowBackupRepository; import com.codingapi.flow.repository.WorkflowRepository; import com.codingapi.flow.service.impl.FlowCreateService; import com.codingapi.flow.service.impl.FlowActionService; import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Configuration; /** * 流程服务 */ +@Configuration @AllArgsConstructor public class FlowService { @@ -20,15 +23,16 @@ public class FlowService { private final FlowOperatorGateway flowOperatorGateway; private final FlowRecordRepository flowRecordRepository; private final WorkflowBackupRepository workflowBackupRepository; + private final ParallelBranchRepository parallelBranchRepository; public void create(FlowCreateRequest request) { - FlowCreateService flowCreateService = new FlowCreateService(request, flowOperatorGateway, flowRecordRepository, workflowRepository, workflowBackupRepository); + FlowCreateService flowCreateService = new FlowCreateService(request, flowOperatorGateway, flowRecordRepository, workflowRepository, workflowBackupRepository,parallelBranchRepository); flowCreateService.create(); } public void action(FlowActionRequest request) { - FlowActionService flowActionService = new FlowActionService(request, flowOperatorGateway, flowRecordRepository, workflowBackupRepository); + FlowActionService flowActionService = new FlowActionService(request, flowOperatorGateway, flowRecordRepository, workflowBackupRepository,parallelBranchRepository); flowActionService.action(); } } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowActionService.java b/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowActionService.java index 7a4f979a..0ddc0ea9 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowActionService.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowActionService.java @@ -1,29 +1,22 @@ package com.codingapi.flow.service.impl; import com.codingapi.flow.action.IFlowAction; -import com.codingapi.flow.action.PassAction; import com.codingapi.flow.backup.WorkflowBackup; -import com.codingapi.flow.event.FlowRecordDoneEvent; -import com.codingapi.flow.event.FlowRecordFinishEvent; -import com.codingapi.flow.event.FlowRecordTodoEvent; -import com.codingapi.flow.event.IFlowEvent; +import com.codingapi.flow.context.RepositoryContext; import com.codingapi.flow.form.FormData; import com.codingapi.flow.gateway.FlowOperatorGateway; -import com.codingapi.flow.node.fixed.EndNode; -import com.codingapi.flow.node.IAuditNode; -import com.codingapi.flow.node.manager.FieldPermissionManager; +import com.codingapi.flow.node.IFlowNode; import com.codingapi.flow.operator.IFlowOperator; import com.codingapi.flow.pojo.request.FlowActionRequest; import com.codingapi.flow.record.FlowRecord; import com.codingapi.flow.repository.FlowRecordRepository; +import com.codingapi.flow.repository.ParallelBranchRepository; import com.codingapi.flow.repository.WorkflowBackupRepository; import com.codingapi.flow.session.FlowAdvice; import com.codingapi.flow.session.FlowSession; import com.codingapi.flow.workflow.Workflow; -import com.codingapi.springboot.framework.event.EventPusher; import lombok.AllArgsConstructor; -import java.util.ArrayList; import java.util.List; @AllArgsConstructor @@ -33,8 +26,13 @@ public class FlowActionService { private final FlowOperatorGateway flowOperatorGateway; private final FlowRecordRepository flowRecordRepository; private final WorkflowBackupRepository workflowBackupRepository; + private final ParallelBranchRepository parallelBranchRepository; public void action() { + RepositoryContext.getInstance().setFlowRecordRepository(flowRecordRepository); + RepositoryContext.getInstance().setFlowOperatorGateway(flowOperatorGateway); + RepositoryContext.getInstance().setParallelBranchRepository(parallelBranchRepository); + request.verify(); // 验证当前用户 IFlowOperator currentOperator = flowOperatorGateway.get(request.getAdvice().getOperatorId()); @@ -60,11 +58,11 @@ public void action() { } Workflow workflow = workflowBackup.toWorkflow(); - IAuditNode currentNode = workflow.getAuditNode(flowRecord.getNodeId()); + IFlowNode currentNode = workflow.getFlowNode(flowRecord.getNodeId()); if (currentNode == null) { throw new IllegalArgumentException("currentNode not exist"); } - IFlowAction flowAction = currentNode.actions().getActionById(request.getAdvice().getActionId()); + IFlowAction flowAction = currentNode.actionManager().getActionById(request.getAdvice().getActionId()); if (flowAction == null) { throw new IllegalArgumentException("action not exist"); } @@ -72,75 +70,15 @@ public void action() { // 构建表单数据 FormData formData = new FormData(workflow.getForm()); formData.reset(request.getFormData()); + FlowAdvice flowAdvice = request.toFlowAdvice(workflow, flowAction); - FlowAdvice flowAdvice = request.toFlowAdvice(workflow,flowAction); - // 数据验证 - FieldPermissionManager fieldPermissionManager = currentNode.formFieldsPermissions(); - fieldPermissionManager.verifyFormData(workflow.getForm(),flowRecord.getFormData(),request.getFormData()); - - // 节点验证 - currentNode.verifyFlowAdvice(flowAdvice); + List currentRecords = RepositoryContext.getInstance().findRecordsByFromIdAndNodeId(flowRecord.getFromId(), flowRecord.getNodeId()); + FlowSession session = new FlowSession(currentOperator, workflow, currentNode, flowAction, formData, flowRecord, currentRecords, workflowBackup.getId(), flowAdvice); - List currentRecords = flowRecordRepository.findRecordsByFromId(flowRecord.getFromId()); - FlowSession session = new FlowSession(currentOperator, workflow.getForm(), workflow, currentNode, formData, workflowBackup.getId(),flowAdvice); + currentNode.verifySession(session); - List flowEvents = new ArrayList<>(); + flowAction.run(session); - // 判断当前节点是否已经完成 - boolean done = flowAction.isDone(session, flowRecord, currentRecords); - if (done) { - List records = flowAction.trigger(session, flowRecord); - if (!records.isEmpty()) { - for (FlowRecord record : records) { - if (record.isShow()) { - flowEvents.add(new FlowRecordTodoEvent(record)); - } - } - } - flowRecord.update(formData.toMapData(), request.getAdvice().getAdvice(), request.getAdvice().getSignKey(), true); - // 判断是否结束 - if (records.size() == 1) { - FlowRecord record = records.get(0); - if (record.isNodeType(EndNode.NODE_TYPE)) { - boolean flowFinish = flowAction instanceof PassAction; - // 添加当前节点到记录中 - records.add(flowRecord); - // 添加历史记录到记录中 - List historyRecords = flowRecordRepository.findRecordsByProcessId(flowRecord.getProcessId()); - records.addAll(historyRecords); - // 设置状态为完成 - records.forEach(item -> { - item.finish(flowFinish); - }); - - // 流程是否正常结束 - if (flowFinish) { - flowEvents.add(new FlowRecordFinishEvent(record)); - } - } - // 添加流程结束事件 - flowEvents.add(new FlowRecordDoneEvent(record)); - } - flowRecordRepository.saveAll(records); - } else { - // 判断是否为串行多操作者 - if (currentNode.strategies().isSequenceMultiOperator()) { - int nextRecordNodeOrder = flowRecord.getNodeOrder() + 1; - FlowRecord nextRecord = currentRecords.stream().filter(record -> record.getNodeOrder() == nextRecordNodeOrder).findFirst().orElse(null); - if (nextRecord != null) { - // 展示下一个审批人的待办 - nextRecord.show(); - flowEvents.add(new FlowRecordTodoEvent(nextRecord)); - flowRecordRepository.save(nextRecord); - } - } - flowRecord.update(formData.toMapData(), request.getAdvice().getAdvice(), request.getAdvice().getSignKey(), false); - flowRecordRepository.save(flowRecord); - } - - // 推送待办事件 - for (IFlowEvent event : flowEvents) { - EventPusher.push(event); - } } } + diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowCreateService.java b/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowCreateService.java index 91093ff8..2d26bdcb 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowCreateService.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowCreateService.java @@ -2,22 +2,21 @@ import com.codingapi.flow.action.IFlowAction; import com.codingapi.flow.backup.WorkflowBackup; +import com.codingapi.flow.context.RepositoryContext; import com.codingapi.flow.event.FlowRecordStartEvent; import com.codingapi.flow.event.FlowRecordTodoEvent; import com.codingapi.flow.event.IFlowEvent; import com.codingapi.flow.form.FormData; import com.codingapi.flow.gateway.FlowOperatorGateway; -import com.codingapi.flow.node.IAuditNode; -import com.codingapi.flow.node.IFlowNode; -import com.codingapi.flow.node.manager.OperatorManager; +import com.codingapi.flow.node.nodes.StartNode; import com.codingapi.flow.operator.IFlowOperator; import com.codingapi.flow.pojo.request.FlowCreateRequest; import com.codingapi.flow.record.FlowRecord; import com.codingapi.flow.repository.FlowRecordRepository; +import com.codingapi.flow.repository.ParallelBranchRepository; import com.codingapi.flow.repository.WorkflowBackupRepository; import com.codingapi.flow.repository.WorkflowRepository; import com.codingapi.flow.session.FlowSession; -import com.codingapi.flow.utils.RandomUtils; import com.codingapi.flow.workflow.Workflow; import com.codingapi.springboot.framework.event.EventPusher; import lombok.AllArgsConstructor; @@ -33,8 +32,14 @@ public class FlowCreateService { private final FlowRecordRepository flowRecordRepository; private final WorkflowRepository workflowRepository; private final WorkflowBackupRepository workflowBackupRepository; + private final ParallelBranchRepository parallelBranchRepository; public void create() { + + RepositoryContext.getInstance().setFlowRecordRepository(flowRecordRepository); + RepositoryContext.getInstance().setFlowOperatorGateway(flowOperatorGateway); + RepositoryContext.getInstance().setParallelBranchRepository(parallelBranchRepository); + request.verify(); Workflow workflow = workflowRepository.get(request.getWorkId()); if (workflow == null) { @@ -56,23 +61,21 @@ public void create() { FormData formData = new FormData(workflow.getForm()); formData.reset(request.getFormData()); - IAuditNode currentNode = workflow.getStartNode(); - FlowSession session = new FlowSession(currentOperator, workflow.getForm(), workflow, currentNode, formData, workflowBackup.getId()); + StartNode currentNode = (StartNode) workflow.getStartNode(); + IFlowAction action = currentNode.actionManager().getActionById(request.getAdvice().getActionId()); + FlowSession session = FlowSession.startSession(currentOperator, workflow, currentNode, action, formData, workflowBackup.getId()); - OperatorManager currentOperators = currentNode.operators(session); - if (!currentOperators.match(currentOperator)) { - throw new IllegalArgumentException("node operator not match"); - } + currentNode.verifySession(session); - IFlowAction action = currentNode.actions().getActionById(request.getAdvice().getActionId()); + List flowRecords = currentNode.generateCurrentRecords(session); - FlowRecord flowRecord = new FlowRecord(session, action.id(), RandomUtils.generateStringId(), 0, 0); - flowRecord.verify(); - flowRecordRepository.save(flowRecord); + flowRecordRepository.saveAll(flowRecords); List events = new ArrayList<>(); - events.add(new FlowRecordStartEvent(flowRecord)); - events.add(new FlowRecordTodoEvent(flowRecord)); + for (FlowRecord flowRecord : flowRecords) { + events.add(new FlowRecordStartEvent(flowRecord)); + events.add(new FlowRecordTodoEvent(flowRecord)); + } // 推送事件 for (IFlowEvent event : events) { diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/session/FlowSession.java b/flow-engine-framework/src/main/java/com/codingapi/flow/session/FlowSession.java index 1b2c2ad0..722cf5a0 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/session/FlowSession.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/session/FlowSession.java @@ -1,10 +1,10 @@ package com.codingapi.flow.session; +import com.codingapi.flow.action.IFlowAction; import com.codingapi.flow.form.FormData; -import com.codingapi.flow.form.FormMeta; -import com.codingapi.flow.node.IAuditNode; import com.codingapi.flow.node.IFlowNode; import com.codingapi.flow.operator.IFlowOperator; +import com.codingapi.flow.record.FlowRecord; import com.codingapi.flow.workflow.Workflow; import lombok.Getter; @@ -20,10 +20,6 @@ public class FlowSession { * 当前操作者 */ private final IFlowOperator currentOperator; - /** - * 当前流程表单 - */ - private final FormMeta formMeta; /** * 当前流程设计 */ @@ -31,7 +27,22 @@ public class FlowSession { /** * 当前流程节点 */ - private final IAuditNode currentNode; + private final IFlowNode currentNode; + + /** + * 当前流程动作 + */ + private final IFlowAction currentAction; + + /** + * 当前审批流程记录 + */ + private final FlowRecord currentRecord; + + /** + * 当前节点的流程记录 + */ + private final List currentNodeRecords; /** * 当前流程表单数据 @@ -48,40 +59,67 @@ public class FlowSession { private final FlowAdvice advice; - public FlowSession(IFlowOperator currentOperator, FormMeta formMeta, Workflow workflow, IAuditNode currentNode, FormData formData, long backupId) { - this(currentOperator, formMeta, workflow, currentNode, formData, backupId, FlowAdvice.nullFlowAdvice()); - } - - public FlowSession(IFlowOperator currentOperator, FormMeta formMeta, Workflow workflow, IAuditNode currentNode, FormData formData, long backupId, FlowAdvice advice) { + public FlowSession(IFlowOperator currentOperator, + Workflow workflow, + IFlowNode currentNode, + IFlowAction currentAction, + FormData formData, + FlowRecord currentRecord, + List currentNodeRecords, + long backupId, + FlowAdvice advice) { this.currentOperator = currentOperator; - this.formMeta = formMeta; this.workflow = workflow; + this.currentAction = currentAction; this.currentNode = currentNode; + this.currentRecord = currentRecord; + this.currentNodeRecords = currentNodeRecords; this.formData = formData; this.backupId = backupId; this.advice = advice; } + /** - * 获取流程的创建者 + * 构建开始会话 + * @param currentOperator 当前操作者 + * @param workflow 流程设计 + * @param currentNode 当前节点 + * @param currentAction 当前动作 + * @param formData 表单数据 + * @param backupId 流程备份id + * @return 新的会话 */ - public IFlowOperator getCreatedOperator() { - return workflow.getCreatedOperator(); + public static FlowSession startSession(IFlowOperator currentOperator, + Workflow workflow, + IFlowNode currentNode, + IFlowAction currentAction, + FormData formData, + long backupId) { + return new FlowSession(currentOperator, workflow, currentNode, currentAction, formData, null, null, backupId, new FlowAdvice()); } + /** - * 获取流程的开始节点 + * 获取流程开始节点 */ public IFlowNode getStartNode() { return workflow.getStartNode(); } - public String getWorkCode() { - return workflow.getCode(); + + /** + * 获取流程的创建者 + */ + public IFlowOperator getCreatedOperator() { + return workflow.getCreatedOperator(); } - public String getWorkTitle() { - return workflow.getTitle(); + /** + * 获取流程设计编号 + */ + public String getWorkCode() { + return workflow.getCode(); } public String getCurrentNodeId() { @@ -92,19 +130,43 @@ public String getCurrentNodeType() { return currentNode.getType(); } - public List nextNodes() { - return workflow.nextNodes(this); + /** + * 获取下一节点列表 + * @return 下一节点列表 + */ + public List matchNextNodes() { + List nodeList = workflow.nextNodes(this.getCurrentNode()); + if(!nodeList.isEmpty() && nodeList.size()>1){ + IFlowNode currentNode = nodeList.get(0); + return currentNode.filterBranches(nodeList,this); + } + return nodeList; } + /** + * 获取表单数据 + * @param fieldName 字段名称 + * @return 表单数据 + */ public Object getFormData(String fieldName) { return formData.getDataBody().get(fieldName); } - public FlowSession updateSession(IAuditNode currentNode) { - return new FlowSession(currentOperator, formMeta, workflow, currentNode, formData, backupId, advice); + /** + * 更新会话 + * @param currentNode 当前节点 + * @return 新的会话 + */ + public FlowSession updateSession(IFlowNode currentNode) { + return new FlowSession(currentOperator, workflow, currentNode, currentAction, formData, currentRecord, currentNodeRecords, backupId, advice); } + /** + * 更新会话 + * @param currentOperator 当前操作者 + * @return 新的会话 + */ public FlowSession updateSession(IFlowOperator currentOperator) { - return new FlowSession(currentOperator, formMeta, workflow, currentNode, formData, backupId, advice); + return new FlowSession(currentOperator, workflow, currentNode, currentAction, formData, currentRecord, currentNodeRecords, backupId, advice); } } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/workflow/Workflow.java b/flow-engine-framework/src/main/java/com/codingapi/flow/workflow/Workflow.java index 8fa00de2..0ee9ad94 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/workflow/Workflow.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/workflow/Workflow.java @@ -4,16 +4,12 @@ import com.codingapi.flow.context.GatewayContext; import com.codingapi.flow.edge.FlowEdge; import com.codingapi.flow.form.FormMeta; -import com.codingapi.flow.node.IAuditNode; -import com.codingapi.flow.node.IBranchNode; -import com.codingapi.flow.node.IConfigNode; import com.codingapi.flow.node.IFlowNode; -import com.codingapi.flow.node.audit.StartNode; -import com.codingapi.flow.node.fixed.EndNode; import com.codingapi.flow.node.factory.NodeFactory; +import com.codingapi.flow.node.nodes.EndNode; +import com.codingapi.flow.node.nodes.StartNode; import com.codingapi.flow.operator.IFlowOperator; import com.codingapi.flow.script.node.OperatorMatchScript; -import com.codingapi.flow.session.FlowSession; import com.codingapi.flow.utils.RandomUtils; import lombok.AllArgsConstructor; import lombok.Getter; @@ -273,7 +269,7 @@ private void verifyNodes() { } for (IFlowNode node : nodes) { - node.verify(form); + node.verifyNode(form); } } @@ -285,7 +281,7 @@ private void verifyEdges() { } IFlowNode startNode = this.nodes.stream().filter(node -> node instanceof StartNode).findFirst().get(); - List nextNodes = edgeNext(startNode); + List nextNodes = nextNodes(startNode); for (IFlowNode nextNode : nextNodes) { this.verifyNextEdge(nextNode); } @@ -295,7 +291,7 @@ private void verifyNextEdge(IFlowNode node) { if (node instanceof EndNode) { return; } else { - List nextNodes = edgeNext(node); + List nextNodes = nextNodes(node); if (nextNodes.isEmpty()) { throw new IllegalArgumentException("workflow edges must have one end edge"); } @@ -305,57 +301,24 @@ private void verifyNextEdge(IFlowNode node) { } } - - public List edgeNext(IFlowNode node) { + public List nextNodes(IFlowNode node) { return edges.stream().filter(edge -> edge.getFrom().equals(node.getId())) .map(edge -> nodes.stream().filter(item -> item.getId().equals(edge.getTo())).findFirst().get()).toList(); } - public List nextNodes(FlowSession session) { - List nodeList = edgeNext(session.getCurrentNode()); - return this.loadNextAuditNodes(nodeList, session); - } - - - private List loadNextAuditNodes(List nodeList, FlowSession session) { - List auditNodeList = new ArrayList<>(); - for (IFlowNode node : nodeList) { - // 审批节点 - if (node instanceof IAuditNode) { - auditNodeList.add((IAuditNode) node); - } - // 配置节点 - if (node instanceof IConfigNode) { - ((IConfigNode) node).execute(session); - } - if (node instanceof IBranchNode) { - if (((IBranchNode) node).match(session)) { - List nextNodes = node.nextNodes(session); - auditNodeList.addAll(this.loadNextAuditNodes(nextNodes, session)); - } - } - } - return auditNodeList; - } - - - public IAuditNode getAuditNode(String nodeId) { + public IFlowNode getFlowNode(String nodeId) { return nodes.stream() - .filter(node -> node instanceof IAuditNode) .filter(node -> node.getId().equals(nodeId)) - .map(node -> (IAuditNode) node) .findFirst().orElse(null); } - public IAuditNode getStartNode() { + public IFlowNode getStartNode() { return nodes.stream().filter(node -> node instanceof StartNode) - .map(node -> (IAuditNode) node) .findFirst().orElse(null); } - public IAuditNode getEndNode() { + public IFlowNode getEndNode() { return nodes.stream().filter(node -> node instanceof EndNode) - .map(node -> (IAuditNode) node) .findFirst().orElse(null); } } diff --git a/flow-engine-framework/src/test/Design.md b/flow-engine-framework/src/test/Design.md new file mode 100644 index 00000000..96dc7b7d --- /dev/null +++ b/flow-engine-framework/src/test/Design.md @@ -0,0 +1,595 @@ +# 关键设计介绍 + +## 一、核心类设计 + +### 1. 流程定义层 (Workflow Layer) + +#### Workflow +- **位置**: `com.codingapi.flow.workflow.Workflow` +- **职责**: 流程定义的顶层容器,包含流程的完整定义信息 +- **核心属性**: + - `id`: 流程唯一标识 + - `code`: 流程编号 + - `title`: 流程名称 + - `form`: 流程表单定义 (`FormMeta`) + - `nodes`: 节点列表 (`List`) + - `edges`: 节点连接关系 (`List`) + - `operatorCreateScript`: 创建者匹配脚本 + - `isInterfere`: 是否开启干预 + - `isRevoke`: 是否开启撤销 +- **核心方法**: + - `verify()`: 验证流程定义的合法性 + - `nextNodes(IFlowNode)`: 获取指定节点的后续节点 + - `getStartNode()`: 获取开始节点 + - `getEndNode()`: 获取结束节点 + - `toJson()`: 序列化为JSON + - `formJson()`: 从JSON反序列化 + +#### WorkflowBuilder +- **位置**: `com.codingapi.flow.workflow.WorkflowBuilder` +- **职责**: 使用Builder模式构建Workflow对象 +- **设计模式**: Builder模式 +- **用法**: `WorkflowBuilder.builder().title("请假流程").node(startNode).build()` + +--- + +### 2. 节点层 (Node Layer) + +#### IFlowNode +- **位置**: `com.codingapi.flow.node.IFlowNode` +- **职责**: 所有流程节点的顶层接口,定义节点生命周期方法 +- **核心方法**(按执行顺序): + 1. `verifySession(FlowSession)`: 验证会话参数 + 2. `continueTrigger(FlowSession)`: 判断是否继续执行后续节点 + 3. `generateCurrentRecords(FlowSession)`: 生成当前节点的流程记录 + 4. `isDone(FlowSession)`: 判断节点是否完成 + 5. `fillNewRecord(FlowSession, FlowRecord)`: 填充新记录数据 + 6. `filterBranches()`: 过滤条件分支 + 7. `actionManager()`: 获取动作管理器 + +#### BaseFlowNode +- **位置**: `com.codingapi.flow.node.BaseFlowNode` +- **职责**: 所有节点的抽象基类,实现IFlowNode的默认行为 +- **核心属性**: `id`, `name`, `order`, `actions` +- **核心方法**: + - `isWaitParallelRecord()`: 判断是否等待并行节点汇聚 + +#### BaseAuditNode +- **位置**: `com.codingapi.flow.node.BaseAuditNode` +- **职责**: 审批类节点的抽象基类(ApprovalNode、HandleNode) +- **核心属性**: + - `operatorScript`: 审批人加载脚本 + - `nodeTitleScript`: 节点标题脚本 + - `errorTriggerScript`: 异常触发脚本 + - `formFieldPermissions`: 表单字段权限 + - `nodeStrategies`: 节点策略列表 +- **核心方法**: + - `operators()`: 获取操作者管理器 + - `strategies()`: 获取策略管理器 + - `generateTitle()`: 生成节点标题 + +#### 节点类型一览 (11种) + +| 节点类型 | 类名 | NODE_TYPE | 说明 | +|---------|------|-----------|------| +| 开始节点 | StartNode | `start` | 流程起点 | +| 结束节点 | EndNode | `end` | 流程终点 | +| 审批节点 | ApprovalNode | `approval` | 需要审批的任务节点 | +| 办理节点 | HandleNode | `handle` | 需要办理的任务节点 | +| 条件分支 | BranchNodeBranchNode | `condition_branch` | 按条件路由 | +| 并行分支 | ParallelBranchNode | `parallel_branch` | 并行执行多个分支 | +| 路由分支 | RouterBranchNode | `router_branch` | 普通路由节点 | +| 包容分支 | InclusiveBranchNode | `inclusive_branch` | 包容性分支 | +| 通知节点 | NotifyNode | `notify` | 发送通知 | +| 延迟节点 | DelayNode | `delay` | 延迟执行 | +| 触发节点 | TriggerNode | `trigger` | 事件触发 | +| 子流程节点 | SubProcessNode | `sub_process` | 嵌套子流程 | + +--- + +### 3. 动作层 (Action Layer) + +#### IFlowAction +- **位置**: `com.codingapi.flow.action.IFlowAction` +- **职责**: 节点上可执行的动作接口 +- **核心方法**: + - `type()`: 动作类型 + - `run(FlowSession)`: 执行动作的主入口 + - `generateRecords()`: 生成流程记录 + +#### BaseAction +- **位置**: `com.codingapi.flow.action.BaseAction` +- **职责**: 动作的抽象基类 +- **核心方法**: + - `triggerNode()`: 递归触发后续节点 + +#### 动作类型 + +| 动作类 | ActionType | 说明 | +|-------|------------|------| +| PassAction | `PASS` | 通过 | +| RejectAction | `REJECT` | 驳回 | +| SaveAction | `SAVE` | 保存 | +| ReturnAction | `RETURN` | 退回 | +| TransferAction | `TRANSFER` | 转办 | +| AddAuditAction | `ADD_AUDIT` | 加签 | +| DelegateAction | `DELEGATE` | 委托 | +| CustomAction | `CUSTOM` | 自定义 | + +--- + +### 4. 流程记录层 (Record Layer) + +#### FlowRecord +- **位置**: `com.codingapi.flow.record.FlowRecord` +- **职责**: 流程执行过程中的每一条记录 +- **状态定义**: + - `SATE_RECORD_TODO = 0`: 待办 + - `SATE_RECORD_DONE = 1`: 已办 + - `SATE_FLOW_RUNNING = 0`: 运行中 + - `SATE_FLOW_DONE = 1`: 已完成 + - `SATE_FLOW_FINISH = 2`: 终止 + - `SATE_FLOW_ERROR = 3`: 异常 + - `SATE_FLOW_DELETE = 4`: 删除 +- **核心属性**: + - `processId`: 流程实例ID(每次启动生成) + - `nodeId`: 当前节点ID + - `currentOperatorId`: 当前审批人ID + - `formData`: 表单数据 + - `parallelId`: 并行分支ID + - `parallelBranchNodeId`: 并行分支节点ID + - `parallelBranchTotal`: 并行分支总数 + +--- + +### 5. 会话层 (Session Layer) + +#### FlowSession +- **位置**: `com.codingapi.flow.session.FlowSession` +- **职责**: 流程执行的上下文会话对象 +- **核心属性**: + - `currentOperator`: 当前操作者 + - `workflow`: 流程定义 + - `currentNode`: 当前节点 + - `currentAction`: 当前动作 + - `currentRecord`: 当前记录 + - `formData`: 表单数据 + - `advice`: 审批意见 +- **核心方法**: + - `matchNextNodes()`: 匹配后续节点 + - `updateSession()`: 更新会话 + +#### FlowAdvice +- **位置**: `com.codingapi.flow.session.FlowAdvice` +- **职责**: 封装审批操作的相关参数 +- **核心属性**: `advice`(审批意见), `signKey`(签名), `action`(动作), `backNode`(退回节点), `transferOperators`(转办人员) + +--- + +### 6. 管理器层 (Manager Layer) + +#### ActionManager +- **位置**: `com.codingapi.flow.node.manager.ActionManager` +- **职责**: 管理节点的动作列表 +- **核心方法**: `getActionById(String)` + +#### OperatorManager +- **位置**: `com.codingapi.flow.node.manager.OperatorManager` +- **职责**: 管理节点的操作者列表 +- **核心方法**: `match(IFlowOperator)` + +#### FieldPermissionManager +- **位置**: `com.codingapi.flow.node.manager.FieldPermissionManager` +- **职责**: 管理表单字段权限 + +#### StrategyManager +- **位置**: `com.codingapi.flow.node.manager.StrategyManager` +- **职责**: 管理节点策略(多人审批、超时、退回等) + +--- + +### 7. 策略层 (Strategy Layer) + +#### INodeStrategy +- **位置**: `com.codingapi.flow.strategy.INodeStrategy` +- **职责**: 节点策略的顶层接口 + +#### 策略类型 + +| 策略类 | 说明 | +|-------|------| +| MultiOperatorAuditStrategy | 多人审批策略(顺序/或签/并签/随机) | +| TimeoutStrategy | 超时策略 | +| SameOperatorAuditStrategy | 同一操作者审批策略 | +| RecordMergeStrategy | 记录合并策略 | +| ResubmitStrategy | 重新提交策略 | +| AdviceStrategy | 审批意见策略 | + +--- + +### 8. 脚本层 (Script Layer) + +#### ScriptRuntimeContext +- **位置**: `com.codingapi.flow.script.runtime.ScriptRuntimeContext` +- **职责**: Groovy脚本运行时环境 +- **核心方法**: `execute(String method, String script, ...)` + +#### 脚本类型 + +| 脚本类 | 说明 | +|-------|------| +| OperatorMatchScript | 操作者匹配脚本 | +| OperatorLoadScript | 操作者加载脚本 | +| NodeTitleScript | 节点标题脚本 | +| ConditionScript | 条件判断脚本 | +| ErrorTriggerScript | 异常触发脚本 | + +--- + +## 二、流程生命周期 + +### 1. 流程创建阶段 (FlowCreateService) + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ FlowCreateService │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 1. 验证请求参数 │ + │ request.verify() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 2. 获取流程定义 │ + │ workflowRepository.get() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 3. 验证流程定义 │ + │ workflow.verify() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 4. 创建/获取备份 │ + │ WorkflowBackup │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 5. 验证创建者权限 │ + │ matchCreatedOperator() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 6. 构建表单数据 │ + │ new FormData() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 7. 创建开始会话 │ + │ FlowSession.startSession() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 8. 验证会话参数 │ + │ verifySession() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 9. 生成流程记录 │ + │ generateCurrentRecords() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 10. 保存记录 │ + │ flowRecordRepository.saveAll() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 11. 推送事件 │ + │ FlowRecordStartEvent │ + │ FlowRecordTodoEvent │ + └─────────────────┘ +``` + +--- + +### 2. 流程执行阶段 (FlowActionService) + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ FlowActionService │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 1. 验证请求参数 │ + │ request.verify() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 2. 验证操作者 │ + │ flowOperatorGateway.get() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 3. 获取流程记录 │ + │ flowRecordRepository.get() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 4. 验证记录状态 │ + │ isTodo() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 5. 加载流程定义 │ + │ workflowBackup.toWorkflow() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 6. 获取当前节点 │ + │ workflow.getFlowNode() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 7. 获取动作对象 │ + │ actionManager.getActionById() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 8. 构建表单数据 │ + │ new FormData() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 9. 创建执行会话 │ + │ new FlowSession() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 10. 验证会话参数 │ + │ verifySession() │ + └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 11. 执行动作 │ + │ flowAction.run(session) │ + └─────────────────┘ +``` + +--- + +### 3. 节点生命周期 (Node Lifecycle) + +``` + ┌─────────────────────────────────────────┐ + │ 节点生命周期 │ + └─────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 1. verifySession(session) │ + │ 验证会话参数是否满足节点要求 │ + └───────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 2. continueTrigger(session) │ + │ 判断是否继续执行后续节点 │ + │ true: 继续执行下一节点 │ + │ false: 执行步骤3 │ + └───────────────────────────────────────────┘ + │ │ + true │ │ false + ▼ ▼ + ┌─────────────────────┐ ┌─────────────────────────┐ + │ 递归执行下一节点 │ │ 3. generateCurrentRecords() │ + │ │ │ 生成当前节点的流程记录 │ + └─────────────────────┘ └─────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 4. fillNewRecord(session, record) │ + │ 填充新记录的数据 │ + └───────────────────────────────────────────┘ +``` + +--- + +### 4. 节点执行流程 (以PassAction为例) + +``` + ┌─────────────────────────────────────────┐ + │ PassAction.run() │ + └─────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 1. 判断节点是否完成 │ + │ currentNode.isDone(session) │ + └───────────────────────────────────────────┘ + │ │ + done │ │ not done + ▼ ▼ + ┌─────────────────────┐ ┌─────────────────────┐ + │ 更新当前记录为已办 │ │ 更新当前记录, │ + │ record.update(...) │ │ 保持待办状态 │ + └─────────────────────┘ └─────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 2. 生成后续记录 │ + │ generateRecords(session) │ + └───────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 3. 触发后续节点 │ + │ triggerNode(session, callback) │ + └───────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 4. 获取下一节点列表 │ + │ session.matchNextNodes() │ + └───────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 5. 遍历下一节点 │ + │ for (IFlowNode node : nextNodes) │ + └───────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 6. 更新会话到下一节点 │ + │ session.updateSession(node) │ + └───────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 7. continueTrigger() │ + │ 判断是否继续执行 │ + └───────────────────────────────────────────┘ + │ │ + true │ │ false + ▼ ▼ + ┌─────────────────────┐ ┌─────────────────────┐ + │ 递归执行下一节点 │ │ 生成当前节点记录 │ + │ triggerNode() │ │ callback() │ + └─────────────────────┘ └─────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 8. 保存所有记录 │ + │ RepositoryContext.saveRecords() │ + └───────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 9. 推送事件 │ + │ FlowRecordDoneEvent │ + │ FlowRecordTodoEvent │ + └───────────────────────────────────────────┘ +``` + +--- + +### 5. 并行分支执行流程 + +``` + ┌─────────────────────────────────────────┐ + │ 遇到ParallelBranchNode │ + └─────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 1. filterBranches() │ + │ 分析并行分支的结束汇聚节点 │ + └───────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 2. 记录并行信息 │ + │ flowRecord.parallelBranchNode() │ + │ - parallelBranchNodeId: 汇聚节点ID │ + │ - parallelBranchTotal: 分支总数 │ + │ - parallelId: 并行实例ID │ + └───────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 3. 同时执行所有并行分支 │ + │ 为每个分支生成流程记录 │ + └───────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 4. 分支执行中... │ + │ 每个分支独立执行 │ + └───────────────────────────────────────────┘ + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 5. 到达汇聚节点 │ + │ isWaitParallelRecord() │ + └───────────────────────────────────────────┘ + │ │ + 等待 │ │ 全部到达 + ▼ ▼ + ┌─────────────────────┐ ┌─────────────────────┐ + │ 增加触发计数 │ │ 清空并行信息 │ + │ 等待其他分支完成 │ │ 继续执行后续流程 │ + └─────────────────────┘ └─────────────────────┘ +``` + +--- + +## 三、核心设计模式 + +### 1. 建造者模式 (Builder Pattern) +- `WorkflowBuilder`: 构建流程定义 +- `BaseNodeBuilder`: 构建节点对象 +- `AuditNodeBuilder`: 构建审批节点 + +### 2. 工厂模式 (Factory Pattern) +- `NodeFactory`: 创建不同类型的节点 +- `FlowActionFactory`: 创建不同类型的动作 + +### 3. 策略模式 (Strategy Pattern) +- `INodeStrategy`: 节点策略接口 +- `MultiOperatorAuditStrategy`: 多人审批策略 +- `TimeoutStrategy`: 超时策略 + +### 4. 模板方法模式 (Template Method Pattern) +- `BaseFlowNode`: 定义节点生命周期模板 +- `BaseAction`: 定义动作执行模板 + +### 5. 单例模式 (Singleton Pattern) +- `NodeFactory.getInstance()`: 节点工厂单例 +- `ScriptRuntimeContext.getInstance()`: 脚本运行时单例 +- `RepositoryContext.getInstance()`: 仓储上下文单例 + +### 6. 责任链模式 (Chain of Responsibility Pattern) +- `triggerNode()`: 递归触发后续节点 + +--- + +## 四、扩展点 + +### 1. 自定义节点 +继承 `BaseFlowNode` 或 `BaseAuditNode`,实现 `IFlowNode` 接口 + +### 2. 自定义动作 +继承 `BaseAction`,实现 `IFlowAction` 接口 + +### 3. 自定义策略 +实现 `INodeStrategy` 接口 + +### 4. 自定义脚本 +使用 `ScriptRuntimeContext` 执行 Groovy 脚本 + +### 5. 事件扩展 +监听 `FlowRecordStartEvent`, `FlowRecordTodoEvent`, `FlowRecordDoneEvent`, `FlowRecordFinishEvent` diff --git a/flow-engine-framework/src/test/java/com/codingapi/flow/repository/FlowRecordRepositoryImpl.java b/flow-engine-framework/src/test/java/com/codingapi/flow/repository/FlowRecordRepositoryImpl.java index 1308e09f..1613f2d7 100644 --- a/flow-engine-framework/src/test/java/com/codingapi/flow/repository/FlowRecordRepositoryImpl.java +++ b/flow-engine-framework/src/test/java/com/codingapi/flow/repository/FlowRecordRepositoryImpl.java @@ -45,12 +45,17 @@ public void delete(FlowRecord flowRecord) { } @Override - public List findRecordsByFromId(long fromId) { - return cache.values().stream().filter(flowRecord -> flowRecord.getFromId() == fromId).toList(); + public List findRecordsByFromIdAndNodeId(long fromId,String nodeId) { + return cache.values().stream().filter(flowRecord -> + flowRecord.getFromId() == fromId + && flowRecord.getNodeId().equals(nodeId) + ) + .toList(); } @Override public List findRecordsByProcessId(String processId) { return cache.values().stream().filter(flowRecord -> flowRecord.getProcessId().equals(processId)).toList(); } + } diff --git a/flow-engine-framework/src/test/java/com/codingapi/flow/repository/ParallelBranchRepositoryImpl.java b/flow-engine-framework/src/test/java/com/codingapi/flow/repository/ParallelBranchRepositoryImpl.java new file mode 100644 index 00000000..7ae7a041 --- /dev/null +++ b/flow-engine-framework/src/test/java/com/codingapi/flow/repository/ParallelBranchRepositoryImpl.java @@ -0,0 +1,20 @@ +package com.codingapi.flow.repository; + +import java.util.HashMap; +import java.util.Map; + +public class ParallelBranchRepositoryImpl implements ParallelBranchRepository{ + + private final Map cache = new HashMap<>(); + + @Override + public int getTriggerCount(String parallelId) { + Integer value = cache.get(parallelId); + return value == null ? 0 : value; + } + + @Override + public void addTriggerCount(String parallelId) { + this.cache.put(parallelId, this.getTriggerCount(parallelId) + 1); + } +} diff --git a/flow-engine-framework/src/test/java/com/codingapi/flow/script/ErrorTriggerScriptTest.java b/flow-engine-framework/src/test/java/com/codingapi/flow/script/ErrorTriggerScriptTest.java index ddf94290..47252c71 100644 --- a/flow-engine-framework/src/test/java/com/codingapi/flow/script/ErrorTriggerScriptTest.java +++ b/flow-engine-framework/src/test/java/com/codingapi/flow/script/ErrorTriggerScriptTest.java @@ -6,9 +6,10 @@ import com.codingapi.flow.form.FormMeta; import com.codingapi.flow.form.FormMetaBuilder; import com.codingapi.flow.form.permission.PermissionType; -import com.codingapi.flow.node.audit.ApprovalNode; -import com.codingapi.flow.node.audit.StartNode; -import com.codingapi.flow.node.fixed.EndNode; +import com.codingapi.flow.node.builder.FormFieldPermissionsBuilder; +import com.codingapi.flow.node.nodes.ApprovalNode; +import com.codingapi.flow.node.nodes.StartNode; +import com.codingapi.flow.node.nodes.EndNode; import com.codingapi.flow.script.node.ErrorTriggerScript; import com.codingapi.flow.session.FlowSession; import com.codingapi.flow.user.User; @@ -33,21 +34,25 @@ void execute() { .build(); StartNode startNode = StartNode.builder() - .formFieldPermissionsBuilder() - .addPermission("leave", "name", PermissionType.WRITE) - .addPermission("leave", "days", PermissionType.WRITE) - .addPermission("leave", "reason", PermissionType.WRITE) - .build() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.READ) + .addPermission("leave", "days", PermissionType.READ) + .addPermission("leave", "reason", PermissionType.READ) + .build() + ) .build(); ApprovalNode approvalNode = ApprovalNode.builder() .name("经理审批") .operatorScript("def run(request){return [request.getCreatedOperator()]}") - .formFieldPermissionsBuilder() - .addPermission("leave", "name", PermissionType.READ) - .addPermission("leave", "days", PermissionType.READ) - .addPermission("leave", "reason", PermissionType.READ) - .build() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.READ) + .addPermission("leave", "days", PermissionType.READ) + .addPermission("leave", "reason", PermissionType.READ) + .build() + ) .build(); EndNode endNode = EndNode.builder().build(); @@ -66,7 +71,7 @@ void execute() { FormData data = new FormData(form); data.getDataBody().set("name", "张三").set("days", 10).set("reason", "事由"); - FlowSession flowSession = new FlowSession(user, form, workflow, startNode, data, 0); + FlowSession flowSession = FlowSession.startSession(user, workflow, startNode,startNode.getActions().get(0), data, 0); ErrorTriggerScript errorNodeTriggerScript = ErrorTriggerScript.defaultNodeScript(); ErrorThrow errorThrow = errorNodeTriggerScript.execute(flowSession); diff --git a/flow-engine-framework/src/test/java/com/codingapi/flow/service/FlowServiceTest.java b/flow-engine-framework/src/test/java/com/codingapi/flow/service/FlowServiceTest.java index 5e0e8204..866febc7 100644 --- a/flow-engine-framework/src/test/java/com/codingapi/flow/service/FlowServiceTest.java +++ b/flow-engine-framework/src/test/java/com/codingapi/flow/service/FlowServiceTest.java @@ -7,13 +7,11 @@ import com.codingapi.flow.form.FormMetaBuilder; import com.codingapi.flow.form.permission.PermissionType; import com.codingapi.flow.gateway.impl.UserGateway; -import com.codingapi.flow.node.audit.ApprovalNode; -import com.codingapi.flow.node.audit.StartNode; -import com.codingapi.flow.node.branch.BranchNodeBranchNode; -import com.codingapi.flow.node.fixed.EndNode; +import com.codingapi.flow.node.builder.FormFieldPermissionsBuilder; +import com.codingapi.flow.node.nodes.*; import com.codingapi.flow.pojo.body.FlowAdviceBody; -import com.codingapi.flow.pojo.request.FlowCreateRequest; import com.codingapi.flow.pojo.request.FlowActionRequest; +import com.codingapi.flow.pojo.request.FlowCreateRequest; import com.codingapi.flow.record.FlowRecord; import com.codingapi.flow.repository.*; import com.codingapi.flow.script.runtime.FlowScriptContext; @@ -34,7 +32,8 @@ class FlowServiceTest { private final UserGateway userGateway = new UserGateway(); private final WorkflowBackupRepository workflowBackupRepository = new WorkflowBackupRepositoryImpl(); private final WorkflowRepository workflowRepository = new WorkflowRepositoryImpl(); - private final FlowService flowService = new FlowService(workflowRepository, userGateway, flowRecordRepository, workflowBackupRepository); + private final ParallelBranchRepository parallelBranchRepository = new ParallelBranchRepositoryImpl(); + private final FlowService flowService = new FlowService(workflowRepository, userGateway, flowRecordRepository, workflowBackupRepository,parallelBranchRepository); @Test void create() { @@ -56,21 +55,25 @@ void create() { StartNode startNode = StartNode .builder() - .formFieldPermissionsBuilder() - .addPermission("leave", "name", PermissionType.WRITE) - .addPermission("leave", "days", PermissionType.WRITE) - .addPermission("leave", "reason", PermissionType.WRITE) - .build() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build() + ) .build(); ApprovalNode approvalNode = ApprovalNode.builder() .name("经理审批") .operatorScript("def run(request){return [$bind.getOperatorById(2)]}") - .formFieldPermissionsBuilder() - .addPermission("leave", "name", PermissionType.READ) - .addPermission("leave", "days", PermissionType.READ) - .addPermission("leave", "reason", PermissionType.READ) - .build() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.READ) + .addPermission("leave", "days", PermissionType.READ) + .addPermission("leave", "reason", PermissionType.READ) + .build() + ) .build(); EndNode endNode = EndNode.builder().build(); @@ -91,7 +94,7 @@ void create() { FlowCreateRequest request = new FlowCreateRequest(); request.setWorkId(workflow.getId()); request.setFormData(Map.of("name", "lorne", "days", 1, "reason", "leave")); - List actions = startNode.actions().getActions(); + List actions = startNode.actionManager().getActions(); request.setAdvice(new FlowAdviceBody(actions.get(0).id(), "同意", test.getUserId())); flowService.create(request); @@ -124,21 +127,25 @@ void pass() { StartNode startNode = StartNode .builder() - .formFieldPermissionsBuilder() - .addPermission("leave", "name", PermissionType.WRITE) - .addPermission("leave", "days", PermissionType.WRITE) - .addPermission("leave", "reason", PermissionType.WRITE) - .build() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build() + ) .build(); ApprovalNode approvalNode = ApprovalNode.builder() .name("经理审批") .operatorScript("def run(request){return [$bind.getOperatorById(2)]}") - .formFieldPermissionsBuilder() - .addPermission("leave", "name", PermissionType.READ) - .addPermission("leave", "days", PermissionType.READ) - .addPermission("leave", "reason", PermissionType.READ) - .build() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.READ) + .addPermission("leave", "days", PermissionType.READ) + .addPermission("leave", "reason", PermissionType.READ) + .build() + ) .build(); EndNode endNode = EndNode.builder().build(); @@ -159,7 +166,7 @@ void pass() { FlowCreateRequest request = new FlowCreateRequest(); request.setWorkId(workflow.getId()); request.setFormData(Map.of("name", "lorne", "days", 1, "reason", "leave")); - List actions = startNode.actions().getActions(); + List actions = startNode.actionManager().getActions(); request.setAdvice(new FlowAdviceBody(actions.get(0).id(), "同意", test.getUserId())); flowService.create(request); @@ -177,7 +184,7 @@ void pass() { assertEquals(1, lorneRecordList.size()); - List lorneActions = approvalNode.actions().getActions(); + List lorneActions = approvalNode.actionManager().getActions(); FlowActionRequest lorneRequest = new FlowActionRequest(); lorneRequest.setFormData(Map.of("name", "lorne", "days", 1, "reason", "leave")); @@ -217,11 +224,13 @@ void condition() { StartNode startNode = StartNode .builder() - .formFieldPermissionsBuilder() - .addPermission("leave", "name", PermissionType.WRITE) - .addPermission("leave", "days", PermissionType.WRITE) - .addPermission("leave", "reason", PermissionType.WRITE) - .build() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build() + ) .build(); BranchNodeBranchNode departConditionNode = BranchNodeBranchNode.builder() @@ -239,21 +248,25 @@ void condition() { ApprovalNode departApprovalNode = ApprovalNode.builder() .name("经理审批") .operatorScript("def run(request){return [$bind.getOperatorById(2)]}") - .formFieldPermissionsBuilder() - .addPermission("leave", "name", PermissionType.READ) - .addPermission("leave", "days", PermissionType.READ) - .addPermission("leave", "reason", PermissionType.READ) - .build() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build() + ) .build(); ApprovalNode bossApprovalNode = ApprovalNode.builder() .name("经理审批") .operatorScript("def run(request){return [$bind.getOperatorById(3)]}") - .formFieldPermissionsBuilder() - .addPermission("leave", "name", PermissionType.READ) - .addPermission("leave", "days", PermissionType.READ) - .addPermission("leave", "reason", PermissionType.READ) - .build() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build() + ) .build(); EndNode endNode = EndNode.builder().build(); @@ -283,7 +296,7 @@ void condition() { FlowCreateRequest request = new FlowCreateRequest(); request.setWorkId(workflow.getId()); request.setFormData(data); - List actions = startNode.actions().getActions(); + List actions = startNode.actionManager().getActions(); request.setAdvice(new FlowAdviceBody(actions.get(0).id(), "同意", user.getUserId())); flowService.create(request); @@ -301,7 +314,7 @@ void condition() { assertEquals(1, lorneRecordList.size()); - List lorneActions = departApprovalNode.actions().getActions(); + List lorneActions = departApprovalNode.actionManager().getActions(); FlowActionRequest lorneRequest = new FlowActionRequest(); lorneRequest.setFormData(data); @@ -345,21 +358,25 @@ public FlowRecord getRecordById(long id) { StartNode startNode = StartNode .builder() - .formFieldPermissionsBuilder() - .addPermission("leave", "name", PermissionType.WRITE) - .addPermission("leave", "days", PermissionType.WRITE) - .addPermission("leave", "reason", PermissionType.WRITE) - .build() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build() + ) .build(); ApprovalNode approvalNode = ApprovalNode.builder() .name("经理审批") .operatorScript("def run(request){return [$bind.getOperatorById(2)]}") - .formFieldPermissionsBuilder() - .addPermission("leave", "name", PermissionType.READ) - .addPermission("leave", "days", PermissionType.READ) - .addPermission("leave", "reason", PermissionType.READ) - .build() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build() + ) .build(); EndNode endNode = EndNode.builder().build(); @@ -380,7 +397,7 @@ public FlowRecord getRecordById(long id) { FlowCreateRequest request = new FlowCreateRequest(); request.setWorkId(workflow.getId()); request.setFormData(Map.of("name", "lorne", "days", 1, "reason", "leave")); - List startActions = startNode.actions().getActions(); + List startActions = startNode.actionManager().getActions(); request.setAdvice(new FlowAdviceBody(startActions.get(0).id(), "同意", test.getUserId())); flowService.create(request); @@ -398,7 +415,7 @@ public FlowRecord getRecordById(long id) { assertEquals(1, lorneRecordList.size()); - List lorneActions = approvalNode.actions().getActions(); + List lorneActions = approvalNode.actionManager().getActions(); FlowActionRequest lorneRequest = new FlowActionRequest(); lorneRequest.setFormData(Map.of("name", "lorne", "days", 1, "reason", "leave")); @@ -430,4 +447,176 @@ public FlowRecord getRecordById(long id) { assertEquals(5, records.stream().filter(FlowRecord::isFinish).toList().size()); } + + + + + /** + * 并行分支测试 + */ + @Test + void parallel() { + + User user = new User(1, "user"); + User depart = new User(2, "depart"); + User boss = new User(3, "boss"); + userGateway.save(user); + userGateway.save(depart); + userGateway.save(boss); + + GatewayContext.getInstance().setFlowOperatorGateway(userGateway); + + FormMeta form = FormMetaBuilder.builder() + .name("请假流程") + .code("leave") + .addField("请假人", "name", "string") + .addField("请假天数", "days", "int") + .addField("请假事由", "reason", "string") + .build(); + + StartNode startNode = StartNode + .builder() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build() + ) + .build(); + + ParallelBranchNode parallelBranchNode1 = ParallelBranchNode.builder() + .name("并行分支1") + .order(1) + .build(); + + ParallelBranchNode parallelBranchNode2 = ParallelBranchNode.builder() + .name("并行分支2") + .order(2) + .build(); + + ApprovalNode departApprovalNode = ApprovalNode.builder() + .name("经理审批") + .operatorScript("def run(request){return [$bind.getOperatorById(2)]}") + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build() + ) + .build(); + + ApprovalNode bossApprovalNode = ApprovalNode.builder() + .name("老板审批") + .operatorScript("def run(request){return [$bind.getOperatorById(3)]}") + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build() + ) + .build(); + + ApprovalNode bigBossApprovalNode = ApprovalNode.builder() + .name("大老板审批") + .operatorScript("def run(request){return [$bind.getOperatorById(3)]}") + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build() + ) + .build(); + + EndNode endNode = EndNode.builder().build(); + Workflow workflow = WorkflowBuilder.builder() + .title("请假流程") + .code("leave") + .createdOperator(user) + .form(form) + .addNode(startNode) + .addNode(parallelBranchNode1) + .addNode(parallelBranchNode2) + .addNode(departApprovalNode) + .addNode(bossApprovalNode) + .addNode(bigBossApprovalNode) + .addNode(endNode) + .addEdge(new FlowEdge(startNode.getId(), parallelBranchNode1.getId())) + .addEdge(new FlowEdge(startNode.getId(), parallelBranchNode2.getId())) + .addEdge(new FlowEdge(parallelBranchNode1.getId(), departApprovalNode.getId())) + .addEdge(new FlowEdge(parallelBranchNode2.getId(), bossApprovalNode.getId())) + .addEdge(new FlowEdge(bossApprovalNode.getId(), bigBossApprovalNode.getId())) + .addEdge(new FlowEdge(departApprovalNode.getId(), endNode.getId())) + .addEdge(new FlowEdge(bigBossApprovalNode.getId(), endNode.getId())) + .build(); + + workflowRepository.save(workflow); + + Map data = Map.of("name", "lorne", "days", 3, "reason", "leave"); + + FlowCreateRequest request = new FlowCreateRequest(); + request.setWorkId(workflow.getId()); + request.setFormData(data); + List actions = startNode.actionManager().getActions(); + request.setAdvice(new FlowAdviceBody(actions.get(0).id(), "同意", user.getUserId())); + + flowService.create(request); + + List recordList = flowRecordRepository.findTodoByOperator(user.getUserId()); + assertEquals(1, recordList.size()); + + FlowActionRequest submitRequest = new FlowActionRequest(); + submitRequest.setFormData(data); + submitRequest.setRecordId(recordList.get(0).getId()); + submitRequest.setAdvice(new FlowAdviceBody(actions.get(0).id(), "同意", user.getUserId())); + flowService.action(submitRequest); + + List departRecordList = flowRecordRepository.findTodoByOperator(depart.getUserId()); + assertEquals(1, departRecordList.size()); + + List boosRecordList = flowRecordRepository.findTodoByOperator(boss.getUserId()); + assertEquals(1, boosRecordList.size()); + + + List departActions = departApprovalNode.actionManager().getActions(); + + FlowActionRequest departRequest = new FlowActionRequest(); + departRequest.setFormData(data); + departRequest.setRecordId(departRecordList.get(0).getId()); + departRequest.setAdvice(new FlowAdviceBody(departActions.get(0).id(), "同意", depart.getUserId())); + flowService.action(departRequest); + + boosRecordList = flowRecordRepository.findTodoByOperator(boss.getUserId()); + assertEquals(1, boosRecordList.size()); + + List bossActions = bossApprovalNode.actionManager().getActions(); + + FlowActionRequest dossRequest = new FlowActionRequest(); + dossRequest.setFormData(data); + dossRequest.setRecordId(boosRecordList.get(0).getId()); + dossRequest.setAdvice(new FlowAdviceBody(bossActions.get(0).id(), "同意", boss.getUserId())); + flowService.action(dossRequest); + + + boosRecordList = flowRecordRepository.findTodoByOperator(boss.getUserId()); + assertEquals(1, boosRecordList.size()); + + List bigBossActions = bigBossApprovalNode.actionManager().getActions(); + + FlowActionRequest bigBossRequest = new FlowActionRequest(); + bigBossRequest.setFormData(data); + bigBossRequest.setRecordId(boosRecordList.get(0).getId()); + bigBossRequest.setAdvice(new FlowAdviceBody(bigBossActions.get(0).id(), "同意", boss.getUserId())); + flowService.action(bigBossRequest); + + List records = flowRecordRepository.findRecordsByProcessId(departRecordList.get(0).getProcessId()); + assertEquals(5, records.size()); + assertEquals(0, records.stream().filter(FlowRecord::isTodo).toList().size()); + assertEquals(5, records.stream().filter(FlowRecord::isFinish).toList().size()); + + } + } \ No newline at end of file diff --git a/flow-engine-framework/src/test/java/com/codingapi/flow/workflow/WorkflowBuilderTest.java b/flow-engine-framework/src/test/java/com/codingapi/flow/workflow/WorkflowBuilderTest.java index 9e29b0f8..9cebad69 100644 --- a/flow-engine-framework/src/test/java/com/codingapi/flow/workflow/WorkflowBuilderTest.java +++ b/flow-engine-framework/src/test/java/com/codingapi/flow/workflow/WorkflowBuilderTest.java @@ -5,9 +5,10 @@ import com.codingapi.flow.form.FormMeta; import com.codingapi.flow.form.FormMetaBuilder; import com.codingapi.flow.form.permission.PermissionType; -import com.codingapi.flow.node.audit.ApprovalNode; -import com.codingapi.flow.node.audit.StartNode; -import com.codingapi.flow.node.fixed.EndNode; +import com.codingapi.flow.node.builder.FormFieldPermissionsBuilder; +import com.codingapi.flow.node.nodes.ApprovalNode; +import com.codingapi.flow.node.nodes.StartNode; +import com.codingapi.flow.node.nodes.EndNode; import com.codingapi.flow.gateway.impl.UserGateway; import com.codingapi.flow.user.User; import org.junit.jupiter.api.Test; @@ -36,21 +37,25 @@ void buildBasicWorkflow() { StartNode startNode = StartNode .builder() - .formFieldPermissionsBuilder() - .addPermission("leave", "name", PermissionType.WRITE) - .addPermission("leave", "days", PermissionType.WRITE) - .addPermission("leave", "reason", PermissionType.WRITE) - .build() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build() + ) .build(); ApprovalNode approvalNode = ApprovalNode.builder() .name("经理审批") .operatorScript("def run(request){return [request.getCreatedOperator()]}") - .formFieldPermissionsBuilder() - .addPermission("leave", "name", PermissionType.READ) - .addPermission("leave", "days", PermissionType.READ) - .addPermission("leave", "reason", PermissionType.READ) - .build() + .formFieldsPermissions( + FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build() + ) .build(); EndNode endNode = EndNode.builder().build();