public interface DataStrategy {
    Object execute(Map<String, Object> param);
}
@Component
public class DictDataStrategy implements DataStrategy {
    @Override
    public Object execute(Map<String, Object> param) {
        String dictCode = (String)param.get("dictCode");

        return Arrays.asList(
                MapUtil.builder().put("label", "同意").put("value", "Y").put("order", "1").build(),
                MapUtil.builder().put("label", "不同意").put("value", "N").put("order", "2").build()
        );
    }
}
/**
 * 元素描述配置(可以配置在UserTask、GateWay、SequenceFlow上)
 */
@Data
public class ElementConf {
    /** 是否强制跳过条件表达式 */
    private boolean isForceSkipConditionExpression;
    private boolean isReturn;

}
@Component
public class EndEventStrategy implements FlowNodeStrategy{
    @Override
    public void handleConfig(TaskConfigVo configVo, TaskConfigVo.Modal modal, FlowNode currentNode, SequenceFlow outgoingFlow, SequenceFlowConf outgoingFlowConf, FlowElement targetFlowElement, ElementConf targetConf) {

    }

    @Override
    public void afterPropertiesSet() throws Exception {
        FlowNodeStrategyFactory.regist(EndEvent.class, this);
    }
}
@Data
public class ExclusiveGatewayConf extends ElementConf  {
    private String result;
    private List<TaskConfigVo.FormItem> formItems;
}
@Component
public class ExclusiveGatewayStrategy implements FlowNodeStrategy {
    @Override
    public void handleConfig(TaskConfigVo configVo, TaskConfigVo.Modal modal, FlowNode currentNode, SequenceFlow outgoingFlow, SequenceFlowConf outgoingFlowConf, FlowElement targetElement, ElementConf targetConf) {
        if (targetConf == null) {
            return;
        }

        ExclusiveGatewayConf exclusiveGatewayConf = (ExclusiveGatewayConf)targetConf;
        // 支持 radio、select、upload等组件
        for (TaskConfigVo.FormItem formItem : exclusiveGatewayConf.getFormItems()) {
            if (StringUtils.equals(formItem.getType(), "radio")) {
                String beanName = formItem.getDataStrategy();
                Map<String, Object> args = formItem.getArgs();
                DictDataStrategy dataStrategy = SpringUtil.getBean(beanName);
                Object data = dataStrategy.execute(args);
                formItem.setData(data);

                formItem.setValue(exclusiveGatewayConf.getResult());
            }
        }

        modal.setFormItems(exclusiveGatewayConf.getFormItems());
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        FlowNodeStrategyFactory.regist(ExclusiveGateway.class, this);
    }
}
public interface FlowNodeStrategy extends InitializingBean {

    void handleConfig(TaskConfigVo configVo,
                      TaskConfigVo.Modal modal,
                      FlowNode currentNode,
                      SequenceFlow outgoingFlow,
                      SequenceFlowConf outgoingFlowConf,
                      FlowElement targetFlowElement,
                      ElementConf targetConf);
}
public class FlowNodeStrategyFactory {
    private FlowNodeStrategyFactory() {}

    private static Map<Class, FlowNodeStrategy> factory = new HashMap<>();

    public static void regist(Class clazz, FlowNodeStrategy strategyFactory) {
        factory.put(clazz, strategyFactory);
    }

    public static FlowNodeStrategy get(Class clazz) {
        return factory.get(clazz);
    }
}
@Data
public class SequenceFlowConf extends ElementConf {
    private int order;
    private Map<String, Object> localVariables;
}
@Data
public class TaskConfigBo {
    private String taskId;
}
@Data
public class TaskConfigVo {
    private String procInstId;
    private String procDefId;
    private String taskId;
    private String currentTaskDefKey;
    private String name;

    /** 是否允许撤回 */
    private Boolean isRevoke;

    /** outgoingFlows */
    private List<Button> buttons;


    /**
     * 按钮
     */
    @Data
    public static class Button {
        private String targetTaskDefKey;
        private String flowElement;
        private String name;
        private Map<String, String> localVariables;
        /** 按钮顺序 */
        private int order;

