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



所有评论(0)