基于 Spring AI 实现类 Dify 风格的大模型工作流 Demo(核心是可视化流程编排、节点化 AI 能力调用、流程参数传递、分支/条件判断),我会提供完整可运行的代码,模拟 Dify 的核心特性:可视化流程节点编排 + AI 节点(问答/分析/生成) + 流程控制节点(分支/条件) + 参数流转,让你体验“大模型工作流”的完整生命周期。

一、Demo 核心设计(类 Dify 特性)

Dify 核心特性

本 Demo 实现方式

流程节点编排

定义标准化的流程节点(AI 节点/条件节点/结束节点),JSON 配置流程结构

AI 能力调用节点

Spring AI 封装为“AI 问答节点”“AI 分析节点”,支持参数传入/结果输出

流程参数流转

节点间通过上下文(Context)传递参数,支持 EL 表达式引用上游节点结果

条件分支节点

基于 AI 分析结果/参数值,动态跳转不同流程分支

可视化操作

前端简化版可视化编排(模拟 Dify 的拖拽式操作,输出流程 JSON 配置)

二、完整代码实现
1. 依赖配置(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>springai-dify-workflow</artifactId>
    <version>1.0.0</version>
    <name>SpringAI-Dify-Workflow</name>

    <dependencies>
        <!-- Spring Web + Thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- Spring AI 核心 + OpenAI -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
            <version>1.0.1</version>
        </dependency>

        <!-- JSON 处理 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

        <!-- EL 表达式解析(参数引用) -->
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>3.0.1-b12</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.el</artifactId>
            <version>3.0.1-b12</version>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.18</version>
            </plugin>
        </plugins>
    </build>
</project>
2. 配置文件(application.yml)
server:
  port: 8080

spring:
  # Thymeleaf 配置
  thymeleaf:
    cache: false
  # Spring AI + OpenAI 配置(替换为你的API Key)
  ai:
    openai:
      api-key: your-openai-api-key
      chat:
        options:
          model: gpt-3.5-turbo
          temperature: 0.2

# 日志配置
logging:
  level:
    com.example.demo: DEBUG
3. 核心模型定义(类 Dify 流程节点)
3.1 流程上下文(WorkflowContext.java)
package com.example.demo.model;

import lombok.Data;

import java.util.HashMap;
import java.util.Map;

/**
 * 流程上下文:存储节点参数、输出结果,实现节点间参数流转
 */
@Data
public class WorkflowContext {
    /** 全局参数 */
    private Map<String, Object> globalParams = new HashMap<>();
    /** 节点输出结果(key:节点ID,value:输出内容) */
    private Map<String, Object> nodeOutputs = new HashMap<>();

    /**
     * 设置节点输出
     */
    public void setNodeOutput(String nodeId, Object output) {
        nodeOutputs.put(nodeId, output);
    }

    /**
     * 获取节点输出
     */
    public Object getNodeOutput(String nodeId) {
        return nodeOutputs.get(nodeId);
    }

    /**
     * 设置全局参数
     */
    public void setGlobalParam(String key, Object value) {
        globalParams.put(key, value);
    }

    /**
     * 获取全局参数
     */
    public Object getGlobalParam(String key) {
        return globalParams.get(key);
    }
}
3.2 流程节点基类(BaseNode.java)
package com.example.demo.model.node;

import com.example.demo.model.WorkflowContext;
import lombok.Data;

/**
 * 流程节点基类:所有节点的通用属性
 */
@Data
public abstract class BaseNode {
    /** 节点ID(唯一) */
    private String nodeId;
    /** 节点类型(ai_chat/condition/end) */
    private String nodeType;
    /** 节点名称 */
    private String nodeName;
    /** 下一个节点ID(默认分支) */
    private String nextNodeId;

    /**
     * 执行节点逻辑
     */
    public abstract void execute(WorkflowContext context);
}
3.3 AI 问答节点(AiChatNode.java)
package com.example.demo.model.node;

import com.example.demo.model.WorkflowContext;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * AI 问答节点:调用大模型生成回答,支持参数引用
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class AiChatNode extends BaseNode {
    /** 提问模板(支持EL表达式:${node_1.output} / ${global.user_input}) */
    private String promptTemplate;
    /** 输出参数名(存储到上下文的key) */
    private String outputKey;

    @Override
    public void execute(WorkflowContext context) {
        // 空实现,由处理器处理
    }
}
3.4 条件分支节点(ConditionNode.java)
package com.example.demo.model.node;

import com.example.demo.model.WorkflowContext;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.Map;

/**
 * 条件分支节点:根据条件跳转不同节点
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class ConditionNode extends BaseNode {
    /** 条件表达式(EL表达式) */
    private String conditionExpression;
    /** 分支配置(key:条件结果(true/false),value:节点ID) */
    private Map<String, String> branchConfig;

    @Override
    public void execute(WorkflowContext context) {
        // 空实现,由处理器处理
    }
}
3.5 结束节点(EndNode.java)
package com.example.demo.model.node;