        /** 对话框(UserTask、ExclusiveGateway 中需要展示的数据) */
        private Modal modal;
    }

    @Data
    public static class Modal {
        private String title;

        private List<FormItem> formItems;

        /** 节点审批人变量名,路由等 */
        private Map<String, Object> localVariables;
    }

    @Data
    public static class FormItem {
        private String label;
        private String type;
        private String value;
        private boolean multiple;
        /**
         * nodeType = ExclusiveGateway : data为审批的选项(例如:同意、不同意;通过、不通过等)
         * nodeType = UserTask : data可以为单个用户ID,也可以是用户列表
         */
        private String dataStrategy;
        private Map<String, Object> args;
        private Object data;
    }

}
@Component
public class UserListDataStrategy implements DataStrategy {
    @Override
    public Object execute(Map<String, Object> param) {
        String roleId = (String)param.get("roleId");
        return Arrays.asList("1001-张三-" + roleId, "10002-李四-"+roleId);
    }
}
@Data
public class UserTaskConf extends ElementConf {

    private String name;
    private List<TaskConfigVo.FormItem> formItems;
}
@Component
public class UserTaskStrategy implements FlowNodeStrategy  {
    @Override
    public void handleConfig(TaskConfigVo configVo, TaskConfigVo.Modal modal, FlowNode currentNode,
                                          SequenceFlow outgoingFlow, SequenceFlowConf outgoingFlowConf,
                                          FlowElement targetElement, ElementConf targetConf) {
        if (targetConf == null) {
            return;
        }

        UserTaskConf userTaskConf = (UserTaskConf)targetConf;
        // 支持 radio、select、upload等组件
        for (TaskConfigVo.FormItem formItem : userTaskConf.getFormItems()) {
            if (StringUtils.equals(formItem.getType(), "select")) {
                String beanName = formItem.getDataStrategy();
                Map<String, Object> args = formItem.getArgs();
                UserListDataStrategy dataStrategy = SpringUtil.getBean(beanName);
                Object data = dataStrategy.execute(args);
                formItem.setData(data);

                UserTask flowNode = (UserTask)targetElement;
                if (StringUtils.isNotBlank(flowNode.getAssignee())) {
                    formItem.setValue(flowNode.getAssignee().trim().replace("${", "").replace("}", ""));
                }
            }
        }

        modal.setFormItems(userTaskConf.getFormItems());
    }


    @Override
    public void afterPropertiesSet() {
        FlowNodeStrategyFactory.regist(UserTask.class, this);
    }
}
public class UserTaskStrategyFactory {
}
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("workflow")
public class WorkFlowController {

    @Autowired
    private WorkFlowService workFlowService;


    @PostMapping("getTaskConf")
    public R<TaskConfigVo> getTaskConf(@RequestBody TaskConfigBo bo) {
        return R.ok(workFlowService.getTaskConf(bo));
    }
}
@Service
public class WorkFlowService {

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private RepositoryService repositoryService;

    public TaskConfigVo getTaskConf(TaskConfigBo bo) {
        Assert.isTrue(StringUtils.isNotBlank(bo.getTaskId()), "taskId不能为空");
        TaskConfigVo configVo = new TaskConfigVo();

        HistoricTaskInstance historyTask = historyService.createHistoricTaskInstanceQuery().taskId(bo.getTaskId()).singleResult();
        configVo.setProcInstId(historyTask.getProcessInstanceId());
        configVo.setProcDefId(historyTask.getProcessDefinitionId());
        configVo.setTaskId(bo.getTaskId());
        configVo.setCurrentTaskDefKey(historyTask.getTaskDefinitionKey());
        configVo.setName(historyTask.getName());


        if (historyTask.getEndTime() == null) {
            // 待办
            buildVo(bo, configVo);
        } else {
            // 已办
        }

        return configVo;
    }

    private void buildVo(TaskConfigBo bo, TaskConfigVo configVo) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(configVo.getProcDefId());
        FlowNode currentFlowNode = (FlowNode)bpmnModel.getFlowElement(configVo.getCurrentTaskDefKey());