import com.example.demo.model.WorkflowContext;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 结束节点:流程终止
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class EndNode extends BaseNode {
    /** 结束提示语 */
    private String endMessage;

    @Override
    public void execute(WorkflowContext context) {
        System.out.println("流程结束:" + endMessage);
        context.setGlobalParam("process_end", true);
    }
}
3.6 流程定义(WorkflowDefinition.java)
package com.example.demo.model;

import lombok.Data;

import java.util.Map;

/**
 * 流程定义:类 Dify 的流程配置
 */
@Data
public class WorkflowDefinition {
    /** 流程ID */
    private String workflowId;
    /** 流程名称 */
    private String workflowName;
    /** 起始节点ID */
    private String startNodeId;
    /** 节点配置(key:节点ID,value:节点对象) */
    private Map<String, BaseNode> nodes;
}
4. 核心处理器(节点执行逻辑)
4.1 EL 表达式工具(ElExpressionUtil.java)
package com.example.demo.util;

import com.example.demo.model.WorkflowContext;
import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import org.glassfish.el.ExpressionFactoryImpl;
import org.glassfish.el.lang.EvaluationContext;

/**
 * EL 表达式解析工具:支持引用上下文参数
 */
public class ElExpressionUtil {
    private static final ExpressionFactory EXPRESSION_FACTORY = new ExpressionFactoryImpl();

    /**
     * 解析EL表达式,替换为上下文参数
     */
    public static String parseExpression(String template, WorkflowContext context) {
        if (template == null || !template.contains("${")) {
            return template;
        }

        // 构建EL上下文
        ELContext elContext = new EvaluationContext(null, null, null);
        // 注册全局参数解析
        elContext.setPropertyResolved(false);
        elContext.getELResolver().setValue(elContext, null, "global", context.getGlobalParams());
        elContext.getELResolver().setValue(elContext, null, "node", context.getNodeOutputs());

        // 替换所有EL表达式
        StringBuilder result = new StringBuilder();
        int start = 0;
        int exprStart = template.indexOf("${", start);
        while (exprStart != -1) {
            result.append(template, start, exprStart);
            int exprEnd = template.indexOf("}", exprStart + 2);
            if (exprEnd == -1) {
                break;
            }
            String expr = template.substring(exprStart + 2, exprEnd);
            // 解析表达式
            ValueExpression valueExpr = EXPRESSION_FACTORY.createValueExpression(elContext, expr, Object.class);
            Object value = valueExpr.getValue(elContext);
            result.append(value != null ? value : "");
            start = exprEnd + 1;
            exprStart = template.indexOf("${", start);
        }
        result.append(template.substring(start));
        return result.toString();
    }
}
4.2 AI 节点处理器(AiChatNodeHandler.java)
package com.example.demo.handler;

import com.example.demo.model.WorkflowContext;
import com.example.demo.model.node.AiChatNode;
import com.example.demo.util.ElExpressionUtil;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.stereotype.Component;

/**
 * AI 问答节点处理器:调用大模型生成回答
 */
@Component
public class AiChatNodeHandler {
    private final ChatClient chatClient;