        List<TaskConfigVo.Button> buttons = new ArrayList<>();
        SimpleContext simpleContext = buildSimpleContext(configVo);
        List<SequenceFlow> outgoingFlows = currentFlowNode.getOutgoingFlows();
        for (SequenceFlow outgoingFlow : outgoingFlows) {
            SequenceFlowConf outgoingFlowConf = (SequenceFlowConf)parseDescription(outgoingFlow);
            boolean isShowButton = calcConditionExpression(outgoingFlow, simpleContext, outgoingFlowConf);
            if (isShowButton) {
                FlowElement targetFlowElement = outgoingFlow.getTargetFlowElement();
                ElementConf targetConf = parseDescription(targetFlowElement);

                FlowNodeStrategy flowNodeStrategy = FlowNodeStrategyFactory.get(targetFlowElement.getClass());

                TaskConfigVo.Button button = new TaskConfigVo.Button();
                button.setTargetTaskDefKey(targetFlowElement.getId());
                String sequenceFlowLabel = outgoingFlow.getName();
                if (outgoingFlowConf != null && !CollectionUtils.isEmpty(outgoingFlowConf.getLocalVariables())) {
                    sequenceFlowLabel = (String)outgoingFlowConf.getLocalVariables().get("label");
                }
                button.setName(sequenceFlowLabel);
                button.setOrder(outgoingFlowConf !=null ? outgoingFlowConf.getOrder() : 0);
                button.setFlowElement(targetFlowElement.getClass().getSimpleName());

                TaskConfigVo.Modal modal = new TaskConfigVo.Modal();
                modal.setTitle(outgoingFlow.getName());
                flowNodeStrategy.handleConfig(configVo, modal, currentFlowNode, outgoingFlow, outgoingFlowConf, targetFlowElement, targetConf);
                modal.setLocalVariables(handleVariable(outgoingFlowConf, targetFlowElement));
                button.setModal(modal);
                buttons.add(button);
            }
        }
        configVo.setButtons(buttons.stream().sorted(Comparator.comparing(TaskConfigVo.Button::getOrder)).collect(Collectors.toList()));
    }



    private ElementConf parseDescription(FlowElement flowElement) {
        if (StringUtils.isBlank(flowElement.getDocumentation())) {
            return null;
        }

        if (flowElement instanceof SequenceFlow) {
            return JSONObject.parseObject(flowElement.getDocumentation(), SequenceFlowConf.class);
        } else if (flowElement instanceof UserTask) {
            return JSONObject.parseObject(flowElement.getDocumentation(), UserTaskConf.class);
        } else if (flowElement instanceof ExclusiveGateway) {
            return JSONObject.parseObject(flowElement.getDocumentation(), ExclusiveGatewayConf.class);
        }

        return null;
    }

    private SimpleContext buildSimpleContext(TaskConfigVo configVo) {
        SimpleContext simpleContext = new SimpleContext();

        Map<String, Object> variables = taskService.getVariablesLocal(configVo.getTaskId());
        if (!CollectionUtils.isEmpty(variables)) {
            ExpressionFactoryImpl expressionFactory = new ExpressionFactoryImpl();
            variables.forEach((key, value) -> {
                ObjectValueExpression valueExpression = expressionFactory.createValueExpression(value, Object.class);
                simpleContext.setVariable(key, valueExpression);
            });
        }

        return simpleContext;
    }

    /**
     * 判断条件表达式是否为true
     * @return
     */
    private boolean calcConditionExpression(SequenceFlow outgoingFlow, SimpleContext simpleContext, ElementConf outgoingFlowConf) {
        String conditionExpression = outgoingFlow.getConditionExpression();
        if (StringUtils.isBlank(conditionExpression)) {
            return true;
        } else {
            if (outgoingFlowConf != null && outgoingFlowConf.isForceSkipConditionExpression()) {
                return true;
            }

            ExpressionFactoryImpl expressionFactory = new ExpressionFactoryImpl();
            return (boolean)expressionFactory.createValueExpression(simpleContext, conditionExpression, Boolean.class)
                    .getValue(simpleContext);
        }
    }

    private Map<String, Object> handleVariable(ElementConf outgoingFlowConf, FlowElement targetFlowElement) {
        Map<String, Object> localVariables = new HashMap<>();
        if (outgoingFlowConf != null && outgoingFlowConf instanceof SequenceFlowConf) {
            SequenceFlowConf sequenceFlowConf = (SequenceFlowConf)outgoingFlowConf;
            if (!CollectionUtils.isEmpty(sequenceFlowConf.getLocalVariables())) {
                localVariables.putAll(sequenceFlowConf.getLocalVariables());
            }
        } else if (targetFlowElement instanceof UserTask) {
            UserTask flowNode = (UserTask)targetFlowElement;
            localVariables.put(flowNode.getAssignee().trim().replace("${", "").replace("}", ""), "");
        }


        return localVariables;
    }
}