    public AiChatNodeHandler(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    /**
     * 执行AI问答节点
     */
    public void handle(AiChatNode node, WorkflowContext context) {
        // 1. 解析EL表达式,生成最终prompt
        String finalPrompt = ElExpressionUtil.parseExpression(node.getPromptTemplate(), context);
        System.out.println("AI节点最终Prompt:" + finalPrompt);

        // 2. 调用大模型
        String aiResponse = chatClient.call(new Prompt(finalPrompt)).getResult().getOutput().getContent();
        System.out.println("AI节点输出:" + aiResponse);

        // 3. 存储到上下文
        context.setNodeOutput(node.getNodeId(), aiResponse);
        if (node.getOutputKey() != null) {
            context.setGlobalParam(node.getOutputKey(), aiResponse);
        }
    }
}
4.3 条件节点处理器(ConditionNodeHandler.java)
package com.example.demo.handler;

import com.example.demo.model.WorkflowContext;
import com.example.demo.model.node.ConditionNode;
import com.example.demo.util.ElExpressionUtil;
import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import org.glassfish.el.ExpressionFactoryImpl;
import org.glassfish.el.lang.EvaluationContext;
import org.springframework.stereotype.Component;

/**
 * 条件分支节点处理器:解析条件并返回目标节点ID
 */
@Component
public class ConditionNodeHandler {
    private static final ExpressionFactory EXPRESSION_FACTORY = new ExpressionFactoryImpl();

    /**
     * 执行条件节点,返回目标节点ID
     */
    public String handle(ConditionNode node, WorkflowContext context) {
        // 1. 解析条件表达式
        ELContext elContext = new EvaluationContext(null, null, null);
        elContext.setPropertyResolved(false);
        elContext.getELResolver().setValue(elContext, null, "global", context.getGlobalParams());
        elContext.getELResolver().setValue(elContext, null, "node", context.getNodeOutputs());

        // 2. 计算条件结果
        ValueExpression valueExpr = EXPRESSION_FACTORY.createValueExpression(
                elContext, node.getConditionExpression(), Boolean.class);
        Boolean conditionResult = (Boolean) valueExpr.getValue(elContext);
        String resultStr = conditionResult != null && conditionResult ? "true" : "false";
        System.out.println("条件节点[" + node.getNodeId() + "]结果:" + resultStr);

        // 3. 返回目标节点ID
        return node.getBranchConfig().getOrDefault(resultStr, node.getNextNodeId());
    }
}
5. 流程引擎核心(WorkflowEngine.java)
package com.example.demo.engine;

import com.example.demo.handler.AiChatNodeHandler;
import com.example.demo.handler.ConditionNodeHandler;
import com.example.demo.model.WorkflowContext;
import com.example.demo.model.WorkflowDefinition;
import com.example.demo.model.node.AiChatNode;
import com.example.demo.model.node.BaseNode;
import com.example.demo.model.node.ConditionNode;
import com.example.demo.model.node.EndNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 流程引擎:类 Dify 的流程执行核心
 */
@Component
public class WorkflowEngine {
    @Autowired
    private AiChatNodeHandler aiChatNodeHandler;
    @Autowired
    private ConditionNodeHandler conditionNodeHandler;

    /**
     * 执行流程
     */
    public WorkflowContext execute(WorkflowDefinition definition, WorkflowContext context) {
        if (definition == null || definition.getStartNodeId() == null) {
            throw new RuntimeException("流程定义无效,缺少起始节点");
        }

        // 初始化上下文
        if (context == null) {
            context = new WorkflowContext();
        }
        context.setGlobalParam("process_start", true);

        // 从起始节点开始执行
        String currentNodeId = definition.getStartNodeId();
        while (currentNodeId != null && !Boolean.TRUE.equals(context.getGlobalParam("process_end"))) {
            BaseNode currentNode = definition.getNodes().get(currentNodeId);
            if (currentNode == null) {
                System.out.println("节点[" + currentNodeId + "]不存在,流程终止");
                break;
            }

            System.out.println("\n执行节点:" + currentNode.getNodeName() + "(" + currentNode.getNodeId() + ")");

            // 根据节点类型执行
            if (currentNode instanceof AiChatNode) {
                aiChatNodeHandler.handle((AiChatNode) currentNode, context);
                currentNodeId = currentNode.getNextNodeId();
            } else if (currentNode instanceof ConditionNode) {
                currentNodeId = conditionNodeHandler.handle((ConditionNode) currentNode, context);
            } else if (currentNode instanceof EndNode) {
                ((EndNode) currentNode).execute(context);
                currentNodeId = null;
            } else {
                System.out.println("不支持的节点类型:" + currentNode.getNodeType());
                currentNodeId = currentNode.getNextNodeId();
            }
        }

        System.out.println("\n流程执行完成!");
        return context;
    }
}
6. 控制器(DifyWorkflowController.java)
package com.example.demo.controller;

import com.example.demo.engine.WorkflowEngine;
import com.example.demo.model.WorkflowContext;
import com.example.demo.model.WorkflowDefinition;
import com.example.demo.model.node.AiChatNode;
import com.example.demo.model.node.ConditionNode;
import com.example.demo.model.node.EndNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

/**
 * 类 Dify 流程控制器:可视化编排 + 流程执行
 */
@Controller
@RequestMapping("/dify-workflow")
public class DifyWorkflowController {
    @Autowired
    private WorkflowEngine workflowEngine;
    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 首页(流程编排+执行)
     */
    @GetMapping("/index")
    public String index() {
        return "index";
    }

    /**
     * 生成示例流程(模拟 Dify 可视化编排后的JSON)
     */
    @GetMapping("/example-workflow")
    @ResponseBody
    public WorkflowDefinition getExampleWorkflow() {
        // 1. 创建流程定义
        WorkflowDefinition definition = new WorkflowDefinition();
        definition.setWorkflowId("wf_001");
        definition.setWorkflowName("AI 请假分析流程");
        definition.setStartNodeId("node_1");

        // 2. 创建节点
        Map<String, BaseNode> nodes = new HashMap<>();

        // 节点1:AI 分析请假原因
        AiChatNode node1 = new AiChatNode();
        node1.setNodeId("node_1");
        node1.setNodeType("ai_chat");
        node1.setNodeName("AI分析请假原因");
        node1.setPromptTemplate("分析以下请假申请是否合理,并给出简短结论(仅返回合理/不合理):${global.user_input}");
        node1.setOutputKey("ai_analysis_result");
        node1.setNextNodeId("node_2");
        nodes.put(node1.getNodeId(), node1);

        // 节点2:条件分支(判断AI分析结果)
        ConditionNode node2 = new ConditionNode();
        node2.setNodeId("node_2");
        node2.setNodeType("condition");
        node2.setNodeName("判断请假合理性");
        node2.setConditionExpression("${global.ai_analysis_result} == '合理'");
        Map<String, String> branchConfig = new HashMap<>();
        branchConfig.put("true", "node_3");
        branchConfig.put("false", "node_4");
        node2.setBranchConfig(branchConfig);
        nodes.put(node2.getNodeId(), node2);

        // 节点3:AI 生成通过审批意见
        AiChatNode node3 = new AiChatNode();
        node3.setNodeId("node_3");
        node3.setNodeType("ai_chat");
        node3.setNodeName("生成通过意见");
        node3.setPromptTemplate("为以下合理的请假申请生成审批通过意见:${global.user_input}");
        node3.setOutputKey("approve_advice");
        node3.setNextNodeId("node_5");
        nodes.put(node3.getNodeId(), node3);

        // 节点4:AI 生成拒绝审批意见
        AiChatNode node4 = new AiChatNode();
        node4.setNodeId("node_4");
        node4.setNodeType("ai_chat");
        node4.setNodeName("生成拒绝意见");
        node4.setPromptTemplate("为以下不合理的请假申请生成审批拒绝意见:${global.user_input}");
        node4.setOutputKey("reject_advice");
        node4.setNextNodeId("node_5");
        nodes.put(node4.getNodeId(), node4);

        // 节点5:结束节点
        EndNode node5 = new EndNode();
        node5.setNodeId("node_5");
        node5.setNodeType("end");
        node5.setNodeName("流程结束");
        node5.setEndMessage("请假审批流程完成");
        nodes.put(node5.getNodeId(), node5);

        definition.setNodes(nodes);
        return definition;
    }