DemoProcess.bpmn20.xml

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
  <process id="DemoProcess" name="DemoProcess" isExecutable="true">
    <startEvent id="sid-902bf5c2-80ae-4160-b974-aab02f9e327d"/>
    <userTask id="sid-f07307a1-0b78-4f6f-8e2d-a4e827ac8ed5" name="UserTask1" flowable:assignee="${assignee1}"/>
    <sequenceFlow id="sid-53ac87e9-9658-450c-9230-daf726d0c8ce" sourceRef="sid-902bf5c2-80ae-4160-b974-aab02f9e327d" targetRef="sid-f07307a1-0b78-4f6f-8e2d-a4e827ac8ed5"/>
    <userTask id="UserTask2" name="UserTask2" flowable:assignee="${assignee2}" isForCompensation="true">
      <documentation><![CDATA[{
     "formItems": [
         {"type": "select", "label": "审批人", "dataStrategy": "userListDataStrategy", "args": {"roleId": "0001"}}
    ]
}]]></documentation>
    </userTask>
    <sequenceFlow id="flow1" sourceRef="sid-f07307a1-0b78-4f6f-8e2d-a4e827ac8ed5" targetRef="UserTask2"/>
    <userTask id="UserTask3" name="UserTask3" flowable:assignee="${assignee3}">
      <documentation><![CDATA[{
     "formItems": [
         {"type": "select", "label": "审批人", "dataStrategy": "formItemStrategyDataStrategy", "args": {"roleId": "0002"}}
    ]
}]]></documentation>
    </userTask>
    <sequenceFlow id="flow2" sourceRef="UserTask2" targetRef="gateway1" name="办理">
      <documentation>{"order":2,"localVariables": {"method": "complete"}}</documentation>
    </sequenceFlow>
    <userTask id="UserTask4" name="UserTask4"/>
    <sequenceFlow id="usertask3-usertask4" sourceRef="UserTask3" targetRef="UserTask4" name="办理"/>
    <sequenceFlow id="usertask2-usertask2" sourceRef="UserTask2" targetRef="UserTask2" name="转办">
      <documentation>{"order":1,"localVariables": {"method": "transfer", "flow": "self", "label": "转办人"}}</documentation>
    </sequenceFlow>
    <exclusiveGateway id="gateway1">
      <documentation><![CDATA[{
     "result": "approved",
     "formItems": [
         {"label": "审批结果", "type": "radio", "dataStrategy": "dictDataStrategy", "args": {"dictCode": "approved_result"}}
    ]
}]]></documentation>
    </exclusiveGateway>
    <sequenceFlow id="yesflow" sourceRef="gateway1" targetRef="UserTask3" name="同意">
      <documentation/>
      <conditionExpression xsi:type="tFormalExpression">${approved==\"Y\"}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="noflow" sourceRef="gateway1" targetRef="sid-f07307a1-0b78-4f6f-8e2d-a4e827ac8ed5" name="不同意">
      <conditionExpression xsi:type="tFormalExpression">${approved==\"N\"}</conditionExpression>
    </sequenceFlow>
    <endEvent id="sid-f7afaa2b-8464-457d-a460-f39a5d82999f"/>
    <sequenceFlow id="endflow" sourceRef="UserTask4" targetRef="sid-f7afaa2b-8464-457d-a460-f39a5d82999f" name="办理"/>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_DemoProcess">
    <bpmndi:BPMNPlane bpmnElement="DemoProcess" id="BPMNPlane_DemoProcess">
      <bpmndi:BPMNShape id="shape-0c1343dd-4132-48bf-8129-b3f75ef43179" bpmnElement="sid-902bf5c2-80ae-4160-b974-aab02f9e327d">
        <omgdc:Bounds x="-365.0" y="-55.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-d4fc9ad7-31e7-43d9-8081-15b509c4cabf" bpmnElement="sid-f07307a1-0b78-4f6f-8e2d-a4e827ac8ed5">
        <omgdc:Bounds x="-305.0" y="-57.5" width="50.0" height="35.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-cd9a8fa4-cd02-444e-aa05-df1b9d4510c9" bpmnElement="sid-53ac87e9-9658-450c-9230-daf726d0c8ce">
        <omgdi:waypoint x="-335.0" y="-40.0"/>
        <omgdi:waypoint x="-305.0" y="-40.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-63168950-a924-44a6-b685-ee23b78d3388" bpmnElement="UserTask2">
        <omgdc:Bounds x="-225.0" y="-55.0" width="55.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-660b7849-5cf4-4d4a-af86-73125a7accd3" bpmnElement="flow1">
        <omgdi:waypoint x="-255.0" y="-40.0"/>
        <omgdi:waypoint x="-225.0" y="-40.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-67a7985c-31e3-4b9b-8dce-a1dd2f8d429f" bpmnElement="UserTask3">
        <omgdc:Bounds x="-55.0" y="-57.500008" width="70.0" height="35.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-585272e8-ceb5-4359-a201-b9f7abb7e04c" bpmnElement="flow2">
        <omgdi:waypoint x="-170.0" y="-40.0"/>
        <omgdi:waypoint x="-125.0" y="-40.000008"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-99dfa344-0b18-4ff6-ad9a-73b23534989e" bpmnElement="UserTask4">
        <omgdc:Bounds x="44.999996" y="-57.50001" width="65.0" height="35.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-ff6269bd-3e1e-46e9-aaf9-caed3bf4a42f" bpmnElement="usertask3-usertask4">
        <omgdi:waypoint x="15.0" y="-40.000008"/>
        <omgdi:waypoint x="44.999996" y="-40.00001"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-4aa66e24-7bf1-4562-9969-0f8ebec961af" bpmnElement="usertask2-usertask2">
        <omgdi:waypoint x="-211.25" y="-55.0"/>
        <omgdi:waypoint x="-206.25" y="-75.0"/>
        <omgdi:waypoint x="-178.75" y="-75.0"/>
        <omgdi:waypoint x="-183.75" y="-55.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-29c00987-acb9-4602-a353-6c0093da1558" bpmnElement="gateway1">
        <omgdc:Bounds x="-125.0" y="-60.000008" width="40.0" height="40.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-ca471b5b-74d6-4dd0-8b35-f74efeda6666" bpmnElement="yesflow">
        <omgdi:waypoint x="-85.0" y="-40.000008"/>
        <omgdi:waypoint x="-55.0" y="-40.000008"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-a7ae64bb-2635-4475-97bf-6cced692bc3c" bpmnElement="noflow">
        <omgdi:waypoint x="-104.99999" y="-60.000008"/>
        <omgdi:waypoint x="-105.0" y="-95.625"/>
        <omgdi:waypoint x="-280.0" y="-95.62501"/>
        <omgdi:waypoint x="-280.00003" y="-57.5"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-7d0ea60b-d9d9-4052-adba-a042e9bf49f1" bpmnElement="sid-f7afaa2b-8464-457d-a460-f39a5d82999f">
        <omgdc:Bounds x="139.99998" y="-55.00001" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-5d334ebf-72a6-40b4-a6c4-aaaae36a4966" bpmnElement="endflow">
        <omgdi:waypoint x="110.0" y="-40.00001"/>
        <omgdi:waypoint x="139.99998" y="-40.00001"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>