    /**
     * 执行流程
     */
    @PostMapping("/execute")
    @ResponseBody
    public Map<String, Object> executeWorkflow(
            @RequestParam String userInput,
            @RequestParam(required = false) String workflowJson) {
        try {
            // 1. 获取流程定义(使用示例流程或自定义JSON)
            WorkflowDefinition definition;
            if (workflowJson != null && !workflowJson.isEmpty()) {
                definition = objectMapper.readValue(workflowJson, WorkflowDefinition.class);
            } else {
                definition = getExampleWorkflow();
            }

            // 2. 初始化上下文(传入用户输入)
            WorkflowContext context = new WorkflowContext();
            context.setGlobalParam("user_input", userInput);

            // 3. 执行流程
            WorkflowContext resultContext = workflowEngine.execute(definition, context);

            // 4. 组装返回结果
            Map<String, Object> result = new HashMap<>();
            result.put("success", true);
            result.put("global_params", resultContext.getGlobalParams());
            result.put("node_outputs", resultContext.getNodeOutputs());
            return result;
        } catch (Exception e) {
            Map<String, Object> error = new HashMap<>();
            error.put("success", false);
            error.put("msg", "流程执行失败:" + e.getMessage());
            return error;
        }
    }

    /**
     * 流程编排页面(模拟 Dify 可视化编辑)
     */
    @GetMapping("/editor")
    public String workflowEditor(Model model) {
        // 传入示例流程JSON
        try {
            WorkflowDefinition example = getExampleWorkflow();
            model.addAttribute("exampleWorkflowJson", objectMapper.writeValueAsString(example));
        } catch (Exception e) {
            model.addAttribute("exampleWorkflowJson", "{}");
        }
        return "workflow-editor";
    }
}
7. 启动类(DifyWorkflowApplication.java)
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DifyWorkflowApplication {
    public static void main(String[] args) {
        SpringApplication.run(DifyWorkflowApplication.class, args);
        System.out.println("===== 类 Dify AI 工作流Demo启动成功 =====");
        System.out.println("首页:http://localhost:8080/dify-workflow/index");
        System.out.println("流程编排:http://localhost:8080/dify-workflow/editor");
    }
}
8. 前端页面(类 Dify 可视化体验)
8.1 首页(index.html)
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Spring AI 类 Dify 工作流 Demo</title>
    <link rel="stylesheet" type="text/css" href="https://www.jeasyui.com/easyui/themes/default/easyui.css">
    <script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
    <script type="text/javascript" src="https://www.jeasyui.com/easyui/jquery.easyui.min.js"></script>
    <style>
        .container {
            width: 90%;
            margin: 50px auto;
        }
        .card {
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            padding: 20px;
            margin-bottom: 20px;
        }
        .btn {
            margin: 10px 5px;
        }
        .result-area {
            margin-top: 20px;
            padding: 15px;
            background-color: #f5f5f5;
            border-radius: 5px;
            display: none;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Spring AI 类 Dify 工作流 Demo</h1>
        
        <!-- 快速执行区域 -->
        <div class="card">
            <h3>📝 快速执行示例流程(AI请假分析)</h3>
            <div class="form-item">
                <label>请假申请描述:</label>
                <input class="easyui-textbox" id="userInput" style="width: 600px;" 
                       data-options="multiline:true, rows:3, prompt:'例如:我要请假3天,原因是感冒发烧需要休息'">
            </div>
            <a href="javascript:void(0)" class="easyui-linkbutton btn" onclick="executeWorkflow()" data-options="iconCls:'icon-play'">执行流程</a>
            <a href="/dify-workflow/editor" class="easyui-linkbutton btn" data-options="iconCls:'icon-edit'">流程编排</a>
            
            <!-- 执行结果 -->
            <div id="resultArea" class="result-area">
                <h4>流程执行结果:</h4>
                <pre id="resultContent"></pre>
            </div>
        </div>
    </div>

    <script type="text/javascript">
        // 执行流程
        function executeWorkflow() {
            var userInput = $('#userInput').val().trim();
            if (!userInput) {
                $.messager.alert('提示', '请输入请假申请描述!', 'warning');
                return;
            }

            $.ajax({
                url: '/dify-workflow/execute',
                type: 'POST',
                data: {
                    userInput: userInput
                },
                beforeSend: function() {
                    $.messager.progress({title:'提示', msg:'流程执行中...'});
                },
                success: function(data) {
                    $.messager.progress('close');
                    if (data.success) {
                        $('#resultContent').text(JSON.stringify(data, null, 2));
                        $('#resultArea').show();
                        $.messager.alert('成功', '流程执行完成!', 'info');
                    } else {
                        $.messager.alert('失败', data.msg, 'error');
                    }
                },
                error: function() {
                    $.messager.progress('close');
                    $.messager.alert('错误', '请求失败!', 'error');
                }
            });
        }
    </script>
</body>
</html>
8.2 流程编排页面(workflow-editor.html)
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>流程编排(类 Dify)</title>
    <link rel="stylesheet" type="text/css" href="https://www.jeasyui.com/easyui/themes/default/easyui.css">
    <script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
    <script type="text/javascript" src="https://www.jeasyui.com/easyui/jquery.easyui.min.js"></script>
    <style>
        .container {
            width: 90%;
            margin: 50px auto;
        }
        .editor-area {
            display: flex;
            gap: 20px;
            margin-top: 20px;
        }
        .node-panel {
            width: 200px;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            padding: 10px;
        }
        .canvas-area {
            flex: 1;
            border: 1px dashed #ccc;
            border-radius: 8px;
            padding: 20px;
            min-height: 500px;
        }
        .json-editor {
            margin-top: 20px;
        }
        .node-item {
            padding: 10px;
            background-color: #f0f0f0;
            border-radius: 4px;
            margin-bottom: 10px;
            cursor: move;
            text-align: center;
        }
        .result-area {
            margin-top: 20px;
            padding: 15px;
            background-color: #f5f5f5;
            border-radius: 5px;
            display: none;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>流程编排(类 Dify 可视化)</h1>
        
        <!-- 编排区域 -->
        <div class="editor-area">
            <!-- 节点面板 -->
            <div class="node-panel">
                <h4>节点库</h4>
                <div class="node-item" draggable="true" data-node-type="ai_chat">AI 问答节点</div>
                <div class="node-item" draggable="true" data-node-type="condition">条件分支节点</div>
                <div class="node-item" draggable="true" data-node-type="end">结束节点</div>
            </div>
            
            <!-- 画布区域 -->
            <div class="canvas-area" id="canvas">
                <p>🖱️ 拖拽左侧节点到画布,模拟 Dify 可视化编排</p>
                <p>(本Demo简化实现,实际Dify为拖拽式可视化,此处展示JSON配置)</p>
            </div>
        </div>
        
        <!-- JSON 编辑器 -->
        <div class="json-editor card">
            <h3>流程JSON配置(类 Dify 导出/导入)</h3>
            <textarea id="workflowJson" class="easyui-textbox" style="width: 100%; height: 300px;"
                      data-options="multiline:true"></textarea>
            <div style="margin-top: 10px;">
                <a href="javascript:void(0)" class="easyui-linkbutton" onclick="loadExample()" data-options="iconCls:'icon-load'">加载示例流程</a>
                <a href="javascript:void(0)" class="easyui-linkbutton" onclick="executeCustomWorkflow()" data-options="iconCls:'icon-play'">执行自定义流程</a>
                <a href="/dify-workflow/index" class="easyui-linkbutton" data-options="iconCls:'icon-back'">返回首页</a>
            </div>
            
            <!-- 执行结果 -->
            <div id="resultArea" class="result-area">
                <h4>自定义流程执行结果:</h4>
                <pre id="resultContent"></pre>
            </div>
        </div>
    </div>

    <script type="text/javascript">
        // 加载示例流程JSON
        function loadExample() {
            $.ajax({
                url: '/dify-workflow/example-workflow',
                type: 'GET',
                success: function(data) {
                    $('#workflowJson').text(JSON.stringify(data, null, 2));
                    $.messager.alert('成功', '示例流程已加载!', 'info');
                }
            });
        }

        // 执行自定义流程
        function executeCustomWorkflow() {
            var workflowJson = $('#workflowJson').val().trim();
            var userInput = prompt("请输入请假申请描述:", "我要请假3天,原因是感冒");
            
            if (!workflowJson) {
                $.messager.alert('提示', '请先加载/编辑流程JSON!', 'warning');
                return;
            }
            if (!userInput) {
                return;
            }

            $.ajax({
                url: '/dify-workflow/execute',
                type: 'POST',
                data: {
                    userInput: userInput,
                    workflowJson: workflowJson
                },
                beforeSend: function() {
                    $.messager.progress({title:'提示', msg:'自定义流程执行中...'});
                },
                success: function(data) {
                    $.messager.progress('close');
                    if (data.success) {
                        $('#resultContent').text(JSON.stringify(data, null, 2));
                        $('#resultArea').show();
                        $.messager.alert('成功', '自定义流程执行完成!', 'info');
                    } else {
                        $.messager.alert('失败', data.msg, 'error');
                    }
                },
                error: function() {
                    $.messager.progress('close');
                    $.messager.alert('错误', '请求失败!', 'error');
                }
            });
        }

        // 初始化拖拽(模拟Dify可视化)
        $(function() {
            $('.node-item').on('dragstart', function(e) {
                e.originalEvent.dataTransfer.setData('nodeType', $(this).data('node-type'));
            });
            
            $('#canvas').on('dragover', function(e) {
                e.preventDefault();
            });
            
            $('#canvas').on('drop', function(e) {
                e.preventDefault();
                var nodeType = e.originalEvent.dataTransfer.getData('nodeType');
                var nodeName = {
                    'ai_chat': 'AI 问答节点',
                    'condition': '条件分支节点',
                    'end': '结束节点'
                }[nodeType];
                
                $(this).append('<div style="padding:10px; margin:10px; background:#e8f4f8; border-radius:4px;">' + 
                               '✅ ' + nodeName + ' (拖拽成功,模拟Dify节点添加)</div>');
            });
        });
    </script>
</body>
</html>
三、Demo 运行与核心演示步骤
1. 环境准备
  1. 替换 ​​application.yml​​ 中的 ​​spring.ai.openai.api-key​​ 为你的 OpenAI API Key;
  2. JDK 8+/11,无需数据库(纯内存执行)。
2. 核心演示流程
步骤1:快速执行示例流程
  1. 访问 ​​http://localhost:8080/dify-workflow/index​​;
  2. 在输入框填写:​​我要请假3天,原因是感冒发烧需要休息​​;
  3. 点击「执行流程」→ 等待几秒后展示执行结果:
  • AI 分析结果:​​合理​​;
  • 生成通过意见:​​鉴于你因感冒发烧需要休息,同意你的3天请假申请,请注意休息,早日康复。​​;
  • 流程正常结束。
步骤2:测试分支逻辑
  1. 输入:​​我要请假30天,原因是想出去玩​​;
  2. 执行流程 → AI 分析结果:​​不合理​​,生成拒绝意见:​​你的请假原因仅为外出游玩,不符合公司请假规定,暂不同意你的30天请假申请,请调整请假时长或补充合理理由。​​。
步骤3:自定义流程编排
  1. 点击「流程编排」→ 点击「加载示例流程」,查看 JSON 配置;
  2. 修改 JSON 中的 Prompt 模板(如调整 AI 分析规则);
  3. 点击「执行自定义流程」→ 输入请假描述,验证自定义流程逻辑。
四、核心设计(类 Dify 核心逻辑)
flowchart TD
    A[用户输入] --> B[流程引擎初始化上下文]
    B --> C[执行起始节点(AI分析)]
    C --> D[条件节点判断AI结果]
    D -- 合理 --> E[AI生成通过意见]
    D -- 不合理 --> F[AI生成拒绝意见]
    E --> G[结束节点]
    F --> G[结束节点]
    G --> H[返回流程结果]
五、总结
  1. 核心特性:该 Demo 完整体现了 Dify 风格的“大模型工作流”核心——节点化编排 + AI 能力调用 + 参数流转 + 条件分支
  2. 关键技术点
  • Spring AI 封装为可复用的 AI 节点,支持动态 Prompt 模板(EL 表达式引用上下文);
  • 流程上下文实现节点间参数流转,模拟 Dify 的“变量”功能;
  • 条件节点支持 EL 表达式,实现动态分支跳转;
  1. 扩展方向(贴近真实 Dify)
  • 增加可视化拖拽编排(集成 mxGraph/GoJS);
  • 支持更多节点类型(HTTP 节点、数据存储节点、循环节点);
  • 集成多模型(讯飞星火、百度文心)、模型参数配置;
  • 增加流程发布、版本管理、日志监控。

这个 Demo 是 Spring AI 实现类 Dify 大模型工作流的最小可运行版本,核心是还原“AI 能力节点化、流程可视化编排、参数动态流转”的 Dify 核心体验,你可基于此扩展为完整的 AI 工作流平台。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