Browse Source

实现小程序农技模块(AI问答、农业知识)

jiuling 11 months ago
parent
commit
1343e9751b
100 changed files with 7540 additions and 34 deletions
  1. 6 0
      pom.xml
  2. 12 0
      ruoyi-common/ruoyi-common-core/pom.xml
  3. 37 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/callback/BaseStreamCallback.java
  4. 74 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/callback/ChatStreamCallback.java
  5. 44 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/callback/ChatflowStreamCallback.java
  6. 49 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/callback/CompletionStreamCallback.java
  7. 64 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/callback/WorkflowStreamCallback.java
  8. 53 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/dify/EventType.java
  9. 29 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/dify/FileTransferMethod.java
  10. 68 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/dify/FileType.java
  11. 30 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/dify/ResponseMode.java
  12. 21 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/AgentMessageEvent.java
  13. 65 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/AgentThoughtEvent.java
  14. 43 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/BaseEvent.java
  15. 27 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/BaseMessageEvent.java
  16. 21 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/BaseWorkflowEvent.java
  17. 33 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/ErrorEvent.java
  18. 91 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/MessageEndEvent.java
  19. 21 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/MessageEvent.java
  20. 45 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/MessageFileEvent.java
  21. 21 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/MessageReplaceEvent.java
  22. 121 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/NodeFinishedEvent.java
  23. 79 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/NodeStartedEvent.java
  24. 14 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/PingEvent.java
  25. 21 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/TtsMessageEndEvent.java
  26. 21 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/TtsMessageEvent.java
  27. 91 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/WorkflowFinishedEvent.java
  28. 53 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/WorkflowStartedEvent.java
  29. 43 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/WorkflowTextChunkEvent.java
  30. 41 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/DifyApiException.java
  31. 34 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/AppInfoResponse.java
  32. 24 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/AppMetaResponse.java
  33. 255 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/AppParametersResponse.java
  34. 22 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/AudioToTextResponse.java
  35. 56 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/ChatMessage.java
  36. 48 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/ChatMessageResponse.java
  37. 54 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/Conversation.java
  38. 34 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/ConversationListResponse.java
  39. 235 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/MessageListResponse.java
  40. 29 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/SuggestedQuestionsResponse.java
  41. 33 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/common/Metadata.java
  42. 67 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/common/RetrieverResource.java
  43. 22 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/common/SimpleResponse.java
  44. 91 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/common/Usage.java
  45. 40 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/completion/CompletionRequest.java
  46. 43 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/completion/CompletionResponse.java
  47. 57 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/CreateDatasetRequest.java
  48. 72 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/CreateDocumentByFileRequest.java
  49. 77 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/CreateDocumentByTextRequest.java
  50. 26 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/CreateMetadataRequest.java
  51. 46 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/CreateSegmentsRequest.java
  52. 116 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/DatasetListResponse.java
  53. 100 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/DatasetResponse.java
  54. 131 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/DocumentListResponse.java
  55. 148 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/DocumentResponse.java
  56. 91 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/IndexingStatusResponse.java
  57. 32 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/MetadataResponse.java
  58. 124 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/ProcessRule.java
  59. 67 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/RetrievalModel.java
  60. 30 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/RetrieveRequest.java
  61. 213 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/RetrieveResponse.java
  62. 141 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/SegmentListResponse.java
  63. 141 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/SegmentResponse.java
  64. 37 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/UpdateDocumentByFileRequest.java
  65. 42 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/UpdateDocumentByTextRequest.java
  66. 20 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/UpdateMetadataRequest.java
  67. 56 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/UpdateSegmentRequest.java
  68. 60 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/UploadFileResponse.java
  69. 37 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/file/FileInfo.java
  70. 24 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/file/FileUploadRequest.java
  71. 50 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/file/FileUploadResponse.java
  72. 174 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/workflow/WorkflowLogsResponse.java
  73. 40 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/workflow/WorkflowRunRequest.java
  74. 91 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/workflow/WorkflowRunResponse.java
  75. 70 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/workflow/WorkflowRunStatusResponse.java
  76. 20 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/workflow/WorkflowStopResponse.java
  77. 45 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/HttpClientUtils.java
  78. 119 0
      ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/JsonUtils.java
  79. 2 3
      ruoyi-modules/ruoyi-system/pom.xml
  80. 1 4
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/RuoYiSystemApplication.java
  81. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/config/RestTemplateConfig.java
  82. 0 10
      ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
  83. 14 14
      ruoyi-modules/ruoyi-uniapp/pom.xml
  84. 1 1
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/UniAppApplication.java
  85. 15 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/config/DifyConfig.java
  86. 17 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/config/DifyProperties.java
  87. 196 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/controller/ChatController.java
  88. 235 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/controller/KnowledgeController.java
  89. 0 1
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/controller/UniAppLoginController.java
  90. 251 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/domain/KnowledgeArticle.java
  91. 132 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/domain/KnowledgeImage.java
  92. 87 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/domain/UserInteraction.java
  93. 200 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/domain/vo/KnowledgeArticleVO.java
  94. 79 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/mapper/KnowledgeArticleMapper.java
  95. 78 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/mapper/KnowledgeImageMapper.java
  96. 86 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/mapper/UserInteractionMapper.java
  97. 116 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/service/IKnowledgeService.java
  98. 354 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/service/impl/AbstractDifyClient.java
  99. 447 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/service/impl/DefaultDifyClient.java
  100. 306 0
      ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/service/impl/DefaultDifyDatasetsClient.java

+ 6 - 0
pom.xml

@@ -272,6 +272,12 @@
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-bootstrap</artifactId>
         </dependency>
+        <!--Lombok-->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>

+ 12 - 0
ruoyi-common/ruoyi-common-core/pom.xml

@@ -107,6 +107,18 @@
             <artifactId>javax.servlet-api</artifactId>
         </dependency>
 
+        <!-- OkHttp -->
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.12.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp-sse</artifactId>
+            <version>4.12.0</version>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 37 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/callback/BaseStreamCallback.java

@@ -0,0 +1,37 @@
+package com.ruoyi.common.core.callback;
+
+
+import com.ruoyi.common.core.event.ErrorEvent;
+import com.ruoyi.common.core.event.PingEvent;
+
+/**
+ * 对话流式回调接口
+ */
+public interface BaseStreamCallback {
+
+    /**
+     * 错误事件
+     *
+     * @param event 事件
+     */
+    default void onError(ErrorEvent event) {
+    }
+
+    /**
+     * 心跳
+     *
+     * @param event 事件
+     */
+    default void onPing(PingEvent event) {
+    }
+
+    /**
+     * 异常处理
+     * 用于处理非ErrorEvent类型的异常,如网络异常、解析异常等
+     *
+     * @param throwable 异常
+     */
+    default void onException(Throwable throwable) {
+    }
+
+}

+ 74 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/callback/ChatStreamCallback.java

@@ -0,0 +1,74 @@
+package com.ruoyi.common.core.callback;
+
+import com.ruoyi.common.core.event.*;
+
+/**
+ * 对话流式回调接口
+ */
+public interface ChatStreamCallback extends BaseStreamCallback {
+
+    /**
+     * 收到消息
+     *
+     * @param event 事件
+     */
+    default void onMessage(MessageEvent event) {
+    }
+
+    /**
+     * 消息结束
+     *
+     * @param event 事件
+     */
+    default void onMessageEnd(MessageEndEvent event) {
+    }
+
+    /**
+     * 收到消息文件
+     *
+     * @param event 事件
+     */
+    default void onMessageFile(MessageFileEvent event) {
+    }
+
+    /**
+     * 收到TTS消息
+     *
+     * @param event 事件
+     */
+    default void onTTSMessage(TtsMessageEvent event) {
+    }
+
+    /**
+     * TTS消息结束
+     *
+     * @param event 事件
+     */
+    default void onTTSMessageEnd(TtsMessageEndEvent event) {
+    }
+
+    /**
+     * 消息替换
+     *
+     * @param event 事件
+     */
+    default void onMessageReplace(MessageReplaceEvent event) {
+    }
+
+    /**
+     * 收到Agent消息
+     *
+     * @param event 事件
+     */
+    default void onAgentMessage(AgentMessageEvent event) {
+    }
+
+    /**
+     * Agent思考
+     *
+     * @param event 事件
+     */
+    default void onAgentThought(AgentThoughtEvent event) {
+    }
+
+}

+ 44 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/callback/ChatflowStreamCallback.java

@@ -0,0 +1,44 @@
+package com.ruoyi.common.core.callback;
+
+import com.ruoyi.common.core.event.NodeFinishedEvent;
+import com.ruoyi.common.core.event.NodeStartedEvent;
+import com.ruoyi.common.core.event.WorkflowFinishedEvent;
+import com.ruoyi.common.core.event.WorkflowStartedEvent;
+
+/**
+ * 工作流编排对话型应用流式回调接口
+ * 继承自 ChatStreamCallback,增加工作流相关回调方法
+ */
+public interface ChatflowStreamCallback extends ChatStreamCallback {
+    /**
+     * 工作流开始事件
+     *
+     * @param event 事件数据
+     */
+    default void onWorkflowStarted(WorkflowStartedEvent event) {
+    }
+
+    /**
+     * 节点开始事件
+     *
+     * @param event 事件数据
+     */
+    default void onNodeStarted(NodeStartedEvent event) {
+    }
+
+    /**
+     * 节点完成事件
+     *
+     * @param event 事件数据
+     */
+    default void onNodeFinished(NodeFinishedEvent event) {
+    }
+
+    /**
+     * 工作流完成事件
+     *
+     * @param event 事件数据
+     */
+    default void onWorkflowFinished(WorkflowFinishedEvent event) {
+    }
+}

+ 49 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/callback/CompletionStreamCallback.java

@@ -0,0 +1,49 @@
+package com.ruoyi.common.core.callback;
+
+import com.ruoyi.common.core.event.*;
+
+/**
+ * 文本生成流式响应回调接口
+ */
+public interface CompletionStreamCallback extends BaseStreamCallback {
+    /**
+     * 消息事件
+     *
+     * @param event 事件数据
+     */
+    default void onMessage(MessageEvent event) {
+    }
+
+    /**
+     * 消息结束事件
+     *
+     * @param event 事件数据
+     */
+    default void onMessageEnd(MessageEndEvent event) {
+    }
+
+    /**
+     * TTS 消息事件
+     *
+     * @param event 事件数据
+     */
+    default void onTtsMessage(TtsMessageEvent event) {
+    }
+
+    /**
+     * TTS 消息结束事件
+     *
+     * @param event 事件数据
+     */
+    default void onTtsMessageEnd(TtsMessageEndEvent event) {
+    }
+
+    /**
+     * 消息替换事件
+     *
+     * @param event 事件数据
+     */
+    default void onMessageReplace(MessageReplaceEvent event) {
+    }
+
+}

+ 64 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/callback/WorkflowStreamCallback.java

@@ -0,0 +1,64 @@
+package com.ruoyi.common.core.callback;
+
+import com.ruoyi.common.core.event.*;
+
+/**
+ * Workflow 流式响应回调接口
+ */
+public interface WorkflowStreamCallback extends BaseStreamCallback {
+    /**
+     * 工作流开始事件
+     *
+     * @param event 事件数据
+     */
+    default void onWorkflowStarted(WorkflowStartedEvent event) {
+    }
+
+    /**
+     * 节点开始事件
+     *
+     * @param event 事件数据
+     */
+    default void onNodeStarted(NodeStartedEvent event) {
+    }
+
+    /**
+     * 节点完成事件
+     *
+     * @param event 事件数据
+     */
+    default void onNodeFinished(NodeFinishedEvent event) {
+    }
+
+    /**
+     * 工作流完成事件
+     *
+     * @param event 事件数据
+     */
+    default void onWorkflowFinished(WorkflowFinishedEvent event) {
+    }
+
+    /**
+     * 工作流LLM执行过程
+     * @param event 事件数据
+     */
+    default void onWorkflowTextChunk(WorkflowTextChunkEvent event){
+    }
+
+    /**
+     * TTS 消息事件
+     *
+     * @param event 事件数据
+     */
+    default void onTtsMessage(TtsMessageEvent event) {
+    }
+
+    /**
+     * TTS 消息结束事件
+     *
+     * @param event 事件数据
+     */
+    default void onTtsMessageEnd(TtsMessageEndEvent event) {
+    }
+
+}

+ 53 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/dify/EventType.java

@@ -0,0 +1,53 @@
+package com.ruoyi.common.core.enums.dify;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Dify API 事件类型枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum EventType {
+    // 通用事件
+    MESSAGE("message"),                   // LLM 返回文本块事件
+    MESSAGE_END("message_end"),           // 消息结束事件
+    MESSAGE_REPLACE("message_replace"),   // 消息内容替换事件
+    TTS_MESSAGE("tts_message"),           // TTS 音频流事件
+    TTS_MESSAGE_END("tts_message_end"),   // TTS 音频流结束事件
+    ERROR("error"),                       // 错误事件
+    PING("ping"),                         // 心跳事件
+
+    // Agent 相关事件
+    AGENT_MESSAGE("agent_message"),       // Agent模式下返回文本块事件
+    AGENT_THOUGHT("agent_thought"),       // Agent模式下思考步骤事件
+    MESSAGE_FILE("message_file"),         // 文件事件
+
+    // Workflow 相关事件
+    WORKFLOW_STARTED("workflow_started"), // workflow 开始执行
+    NODE_STARTED("node_started"),         // node 开始执行
+    NODE_FINISHED("node_finished"),       // node 执行结束
+    WORKFLOW_FINISHED("workflow_finished"), // workflow 执行结束
+
+    // Workflow 中间节点解析
+    WORKFLOW_TEXT_CHUNK("text_chunk") // workflow llm模型输入结果
+
+    ;
+
+    private final String value;
+
+    /**
+     * 根据事件值获取对应的枚举
+     *
+     * @param value 事件值
+     * @return 对应的枚举,如果不存在则返回null
+     */
+    public static EventType fromValue(String value) {
+        for (EventType type : EventType.values()) {
+            if (type.value.equals(value)) {
+                return type;
+            }
+        }
+        return null;
+    }
+}

+ 29 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/dify/FileTransferMethod.java

@@ -0,0 +1,29 @@
+package com.ruoyi.common.core.enums.dify;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 文件传输方式
+ */
+@Getter
+@AllArgsConstructor
+public enum FileTransferMethod {
+    /**
+     * 远程 URL
+     */
+    REMOTE_URL("remote_url"),
+
+    /**
+     * 本地文件
+     */
+    LOCAL_FILE("local_file");
+
+    private final String value;
+
+    @JsonValue
+    public String getValue() {
+        return value;
+    }
+}

+ 68 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/dify/FileType.java

@@ -0,0 +1,68 @@
+package com.ruoyi.common.core.enums.dify;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 文件类型
+ */
+@Getter
+@AllArgsConstructor
+@RequiredArgsConstructor
+public enum FileType {
+    /**
+     * 文档类型
+     */
+    DOCUMENT("document", new String[]{"TXT", "MD", "MARKDOWN", "PDF", "HTML", "XLSX", "XLS", "DOCX", "CSV", "EML", "MSG", "PPTX", "PPT", "XML", "EPUB"}),
+
+    /**
+     * 图片类型
+     */
+    IMAGE("image", new String[]{"JPG", "JPEG", "PNG", "GIF", "WEBP", "SVG"}),
+
+    /**
+     * 音频类型
+     */
+    AUDIO("audio", new String[]{"MP3", "M4A", "WAV", "WEBM", "AMR"}),
+
+    /**
+     * 视频类型
+     */
+    VIDEO("video", new String[]{"MP4", "MOV", "MPEG", "MPGA"}),
+
+    /**
+     * 自定义类型
+     */
+    CUSTOM("custom");
+
+    private final String value;
+
+    @Getter
+    private String[] fileExtensions;
+
+    @JsonValue
+    public String getValue() {
+        return value;
+    }
+
+    public static FileType getByFileExtension(String fileExtension) {
+        if (fileExtension == null) {
+            return CUSTOM;
+        }
+        if (fileExtension.contains(".")) {
+            fileExtension = fileExtension.substring(fileExtension.lastIndexOf(".") + 1);
+        }
+        for (FileType fileType : values()) {
+            if (fileType.getFileExtensions() != null) {
+                for (String extension : fileType.getFileExtensions()) {
+                    if (extension.equalsIgnoreCase(fileExtension)) {
+                        return fileType;
+                    }
+                }
+            }
+        }
+        return CUSTOM;
+    }
+}

+ 30 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/dify/ResponseMode.java

@@ -0,0 +1,30 @@
+package com.ruoyi.common.core.enums.dify;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Dify 响应模式
+ */
+@Getter
+@AllArgsConstructor
+public enum ResponseMode {
+    /**
+     * 流式模式(推荐)。基于 SSE(Server-Sent Events)实现类似打字机输出方式的流式返回。
+     */
+    STREAMING("streaming"),
+
+    /**
+     * 阻塞模式,等待执行完毕后返回结果。(请求若流程较长可能会被中断)。
+     * 由于 Cloudflare 限制,请求会在 100 秒超时无返回后中断。
+     */
+    BLOCKING("blocking");
+
+    private final String value;
+
+    @JsonValue
+    public String getValue() {
+        return value;
+    }
+}

+ 21 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/AgentMessageEvent.java

@@ -0,0 +1,21 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * Agent模式下返回文本块事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class AgentMessageEvent extends BaseMessageEvent {
+
+    /**
+     * LLM 返回文本块内容
+     */
+    @JsonProperty("answer")
+    private String answer;
+}

+ 65 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/AgentThoughtEvent.java

@@ -0,0 +1,65 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * Agent模式下思考步骤事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class AgentThoughtEvent extends BaseMessageEvent {
+
+    /**
+     * agent_thought ID,每一轮Agent迭代都会有一个唯一的id
+     */
+    @JsonProperty("id")
+    private String id;
+
+    /**
+     * agent_thought在消息中的位置,如第一轮迭代position为1
+     */
+    @JsonProperty("position")
+    private Integer position;
+
+    /**
+     * agent的思考内容
+     */
+    @JsonProperty("thought")
+    private String thought;
+
+    /**
+     * 工具调用的返回结果
+     */
+    @JsonProperty("observation")
+    private String observation;
+
+    /**
+     * 使用的工具列表,以 ; 分割多个工具
+     */
+    @JsonProperty("tool")
+    private String tool;
+
+    /**
+     * 工具的输入,JSON格式的字符串(object)
+     */
+    @JsonProperty("tool_input")
+    private String toolInput;
+
+    /**
+     * 当前 agent_thought 关联的文件ID
+     */
+    @JsonProperty("message_files")
+    private List<String> messageFiles;
+
+    /**
+     * 文件ID
+     */
+    @JsonProperty("file_id")
+    private String fileId;
+}

+ 43 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/BaseEvent.java

@@ -0,0 +1,43 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.ruoyi.common.core.enums.dify.EventType;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 所有事件的基类
+ */
+@Data
+@NoArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class BaseEvent {
+
+    /**
+     * 事件类型
+     */
+    @JsonProperty("event")
+    private String event;
+
+    /**
+     * 创建时间戳
+     */
+    @JsonProperty("created_at")
+    private Long createdAt;
+
+    /**
+     * 任务ID,用于请求跟踪和停止响应
+     */
+    @JsonProperty("task_id")
+    private String taskId;
+
+    /**
+     * 获取事件类型
+     *
+     * @return 事件类型枚举
+     */
+    public EventType getEventType() {
+        return EventType.fromValue(event);
+    }
+}

+ 27 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/BaseMessageEvent.java

@@ -0,0 +1,27 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 消息事件的基类
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public abstract class BaseMessageEvent extends BaseEvent {
+
+    /**
+     * 消息唯一ID
+     */
+    @JsonProperty("id")
+    private String messageId;
+
+    /**
+     * 会话ID(对话型应用特有)
+     */
+    @JsonProperty("conversation_id")
+    private String conversationId;
+}

+ 21 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/BaseWorkflowEvent.java

@@ -0,0 +1,21 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 工作流事件的基类
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public abstract class BaseWorkflowEvent extends BaseEvent {
+
+    /**
+     * 工作流执行ID
+     */
+    @JsonProperty("workflow_run_id")
+    private String workflowRunId;
+}

+ 33 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/ErrorEvent.java

@@ -0,0 +1,33 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 错误事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class ErrorEvent extends BaseMessageEvent {
+
+    /**
+     * HTTP 状态码
+     */
+    @JsonProperty("status")
+    private Integer status;
+
+    /**
+     * 错误码
+     */
+    @JsonProperty("code")
+    private String code;
+
+    /**
+     * 错误消息
+     */
+    @JsonProperty("message")
+    private String message;
+}

+ 91 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/MessageEndEvent.java

@@ -0,0 +1,91 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.ruoyi.common.core.model.common.Metadata;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 消息结束事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class MessageEndEvent extends BaseMessageEvent {
+
+    /**
+     * 元数据
+     */
+    @JsonProperty("metadata")
+    private Metadata metadata;
+
+    /**
+     * files
+     */
+    @JsonProperty("files")
+    private List<MessageEndFile> files;
+
+
+    @Data
+    @NoArgsConstructor
+    public static class MessageEndFile {
+        /**
+         * id
+         */
+        private String id;
+
+        /**
+         * 所属租户的唯一标识符
+         */
+        private String tenantId;
+
+        /**
+         * 文件类型分类(例如:image=图片,document=文档等)
+         */
+        private String type;
+
+        /**
+         * 文件传输方式(tool_file=通过工具上传)
+         */
+        private String transferMethod;
+
+        /**
+         * 远程存储地址(当文件存储在第三方时使用)
+         */
+        private String remoteUrl;
+
+        /**
+         * 关联业务对象ID(与文件相关的业务实体标识)
+         */
+        private String relatedId;
+
+        /**
+         * 完整文件名(包含扩展名)
+         */
+        @JsonProperty("filename")
+        private String filename;
+
+        /**
+         * 文件扩展名(包含点符号,例如:.png)
+         */
+        private String extension;
+
+        /**
+         * 文件MIME类型(用于标识文件格式,例如:image/png)
+         */
+        private String mimeType;
+
+        /**
+         * 文件大小(单位:字节)
+         */
+        private Long size;
+
+        /**
+         * 带签名参数的访问地址(包含时间戳、随机数和数字签名)
+         */
+        private String url;
+    }
+}

+ 21 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/MessageEvent.java

@@ -0,0 +1,21 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * LLM 返回文本块事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class MessageEvent extends BaseMessageEvent {
+
+    /**
+     * LLM 返回文本块内容
+     */
+    @JsonProperty("answer")
+    private String answer;
+}

+ 45 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/MessageFileEvent.java

@@ -0,0 +1,45 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 文件事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class MessageFileEvent extends BaseEvent {
+
+    /**
+     * 文件唯一ID
+     */
+    @JsonProperty("id")
+    private String id;
+
+    /**
+     * 文件类型,目前仅为image
+     */
+    @JsonProperty("type")
+    private String type;
+
+    /**
+     * 文件归属,user或assistant,该接口返回仅为 assistant
+     */
+    @JsonProperty("belongs_to")
+    private String belongsTo;
+
+    /**
+     * 文件访问地址
+     */
+    @JsonProperty("url")
+    private String url;
+
+    /**
+     * 会话ID
+     */
+    @JsonProperty("conversation_id")
+    private String conversationId;
+}

+ 21 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/MessageReplaceEvent.java

@@ -0,0 +1,21 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 消息内容替换事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class MessageReplaceEvent extends BaseMessageEvent {
+
+    /**
+     * 替换内容(直接替换 LLM 所有回复文本)
+     */
+    @JsonProperty("answer")
+    private String answer;
+}

+ 121 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/NodeFinishedEvent.java

@@ -0,0 +1,121 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * node 执行结束事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class NodeFinishedEvent extends BaseWorkflowEvent {
+
+    /**
+     * 详细内容
+     */
+    @JsonProperty("data")
+    private NodeFinishedData data;
+
+    /**
+     * node 执行结束事件数据
+     */
+    @Data
+    @NoArgsConstructor
+    public static class NodeFinishedData {
+
+        /**
+         * node 执行 ID
+         */
+        @JsonProperty("id")
+        private String id;
+
+        /**
+         * 节点 ID
+         */
+        @JsonProperty("node_id")
+        private String nodeId;
+
+        /**
+         * 执行序号,用于展示 Tracing Node 顺序
+         */
+        @JsonProperty("index")
+        private Integer index;
+
+        /**
+         * 前置节点 ID,用于画布展示执行路径
+         */
+        @JsonProperty("predecessor_node_id")
+        private String predecessorNodeId;
+
+        /**
+         * 节点中所有使用到的前置节点变量内容
+         */
+        @JsonProperty("inputs")
+        private Map<String, Object> inputs;
+
+        /**
+         * 节点过程数据
+         */
+        @JsonProperty("process_data")
+        private Map<String, Object> processData;
+
+        /**
+         * 输出内容
+         */
+        @JsonProperty("outputs")
+        private Map<String, Object> outputs;
+
+        /**
+         * 执行状态 running / succeeded / failed / stopped
+         */
+        @JsonProperty("status")
+        private String status;
+
+        /**
+         * 错误原因
+         */
+        @JsonProperty("error")
+        private String error;
+
+        /**
+         * 耗时(s)
+         */
+        @JsonProperty("elapsed_time")
+        private Double elapsedTime;
+
+        /**
+         * 元数据
+         */
+        @JsonProperty("execution_metadata")
+        private Map<String, Object> executionMetadata;
+
+        /**
+         * 总使用 tokens
+         */
+        @JsonProperty("total_tokens")
+        private Integer totalTokens;
+
+        /**
+         * 总费用
+         */
+        @JsonProperty("total_price")
+        private Double totalPrice;
+
+        /**
+         * 货币,如 USD / RMB
+         */
+        @JsonProperty("currency")
+        private String currency;
+
+        /**
+         * 开始时间
+         */
+        @JsonProperty("created_at")
+        private Long createdAt;
+    }
+}

+ 79 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/NodeStartedEvent.java

@@ -0,0 +1,79 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * node 开始执行事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class NodeStartedEvent extends BaseWorkflowEvent {
+
+    /**
+     * 详细内容
+     */
+    @JsonProperty("data")
+    private NodeStartedData data;
+
+    /**
+     * node 开始执行事件数据
+     */
+    @Data
+    @NoArgsConstructor
+    public static class NodeStartedData {
+
+        /**
+         * workflow 执行 ID
+         */
+        @JsonProperty("id")
+        private String id;
+
+        /**
+         * 节点 ID
+         */
+        @JsonProperty("node_id")
+        private String nodeId;
+
+        /**
+         * 节点类型
+         */
+        @JsonProperty("node_type")
+        private String nodeType;
+
+        /**
+         * 节点名称
+         */
+        @JsonProperty("title")
+        private String title;
+
+        /**
+         * 执行序号,用于展示 Tracing Node 顺序
+         */
+        @JsonProperty("index")
+        private Integer index;
+
+        /**
+         * 前置节点 ID,用于画布展示执行路径
+         */
+        @JsonProperty("predecessor_node_id")
+        private String predecessorNodeId;
+
+        /**
+         * 节点中所有使用到的前置节点变量内容
+         */
+        @JsonProperty("inputs")
+        private Map<String, Object> inputs;
+
+        /**
+         * 开始时间
+         */
+        @JsonProperty("created_at")
+        private Long createdAt;
+    }
+}

+ 14 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/PingEvent.java

@@ -0,0 +1,14 @@
+package com.ruoyi.common.core.event;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 心跳事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class PingEvent extends BaseEvent {
+}

+ 21 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/TtsMessageEndEvent.java

@@ -0,0 +1,21 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * TTS 音频流结束事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class TtsMessageEndEvent extends BaseMessageEvent {
+
+    /**
+     * 结束事件是没有音频的,所以这里是空字符串
+     */
+    @JsonProperty("audio")
+    private String audio;
+}

+ 21 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/TtsMessageEvent.java

@@ -0,0 +1,21 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * TTS 音频流事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class TtsMessageEvent extends BaseMessageEvent {
+
+    /**
+     * 语音合成之后的音频块使用 Base64 编码之后的文本内容
+     */
+    @JsonProperty("audio")
+    private String audio;
+}

+ 91 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/WorkflowFinishedEvent.java

@@ -0,0 +1,91 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * workflow 执行结束事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class WorkflowFinishedEvent extends BaseWorkflowEvent {
+
+    /**
+     * 详细内容
+     */
+    @JsonProperty("data")
+    private WorkflowFinishedData data;
+
+    /**
+     * workflow 执行结束事件数据
+     */
+    @Data
+    @NoArgsConstructor
+    public static class WorkflowFinishedData {
+
+        /**
+         * workflow 执行 ID
+         */
+        @JsonProperty("id")
+        private String id;
+
+        /**
+         * 关联 Workflow ID
+         */
+        @JsonProperty("workflow_id")
+        private String workflowId;
+
+        /**
+         * 执行状态 running / succeeded / failed / stopped
+         */
+        @JsonProperty("status")
+        private String status;
+
+        /**
+         * 输出内容
+         */
+        @JsonProperty("outputs")
+        private Map<String, Object> outputs;
+
+        /**
+         * 错误原因
+         */
+        @JsonProperty("error")
+        private String error;
+
+        /**
+         * 耗时(s)
+         */
+        @JsonProperty("elapsed_time")
+        private Double elapsedTime;
+
+        /**
+         * 总使用 tokens
+         */
+        @JsonProperty("total_tokens")
+        private Integer totalTokens;
+
+        /**
+         * 总步数(冗余),默认 0
+         */
+        @JsonProperty("total_steps")
+        private Integer totalSteps;
+
+        /**
+         * 开始时间
+         */
+        @JsonProperty("created_at")
+        private Long createdAt;
+
+        /**
+         * 结束时间
+         */
+        @JsonProperty("finished_at")
+        private Long finishedAt;
+    }
+}

+ 53 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/WorkflowStartedEvent.java

@@ -0,0 +1,53 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * workflow 开始执行事件
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class WorkflowStartedEvent extends BaseWorkflowEvent {
+
+    /**
+     * 详细内容
+     */
+    @JsonProperty("data")
+    private WorkflowStartedData data;
+
+    /**
+     * workflow 开始执行事件数据
+     */
+    @Data
+    @NoArgsConstructor
+    public static class WorkflowStartedData {
+
+        /**
+         * workflow 执行 ID
+         */
+        @JsonProperty("id")
+        private String id;
+
+        /**
+         * 关联 Workflow ID
+         */
+        @JsonProperty("workflow_id")
+        private String workflowId;
+
+        /**
+         * 自增序号,App 内自增,从 1 开始
+         */
+        @JsonProperty("sequence_number")
+        private Integer sequenceNumber;
+
+        /**
+         * 开始时间
+         */
+        @JsonProperty("created_at")
+        private Long createdAt;
+    }
+}

+ 43 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/event/WorkflowTextChunkEvent.java

@@ -0,0 +1,43 @@
+package com.ruoyi.common.core.event;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * workflow llm执行过程
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class WorkflowTextChunkEvent extends BaseWorkflowEvent {
+
+    /**
+     * 详细内容
+     */
+    @JsonProperty("data")
+    private WorkflowTextChunkData data;
+
+    /**
+     * workflow llm执行过程内容
+     */
+    @Data
+    @NoArgsConstructor
+    public static class WorkflowTextChunkData {
+
+        /**
+         * workflow llm执行文本
+         */
+        @JsonProperty("text")
+        private String text;
+
+        /**
+         * workflow llm执行文本来源变量选择器
+         */
+        @JsonProperty("from_variable_selector")
+        private List<String> fromVariableSelector;
+    }
+}

+ 41 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/DifyApiException.java

@@ -0,0 +1,41 @@
+package com.ruoyi.common.core.exception;
+
+import lombok.Getter;
+
+import java.io.IOException;
+
+/**
+ * Dify API 异常
+ */
+@Getter
+public class DifyApiException extends IOException {
+    /**
+     * HTTP 状态码
+     */
+    private final int statusCode;
+
+    /**
+     * 错误代码
+     */
+    private final String errorCode;
+
+    /**
+     * 错误消息
+     */
+    private final String errorMessage;
+
+    /**
+     * 构造函数
+     *
+     * @param statusCode   HTTP 状态码
+     * @param errorCode    错误代码
+     * @param errorMessage 错误消息
+     */
+    public DifyApiException(int statusCode, String errorCode, String errorMessage) {
+        super(String.format("API 错误: %s (%d) - %s", errorCode, statusCode, errorMessage));
+        this.statusCode = statusCode;
+        this.errorCode = errorCode;
+        this.errorMessage = errorMessage;
+    }
+
+}

+ 34 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/AppInfoResponse.java

@@ -0,0 +1,34 @@
+package com.ruoyi.common.core.model.chat;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 应用信息响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AppInfoResponse {
+    /**
+     * 应用名称
+     */
+    private String name;
+
+    /**
+     * 应用描述
+     */
+    private String description;
+
+    /**
+     * 应用标签
+     */
+    private List<String> tags;
+}

+ 24 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/AppMetaResponse.java

@@ -0,0 +1,24 @@
+package com.ruoyi.common.core.model.chat;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * 应用元数据响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AppMetaResponse {
+    /**
+     * 工具图标
+     */
+    private Map<String, Object> toolIcons;
+}

+ 255 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/AppParametersResponse.java

@@ -0,0 +1,255 @@
+package com.ruoyi.common.core.model.chat;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 应用参数响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AppParametersResponse {
+
+    /**
+     * 开场白
+     */
+    private String openingStatement;
+
+    /**
+     * 开场推荐问题列表
+     */
+    private List<String> suggestedQuestions;
+
+    /**
+     * 启用回答后给出推荐问题
+     */
+    private SuggestedQuestionsAfterAnswer suggestedQuestionsAfterAnswer;
+
+    /**
+     * 语音转文本
+     */
+    private SpeechToText speechToText;
+
+    /**
+     * 引用和归属
+     */
+    private RetrieverResource retrieverResource;
+
+    /**
+     * 标记回复
+     */
+    private AnnotationReply annotationReply;
+
+    /**
+     * 用户输入表单配置
+     */
+    private List<Map<String, Object>> userInputForm;
+
+    /**
+     * 文件上传配置
+     */
+    private FileUpload fileUpload;
+
+    /**
+     * 系统参数
+     */
+    private SystemParameters systemParameters;
+
+    /**
+     * 启用回答后给出推荐问题
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class SuggestedQuestionsAfterAnswer {
+        /**
+         * 是否开启
+         */
+        private Boolean enabled;
+    }
+
+    /**
+     * 语音转文本
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class SpeechToText {
+        /**
+         * 是否开启
+         */
+        private Boolean enabled;
+    }
+
+    /**
+     * 引用和归属
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class RetrieverResource {
+        /**
+         * 是否开启
+         */
+        private Boolean enabled;
+    }
+
+    /**
+     * 标记回复
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class AnnotationReply {
+        /**
+         * 是否开启
+         */
+        private Boolean enabled;
+    }
+
+    /**
+     * 文件上传配置
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class FileUpload {
+        /**
+         * 图片设置
+         */
+        private Image image;
+        /**
+         * 是否启用文件上传功能
+         */
+        private Boolean enabled;
+        /**
+         * 允许的文件类型
+         */
+        private List<String> allowedFileTypes;
+        /**
+         * 允许的文件扩展名
+         */
+        private List<String> allowedFileExtensions;
+        /**
+         * 传递方式列表
+         */
+        private List<String> allowedFileUploadMethods;
+        /**
+         * 文件数量限制
+         */
+        private Integer numberLimits;
+        /**
+         * 文件上传配置
+         * dify该字段返回为驼峰命名 导致 获取数据失败
+         */
+        @JsonProperty("fileUploadConfig")
+        private FileUploadConfig fileUploadConfig;
+
+        @Data
+        @Builder
+        @NoArgsConstructor
+        @AllArgsConstructor
+        @JsonIgnoreProperties(ignoreUnknown = true)
+        public static class FileUploadConfig {
+            /**
+             * 文件大小限制 (MB)
+             */
+            private Integer fileSizeLimit;
+            /**
+             * 批量上传数量限制
+             */
+            private Integer batchCountLimit;
+            /**
+             * 图片文件大小限制 (MB)
+             */
+            private Integer imageFileSizeLimit;
+            /**
+             * 视频文件大小限制 (MB)
+             */
+            private Integer videoFileSizeLimit;
+            /**
+             * 音频文件大小限制 (MB)
+             */
+            private Integer audioFileSizeLimit;
+            /**
+             * 工作流文件上传数量限制
+             */
+            private Integer workflowFileUploadLimit;
+        }
+
+        /**
+         * 图片设置
+         */
+        @Data
+        @Builder
+        @NoArgsConstructor
+        @AllArgsConstructor
+        @JsonIgnoreProperties(ignoreUnknown = true)
+        public static class Image {
+            /**
+             * 是否开启
+             */
+            private Boolean enabled;
+
+            /**
+             * 图片数量限制
+             */
+            private Integer numberLimits;
+
+            /**
+             * 传递方式列表
+             */
+            private List<String> transferMethods;
+        }
+    }
+
+    /**
+     * 系统参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class SystemParameters {
+        /**
+         * 文档上传大小限制 (MB)
+         */
+        private Integer fileSizeLimit;
+
+        /**
+         * 图片文件上传大小限制 (MB)
+         */
+        private Integer imageFileSizeLimit;
+
+        /**
+         * 音频文件上传大小限制 (MB)
+         */
+        private Integer audioFileSizeLimit;
+
+        /**
+         * 视频文件上传大小限制 (MB)
+         */
+        private Integer videoFileSizeLimit;
+    }
+}

+ 22 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/AudioToTextResponse.java

@@ -0,0 +1,22 @@
+package com.ruoyi.common.core.model.chat;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 语音转文字响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AudioToTextResponse {
+    /**
+     * 文本内容
+     */
+    private String text;
+}

+ 56 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/ChatMessage.java

@@ -0,0 +1,56 @@
+package com.ruoyi.common.core.model.chat;
+
+import com.ruoyi.common.core.enums.dify.ResponseMode;
+import com.ruoyi.common.core.model.file.FileInfo;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 对话消息请求
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ChatMessage {
+    /**
+     * 用户输入/提问内容
+     */
+    private String query;
+
+    /**
+     * 输入参数,允许传入 App 定义的各变量值
+     */
+    private Map<String, Object> inputs;
+
+    /**
+     * 响应模式
+     */
+    private ResponseMode responseMode;
+
+    /**
+     * 用户标识
+     */
+    private String user;
+
+    /**
+     * 会话 ID,需要基于之前的聊天记录继续对话,必须传之前消息的 conversation_id
+     */
+    private String conversationId;
+
+    /**
+     * 文件列表
+     */
+    private List<FileInfo> files;
+
+    /**
+     * 自动生成标题,默认 true
+     */
+    @Builder.Default
+    private Boolean autoGenerateName = true;
+}

+ 48 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/ChatMessageResponse.java

@@ -0,0 +1,48 @@
+package com.ruoyi.common.core.model.chat;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.ruoyi.common.core.model.common.Metadata;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 对话消息响应(阻塞模式)
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ChatMessageResponse {
+    /**
+     * 消息唯一 ID
+     */
+    private String messageId;
+
+    /**
+     * 会话 ID
+     */
+    private String conversationId;
+
+    /**
+     * App 模式,固定为 chat
+     */
+    private String mode;
+
+    /**
+     * 完整回复内容
+     */
+    private String answer;
+
+    /**
+     * 元数据
+     */
+    private Metadata metadata;
+
+    /**
+     * 消息创建时间戳
+     */
+    private Long createdAt;
+}

+ 54 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/Conversation.java

@@ -0,0 +1,54 @@
+package com.ruoyi.common.core.model.chat;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * 对话会话
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Conversation {
+    /**
+     * 会话 ID
+     */
+    private String id;
+
+    /**
+     * 会话名称
+     */
+    private String name;
+
+    /**
+     * 用户输入参数
+     */
+    private Map<String, Object> inputs;
+
+    /**
+     * 会话状态
+     */
+    private String status;
+
+    /**
+     * 开场白
+     */
+    private String introduction;
+
+    /**
+     * 创建时间
+     */
+    private Long createdAt;
+
+    /**
+     * 更新时间
+     */
+    private Long updatedAt;
+}

+ 34 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/ConversationListResponse.java

@@ -0,0 +1,34 @@
+package com.ruoyi.common.core.model.chat;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 对话会话列表响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ConversationListResponse {
+    /**
+     * 返回条数
+     */
+    private Integer limit;
+
+    /**
+     * 是否存在下一页
+     */
+    private Boolean hasMore;
+
+    /**
+     * 会话列表
+     */
+    private List<Conversation> data;
+}

+ 235 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/MessageListResponse.java

@@ -0,0 +1,235 @@
+package com.ruoyi.common.core.model.chat;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 消息列表响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class MessageListResponse {
+    /**
+     * 返回条数
+     */
+    private Integer limit;
+
+    /**
+     * 是否存在下一页
+     */
+    private Boolean hasMore;
+
+    /**
+     * 消息列表
+     */
+    private List<Message> data;
+
+    /**
+     * 消息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Message {
+        /**
+         * 消息 ID
+         */
+        private String id;
+
+        /**
+         * 会话 ID
+         */
+        private String conversationId;
+        /**
+         * 父消息ID
+         */
+        private String parentMessageId;
+
+        /**
+         * 用户输入参数
+         */
+        private Map<String, Object> inputs;
+
+        /**
+         * 用户输入/提问内容
+         */
+        private String query;
+
+        /**
+         * 回答内容
+         */
+        private String answer;
+
+        /**
+         * 消息文件
+         */
+        private List<MessageFile> messageFiles;
+
+        /**
+         * 反馈信息
+         */
+        private Feedback feedback;
+
+        /**
+         * 引用和归属分段列表
+         */
+        private Object retrieverResources;
+
+        /**
+         * Agent 思考内容(仅 Agent 模式下不为空)
+         */
+        private List<AgentThought> agentThoughts;
+
+        /**
+         * 创建时间
+         */
+        private Long createdAt;
+
+        /**
+         * 状态
+         */
+        private String status;
+
+        /**
+         * 错误信息
+         */
+        private String error;
+    }
+
+    /**
+     * 消息文件
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class MessageFile {
+        /**
+         * 文件 ID
+         */
+        private String id;
+        /**
+         * 文件名
+         */
+        @JsonProperty("filename")
+        private String fileName;
+
+        /**
+         * 文件类型
+         */
+        private String type;
+
+        /**
+         * 文件 MIME 类型
+         */
+        private String mimeType;
+
+        /**
+         * 文件上传方式 remote_url/local_file
+         */
+        private String transferMethod;
+
+        /**
+         * 文件大小 字节
+         */
+        private Long size;
+
+        /**
+         * 文件 URL
+         */
+        private String url;
+
+        /**
+         * 文件归属
+         */
+        private String belongsTo;
+    }
+
+    /**
+     * 反馈信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Feedback {
+        /**
+         * 评分
+         */
+        private String rating;
+    }
+
+    /**
+     * Agent 思考内容
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class AgentThought {
+        /**
+         * 思考 ID
+         */
+        private String id;
+
+        /**
+         * 链 ID
+         */
+        private String chainId;
+
+        /**
+         * 消息 ID
+         */
+        private String messageId;
+
+        /**
+         * 位置
+         */
+        private Integer position;
+
+        /**
+         * 思考内容
+         */
+        private String thought;
+
+        /**
+         * 工具
+         */
+        private String tool;
+
+        /**
+         * 工具输入
+         */
+        private String toolInput;
+
+        /**
+         * 创建时间
+         */
+        private Long createdAt;
+
+        /**
+         * 观察结果
+         */
+        private String observation;
+
+        /**
+         * 消息文件
+         */
+        private List<String> messageFiles;
+    }
+}

+ 29 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/chat/SuggestedQuestionsResponse.java

@@ -0,0 +1,29 @@
+package com.ruoyi.common.core.model.chat;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 建议问题响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class SuggestedQuestionsResponse {
+    /**
+     * 结果
+     */
+    private String result;
+
+    /**
+     * 建议问题列表
+     */
+    private List<String> data;
+}

+ 33 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/common/Metadata.java

@@ -0,0 +1,33 @@
+package com.ruoyi.common.core.model.common;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 元数据信息
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Metadata {
+
+    /**
+     * 模型用量信息
+     */
+    @JsonProperty("usage")
+    private Usage usage;
+
+    /**
+     * 引用和归属分段列表
+     */
+    @JsonProperty("retriever_resources")
+    private List<RetrieverResource> retrieverResources;
+} 

+ 67 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/common/RetrieverResource.java

@@ -0,0 +1,67 @@
+package com.ruoyi.common.core.model.common;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 引用和归属分段信息
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RetrieverResource {
+
+    /**
+     * 在消息中的位置
+     */
+    @JsonProperty("position")
+    private Integer position;
+
+    /**
+     * 数据集ID
+     */
+    @JsonProperty("dataset_id")
+    private String datasetId;
+
+    /**
+     * 数据集名称
+     */
+    @JsonProperty("dataset_name")
+    private String datasetName;
+
+    /**
+     * 文档ID
+     */
+    @JsonProperty("document_id")
+    private String documentId;
+
+    /**
+     * 文档名称
+     */
+    @JsonProperty("document_name")
+    private String documentName;
+
+    /**
+     * 分段ID
+     */
+    @JsonProperty("segment_id")
+    private String segmentId;
+
+    /**
+     * 相似度分数
+     */
+    @JsonProperty("score")
+    private Double score;
+
+    /**
+     * 分段内容
+     */
+    @JsonProperty("content")
+    private String content;
+} 

+ 22 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/common/SimpleResponse.java

@@ -0,0 +1,22 @@
+package com.ruoyi.common.core.model.common;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 简单响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class SimpleResponse {
+    /**
+     * 结果
+     */
+    private String result;
+}

+ 91 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/common/Usage.java

@@ -0,0 +1,91 @@
+package com.ruoyi.common.core.model.common;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 模型用量信息
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Usage {
+
+    /**
+     * 提示词使用的 token 数量
+     */
+    @JsonProperty("prompt_tokens")
+    private Integer promptTokens;
+
+    /**
+     * 提示词单价
+     */
+    @JsonProperty("prompt_unit_price")
+    private String promptUnitPrice;
+
+    /**
+     * 提示词价格单位
+     */
+    @JsonProperty("prompt_price_unit")
+    private String promptPriceUnit;
+
+    /**
+     * 提示词总价
+     */
+    @JsonProperty("prompt_price")
+    private String promptPrice;
+
+    /**
+     * 补全使用的 token 数量
+     */
+    @JsonProperty("completion_tokens")
+    private Integer completionTokens;
+
+    /**
+     * 补全单价
+     */
+    @JsonProperty("completion_unit_price")
+    private String completionUnitPrice;
+
+    /**
+     * 补全价格单位
+     */
+    @JsonProperty("completion_price_unit")
+    private String completionPriceUnit;
+
+    /**
+     * 补全总价
+     */
+    @JsonProperty("completion_price")
+    private String completionPrice;
+
+    /**
+     * 总 token 数量
+     */
+    @JsonProperty("total_tokens")
+    private Integer totalTokens;
+
+    /**
+     * 总价
+     */
+    @JsonProperty("total_price")
+    private String totalPrice;
+
+    /**
+     * 货币单位
+     */
+    @JsonProperty("currency")
+    private String currency;
+
+    /**
+     * 延迟时间(秒)
+     */
+    @JsonProperty("latency")
+    private Double latency;
+} 

+ 40 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/completion/CompletionRequest.java

@@ -0,0 +1,40 @@
+package com.ruoyi.common.core.model.completion;
+
+import com.ruoyi.common.core.enums.dify.ResponseMode;
+import com.ruoyi.common.core.model.file.FileInfo;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 文本生成请求
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CompletionRequest {
+    /**
+     * 输入参数,允许传入 App 定义的各变量值
+     */
+    private Map<String, Object> inputs;
+
+    /**
+     * 响应模式
+     */
+    private ResponseMode responseMode;
+
+    /**
+     * 用户标识
+     */
+    private String user;
+
+    /**
+     * 文件列表
+     */
+    private List<FileInfo> files;
+}

+ 43 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/completion/CompletionResponse.java

@@ -0,0 +1,43 @@
+package com.ruoyi.common.core.model.completion;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.ruoyi.common.core.model.common.Metadata;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 文本生成响应(阻塞模式)
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CompletionResponse {
+    /**
+     * 消息唯一 ID
+     */
+    private String messageId;
+
+    /**
+     * App 模式,固定为 chat
+     */
+    private String mode;
+
+    /**
+     * 完整回复内容
+     */
+    private String answer;
+
+    /**
+     * 元数据
+     */
+    private Metadata metadata;
+
+    /**
+     * 消息创建时间戳
+     */
+    private Long createdAt;
+}

+ 57 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/CreateDatasetRequest.java

@@ -0,0 +1,57 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 创建知识库请求
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CreateDatasetRequest {
+    /**
+     * 知识库名称(必填)
+     */
+    private String name;
+
+    /**
+     * 知识库描述(选填)
+     */
+    private String description;
+
+    /**
+     * 索引模式(选填,建议填写)
+     * high_quality 高质量
+     * economy 经济
+     */
+    private String indexingTechnique;
+
+    /**
+     * 权限(选填,默认 only_me)
+     * only_me 仅自己
+     * all_team_members 所有团队成员
+     * partial_members 部分团队成员
+     */
+    private String permission;
+
+    /**
+     * Provider(选填,默认 vendor)
+     * vendor 上传文件
+     * external 外部知识库
+     */
+    private String provider;
+
+    /**
+     * 外部知识库 API_ID(选填)
+     */
+    private String externalKnowledgeApiId;
+
+    /**
+     * 外部知识库 ID(选填)
+     */
+    private String externalKnowledgeId;
+}

+ 72 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/CreateDocumentByFileRequest.java

@@ -0,0 +1,72 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * 通过文件创建文档请求
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CreateDocumentByFileRequest {
+    /**
+     * 源文档ID(选填)
+     */
+    private String originalDocumentId;
+
+    /**
+     * 索引方式
+     * high_quality 高质量:使用 embedding 模型进行嵌入,构建为向量数据库索引
+     * economy 经济:使用 keyword table index 的倒排索引进行构建
+     */
+    private String indexingTechnique;
+
+    /**
+     * 索引内容的形式
+     * text_model text 文档直接 embedding,经济模式默认为该模式
+     * hierarchical_model parent-child 模式
+     * qa_model Q&A 模式:为分片文档生成 Q&A 对,然后对问题进行 embedding
+     */
+    private String docForm;
+
+    /**
+     * 文档类型(选填)
+     */
+    private String docType;
+
+    /**
+     * 文档元数据(如提供文档类型则必填)
+     */
+    private Map<String, Object> docMetadata;
+
+    /**
+     * 在 Q&A 模式下,指定文档的语言,例如:English、Chinese
+     */
+    private String docLanguage;
+
+    /**
+     * 处理规则
+     */
+    private ProcessRule processRule;
+
+    /**
+     * 检索模式
+     */
+    private RetrievalModel retrievalModel;
+
+    /**
+     * Embedding 模型名称
+     */
+    private String embeddingModel;
+
+    /**
+     * Embedding 模型供应商
+     */
+    private String embeddingModelProvider;
+}

+ 77 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/CreateDocumentByTextRequest.java

@@ -0,0 +1,77 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * 通过文本创建文档请求
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CreateDocumentByTextRequest {
+    /**
+     * 文档名称
+     */
+    private String name;
+
+    /**
+     * 文档内容
+     */
+    private String text;
+
+    /**
+     * 文档类型(选填)
+     */
+    private String docType;
+
+    /**
+     * 文档元数据(如提供文档类型则必填)
+     */
+    private Map<String, Object> docMetadata;
+
+    /**
+     * 索引方式
+     * high_quality 高质量:使用 embedding 模型进行嵌入,构建为向量数据库索引
+     * economy 经济:使用 keyword table index 的倒排索引进行构建
+     */
+    private String indexingTechnique;
+
+    /**
+     * 索引内容的形式
+     * text_model text 文档直接 embedding,经济模式默认为该模式
+     * hierarchical_model parent-child 模式
+     * qa_model Q&A 模式:为分片文档生成 Q&A 对,然后对问题进行 embedding
+     */
+    private String docForm;
+
+    /**
+     * 在 Q&A 模式下,指定文档的语言,例如:English、Chinese
+     */
+    private String docLanguage;
+
+    /**
+     * 处理规则
+     */
+    private ProcessRule processRule;
+
+    /**
+     * 检索模式
+     */
+    private RetrievalModel retrievalModel;
+
+    /**
+     * Embedding 模型名称
+     */
+    private String embeddingModel;
+
+    /**
+     * Embedding 模型供应商
+     */
+    private String embeddingModelProvider;
+}

+ 26 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/CreateMetadataRequest.java

@@ -0,0 +1,26 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 新增元数据
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CreateMetadataRequest {
+    /**
+     * 元数据类型,必填
+     */
+    private String type;
+
+    /**
+     * 元数据名称,必填
+     */
+    private String name;
+
+}

+ 46 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/CreateSegmentsRequest.java

@@ -0,0 +1,46 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 新增文档分段请求
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CreateSegmentsRequest {
+    /**
+     * 分段列表
+     */
+    private List<SegmentInfo> segments;
+
+    /**
+     * 分段信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class SegmentInfo {
+        /**
+         * 文本内容/问题内容
+         */
+        private String content;
+
+        /**
+         * 答案内容
+         */
+        private String answer;
+
+        /**
+         * 关键字
+         */
+        private List<String> keywords;
+    }
+}

+ 116 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/DatasetListResponse.java

@@ -0,0 +1,116 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 知识库列表响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DatasetListResponse {
+    /**
+     * 知识库列表
+     */
+    private List<DatasetInfo> data;
+
+    /**
+     * 是否有更多
+     */
+    private Boolean hasMore;
+
+    /**
+     * 每页数量
+     */
+    private Integer limit;
+
+    /**
+     * 总数
+     */
+    private Integer total;
+
+    /**
+     * 页码
+     */
+    private Integer page;
+
+    /**
+     * 知识库信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class DatasetInfo {
+        /**
+         * 知识库ID
+         */
+        private String id;
+
+        /**
+         * 知识库名称
+         */
+        private String name;
+
+        /**
+         * 知识库描述
+         */
+        private String description;
+
+        /**
+         * 权限
+         */
+        private String permission;
+
+        /**
+         * 数据源类型
+         */
+        private String dataSourceType;
+
+        /**
+         * 索引技术
+         */
+        private String indexingTechnique;
+
+        /**
+         * 应用数量
+         */
+        private Integer appCount;
+
+        /**
+         * 文档数量
+         */
+        private Integer documentCount;
+
+        /**
+         * 字数
+         */
+        private Integer wordCount;
+
+        /**
+         * 创建者
+         */
+        private String createdBy;
+
+        /**
+         * 创建时间
+         */
+        private String createdAt;
+
+        /**
+         * 更新者
+         */
+        private String updatedBy;
+
+        /**
+         * 更新时间
+         */
+        private String updatedAt;
+    }
+}

+ 100 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/DatasetResponse.java

@@ -0,0 +1,100 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 知识库响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DatasetResponse {
+    /**
+     * 知识库ID
+     */
+    private String id;
+
+    /**
+     * 知识库名称
+     */
+    private String name;
+
+    /**
+     * 知识库描述
+     */
+    private String description;
+
+    /**
+     * 提供者
+     */
+    private String provider;
+
+    /**
+     * 权限
+     */
+    private String permission;
+
+    /**
+     * 数据源类型
+     */
+    private String dataSourceType;
+
+    /**
+     * 索引技术
+     */
+    private String indexingTechnique;
+
+    /**
+     * 应用数量
+     */
+    private Integer appCount;
+
+    /**
+     * 文档数量
+     */
+    private Integer documentCount;
+
+    /**
+     * 字数
+     */
+    private Integer wordCount;
+
+    /**
+     * 创建者
+     */
+    private String createdBy;
+
+    /**
+     * 创建时间
+     */
+    private Long createdAt;
+
+    /**
+     * 更新者
+     */
+    private String updatedBy;
+
+    /**
+     * 更新时间
+     */
+    private Long updatedAt;
+
+    /**
+     * Embedding模型
+     */
+    private String embeddingModel;
+
+    /**
+     * Embedding模型提供商
+     */
+    private String embeddingModelProvider;
+
+    /**
+     * Embedding是否可用
+     */
+    private Boolean embeddingAvailable;
+}

+ 131 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/DocumentListResponse.java

@@ -0,0 +1,131 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 文档列表响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DocumentListResponse {
+    /**
+     * 文档列表
+     */
+    private List<DocumentInfo> data;
+
+    /**
+     * 是否有更多
+     */
+    private Boolean hasMore;
+
+    /**
+     * 每页数量
+     */
+    private Integer limit;
+
+    /**
+     * 总数
+     */
+    private Integer total;
+
+    /**
+     * 页码
+     */
+    private Integer page;
+
+    /**
+     * 文档信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class DocumentInfo {
+        /**
+         * 文档ID
+         */
+        private String id;
+
+        /**
+         * 位置
+         */
+        private Integer position;
+
+        /**
+         * 数据源类型
+         */
+        private String dataSourceType;
+
+        /**
+         * 数据源信息
+         */
+        private Object dataSourceInfo;
+
+        /**
+         * 知识库处理规则ID
+         */
+        private String datasetProcessRuleId;
+
+        /**
+         * 文档名称
+         */
+        private String name;
+
+        /**
+         * 创建来源
+         */
+        private String createdFrom;
+
+        /**
+         * 创建者
+         */
+        private String createdBy;
+
+        /**
+         * 创建时间
+         */
+        private Long createdAt;
+
+        /**
+         * 令牌数
+         */
+        private Integer tokens;
+
+        /**
+         * 索引状态
+         */
+        private String indexingStatus;
+
+        /**
+         * 错误信息
+         */
+        private String error;
+
+        /**
+         * 是否启用
+         */
+        private Boolean enabled;
+
+        /**
+         * 禁用时间
+         */
+        private Long disabledAt;
+
+        /**
+         * 禁用者
+         */
+        private String disabledBy;
+
+        /**
+         * 是否归档
+         */
+        private Boolean archived;
+    }
+}

+ 148 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/DocumentResponse.java

@@ -0,0 +1,148 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 文档响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DocumentResponse {
+    /**
+     * 文档信息
+     */
+    private Document document;
+
+    /**
+     * 批次号
+     */
+    private String batch;
+
+    /**
+     * 文档信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Document {
+        /**
+         * 文档ID
+         */
+        private String id;
+
+        /**
+         * 位置
+         */
+        private Integer position;
+
+        /**
+         * 数据源类型
+         */
+        private String dataSourceType;
+
+        /**
+         * 数据源信息
+         */
+        private DataSourceInfo dataSourceInfo;
+
+        /**
+         * 知识库处理规则ID
+         */
+        private String datasetProcessRuleId;
+
+        /**
+         * 文档名称
+         */
+        private String name;
+
+        /**
+         * 创建来源
+         */
+        private String createdFrom;
+
+        /**
+         * 创建者
+         */
+        private String createdBy;
+
+        /**
+         * 创建时间
+         */
+        private Long createdAt;
+
+        /**
+         * 令牌数
+         */
+        private Integer tokens;
+
+        /**
+         * 索引状态
+         */
+        private String indexingStatus;
+
+        /**
+         * 错误信息
+         */
+        private String error;
+
+        /**
+         * 是否启用
+         */
+        private Boolean enabled;
+
+        /**
+         * 禁用时间
+         */
+        private Long disabledAt;
+
+        /**
+         * 禁用者
+         */
+        private String disabledBy;
+
+        /**
+         * 是否归档
+         */
+        private Boolean archived;
+
+        /**
+         * 显示状态
+         */
+        private String displayStatus;
+
+        /**
+         * 字数
+         */
+        private Integer wordCount;
+
+        /**
+         * 命中次数
+         */
+        private Integer hitCount;
+
+        /**
+         * 文档形式
+         */
+        private String docForm;
+    }
+
+    /**
+     * 数据源信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class DataSourceInfo {
+        /**
+         * 上传文件ID
+         */
+        private String uploadFileId;
+    }
+}

+ 91 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/IndexingStatusResponse.java

@@ -0,0 +1,91 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 文档嵌入状态响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IndexingStatusResponse {
+    /**
+     * 状态列表
+     */
+    private List<IndexingStatus> data;
+
+    /**
+     * 嵌入状态
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class IndexingStatus {
+        /**
+         * 文档ID
+         */
+        private String id;
+
+        /**
+         * 索引状态
+         */
+        private String indexingStatus;
+
+        /**
+         * 处理开始时间
+         */
+        private Double processingStartedAt;
+
+        /**
+         * 解析完成时间
+         */
+        private Double parsingCompletedAt;
+
+        /**
+         * 清洗完成时间
+         */
+        private Double cleaningCompletedAt;
+
+        /**
+         * 分段完成时间
+         */
+        private Double splittingCompletedAt;
+
+        /**
+         * 完成时间
+         */
+        private Double completedAt;
+
+        /**
+         * 暂停时间
+         */
+        private Double pausedAt;
+
+        /**
+         * 错误信息
+         */
+        private String error;
+
+        /**
+         * 停止时间
+         */
+        private Double stoppedAt;
+
+        /**
+         * 已完成分段数
+         */
+        private Integer completedSegments;
+
+        /**
+         * 总分段数
+         */
+        private Integer totalSegments;
+    }
+}

+ 32 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/MetadataResponse.java

@@ -0,0 +1,32 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 元数据响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MetadataResponse {
+
+    /**
+     * 元数据ID
+     */
+    private String id;
+
+    /**
+     * 元数据类型
+     */
+    private String type;
+
+    /**
+     * 元数据名称
+     */
+    private String name;
+
+}

+ 124 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/ProcessRule.java

@@ -0,0 +1,124 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 处理规则
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProcessRule {
+    /**
+     * 清洗、分段模式
+     * automatic 自动
+     * custom 自定义
+     */
+    private String mode;
+
+    /**
+     * 自定义规则(自动模式下,该字段为空)
+     */
+    private Rules rules;
+
+    /**
+     * 自定义规则
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Rules {
+        /**
+         * 预处理规则
+         */
+        private List<PreProcessingRule> preProcessingRules;
+
+        /**
+         * 分段规则
+         */
+        private Segmentation segmentation;
+
+        /**
+         * 父分段的召回模式
+         * full-doc 全文召回
+         * paragraph 段落召回
+         */
+        private String parentMode;
+
+        /**
+         * 子分段规则
+         */
+        private SubchunkSegmentation subchunkSegmentation;
+    }
+
+    /**
+     * 预处理规则
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class PreProcessingRule {
+        /**
+         * 预处理规则的唯一标识符
+         * remove_extra_spaces 替换连续空格、换行符、制表符
+         * remove_urls_emails 删除 URL、电子邮件地址
+         */
+        private String id;
+
+        /**
+         * 是否选中该规则
+         */
+        private Boolean enabled;
+    }
+
+    /**
+     * 分段规则
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Segmentation {
+        /**
+         * 自定义分段标识符,目前仅允许设置一个分隔符。默认为 \n
+         */
+        private String separator;
+
+        /**
+         * 最大长度(token)默认为 1000
+         */
+        private Integer maxTokens;
+    }
+
+    /**
+     * 子分段规则
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class SubchunkSegmentation {
+        /**
+         * 分段标识符,目前仅允许设置一个分隔符。默认为 ***
+         */
+        private String separator;
+
+        /**
+         * 最大长度 (token) 需要校验小于父级的长度
+         */
+        private Integer maxTokens;
+
+        /**
+         * 分段重叠
+         */
+        private Integer chunkOverlap;
+    }
+}

+ 67 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/RetrievalModel.java

@@ -0,0 +1,67 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 检索模式
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class RetrievalModel {
+    /**
+     * 检索方法
+     * hybrid_search 混合检索
+     * semantic_search 语义检索
+     * full_text_search 全文检索
+     */
+    private String searchMethod;
+
+    /**
+     * 是否开启rerank
+     */
+    private Boolean rerankingEnable;
+
+    /**
+     * Rerank 模型配置
+     */
+    private RerankingModel rerankingModel;
+
+    /**
+     * 召回条数
+     */
+    private Integer topK;
+
+    /**
+     * 是否开启召回分数限制
+     */
+    private Boolean scoreThresholdEnabled;
+
+    /**
+     * 召回分数限制
+     */
+    private Float scoreThreshold;
+
+    /**
+     * Rerank 模型配置
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class RerankingModel {
+        /**
+         * Rerank 模型的提供商
+         */
+        private String rerankingProviderName;
+
+        /**
+         * Rerank 模型的名称
+         */
+        private String rerankingModelName;
+    }
+}

+ 30 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/RetrieveRequest.java

@@ -0,0 +1,30 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 检索请求
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class RetrieveRequest {
+    /**
+     * 检索关键词
+     */
+    private String query;
+
+    /**
+     * 检索参数
+     */
+    private RetrievalModel retrievalModel;
+
+    /**
+     * 外部检索模型
+     */
+    private Object externalRetrievalModel;
+}

+ 213 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/RetrieveResponse.java

@@ -0,0 +1,213 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 检索响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class RetrieveResponse {
+    /**
+     * 查询信息
+     */
+    private QueryInfo query;
+
+    /**
+     * 记录列表
+     */
+    private List<Record> records;
+
+    /**
+     * 查询信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class QueryInfo {
+        /**
+         * 内容
+         */
+        private String content;
+    }
+
+    /**
+     * 记录
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Record {
+        /**
+         * 分段信息
+         */
+        private SegmentInfo segment;
+
+        /**
+         * 分数
+         */
+        private Double score;
+
+        /**
+         * TSNE位置
+         */
+        private Object tsnePosition;
+    }
+
+    /**
+     * 分段信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class SegmentInfo {
+        /**
+         * 分段ID
+         */
+        private String id;
+
+        /**
+         * 位置
+         */
+        private Integer position;
+
+        /**
+         * 文档ID
+         */
+        private String documentId;
+
+        /**
+         * 内容
+         */
+        private String content;
+
+        /**
+         * 答案
+         */
+        private String answer;
+
+        /**
+         * 字数
+         */
+        private Integer wordCount;
+
+        /**
+         * 令牌数
+         */
+        private Integer tokens;
+
+        /**
+         * 关键字
+         */
+        private List<String> keywords;
+
+        /**
+         * 索引节点ID
+         */
+        private String indexNodeId;
+
+        /**
+         * 索引节点哈希
+         */
+        private String indexNodeHash;
+
+        /**
+         * 命中次数
+         */
+        private Integer hitCount;
+
+        /**
+         * 是否启用
+         */
+        private Boolean enabled;
+
+        /**
+         * 禁用时间
+         */
+        private Long disabledAt;
+
+        /**
+         * 禁用者
+         */
+        private String disabledBy;
+
+        /**
+         * 状态
+         */
+        private String status;
+
+        /**
+         * 创建者
+         */
+        private String createdBy;
+
+        /**
+         * 创建时间
+         */
+        private Long createdAt;
+
+        /**
+         * 索引时间
+         */
+        private Long indexingAt;
+
+        /**
+         * 完成时间
+         */
+        private Long completedAt;
+
+        /**
+         * 错误信息
+         */
+        private String error;
+
+        /**
+         * 停止时间
+         */
+        private Long stoppedAt;
+
+        /**
+         * 文档信息
+         */
+        private DocumentInfo document;
+    }
+
+    /**
+     * 文档信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class DocumentInfo {
+        /**
+         * 文档ID
+         */
+        private String id;
+
+        /**
+         * 数据源类型
+         */
+        private String dataSourceType;
+
+        /**
+         * 文档名称
+         */
+        private String name;
+
+        /**
+         * 文档类型
+         */
+        private String docType;
+    }
+}

+ 141 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/SegmentListResponse.java

@@ -0,0 +1,141 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 分段列表响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SegmentListResponse {
+    /**
+     * 分段列表
+     */
+    private List<SegmentInfo> data;
+
+    /**
+     * 文档形式
+     */
+    private String docForm;
+
+    /**
+     * 分段信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class SegmentInfo {
+        /**
+         * 分段ID
+         */
+        private String id;
+
+        /**
+         * 位置
+         */
+        private Integer position;
+
+        /**
+         * 文档ID
+         */
+        private String documentId;
+
+        /**
+         * 内容
+         */
+        private String content;
+
+        /**
+         * 答案
+         */
+        private String answer;
+
+        /**
+         * 字数
+         */
+        private Integer wordCount;
+
+        /**
+         * 令牌数
+         */
+        private Integer tokens;
+
+        /**
+         * 关键字
+         */
+        private List<String> keywords;
+
+        /**
+         * 索引节点ID
+         */
+        private String indexNodeId;
+
+        /**
+         * 索引节点哈希
+         */
+        private String indexNodeHash;
+
+        /**
+         * 命中次数
+         */
+        private Integer hitCount;
+
+        /**
+         * 是否启用
+         */
+        private Boolean enabled;
+
+        /**
+         * 禁用时间
+         */
+        private Long disabledAt;
+
+        /**
+         * 禁用者
+         */
+        private String disabledBy;
+
+        /**
+         * 状态
+         */
+        private String status;
+
+        /**
+         * 创建者
+         */
+        private String createdBy;
+
+        /**
+         * 创建时间
+         */
+        private Long createdAt;
+
+        /**
+         * 索引时间
+         */
+        private Long indexingAt;
+
+        /**
+         * 完成时间
+         */
+        private Long completedAt;
+
+        /**
+         * 错误信息
+         */
+        private String error;
+
+        /**
+         * 停止时间
+         */
+        private Long stoppedAt;
+    }
+}

+ 141 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/SegmentResponse.java

@@ -0,0 +1,141 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 分段响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SegmentResponse {
+    /**
+     * 分段列表
+     */
+    private List<SegmentInfo> data;
+
+    /**
+     * 文档形式
+     */
+    private String docForm;
+
+    /**
+     * 分段信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class SegmentInfo {
+        /**
+         * 分段ID
+         */
+        private String id;
+
+        /**
+         * 位置
+         */
+        private Integer position;
+
+        /**
+         * 文档ID
+         */
+        private String documentId;
+
+        /**
+         * 内容
+         */
+        private String content;
+
+        /**
+         * 答案
+         */
+        private String answer;
+
+        /**
+         * 字数
+         */
+        private Integer wordCount;
+
+        /**
+         * 令牌数
+         */
+        private Integer tokens;
+
+        /**
+         * 关键字
+         */
+        private List<String> keywords;
+
+        /**
+         * 索引节点ID
+         */
+        private String indexNodeId;
+
+        /**
+         * 索引节点哈希
+         */
+        private String indexNodeHash;
+
+        /**
+         * 命中次数
+         */
+        private Integer hitCount;
+
+        /**
+         * 是否启用
+         */
+        private Boolean enabled;
+
+        /**
+         * 禁用时间
+         */
+        private Long disabledAt;
+
+        /**
+         * 禁用者
+         */
+        private String disabledBy;
+
+        /**
+         * 状态
+         */
+        private String status;
+
+        /**
+         * 创建者
+         */
+        private String createdBy;
+
+        /**
+         * 创建时间
+         */
+        private Long createdAt;
+
+        /**
+         * 索引时间
+         */
+        private Long indexingAt;
+
+        /**
+         * 完成时间
+         */
+        private Long completedAt;
+
+        /**
+         * 错误信息
+         */
+        private String error;
+
+        /**
+         * 停止时间
+         */
+        private Long stoppedAt;
+    }
+}

+ 37 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/UpdateDocumentByFileRequest.java

@@ -0,0 +1,37 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * 通过文件更新文档请求
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class UpdateDocumentByFileRequest {
+    /**
+     * 文档名称(选填)
+     */
+    private String name;
+
+    /**
+     * 处理规则(选填)
+     */
+    private ProcessRule processRule;
+
+    /**
+     * 文档类型(选填)
+     */
+    private String docType;
+
+    /**
+     * 文档元数据(如提供文档类型则必填)
+     */
+    private Map<String, Object> docMetadata;
+}

+ 42 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/UpdateDocumentByTextRequest.java

@@ -0,0 +1,42 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * 通过文本更新文档请求
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class UpdateDocumentByTextRequest {
+    /**
+     * 文档名称(选填)
+     */
+    private String name;
+
+    /**
+     * 文档内容(选填)
+     */
+    private String text;
+
+    /**
+     * 文档类型(选填)
+     */
+    private String docType;
+
+    /**
+     * 文档元数据(如提供文档类型则必填)
+     */
+    private Map<String, Object> docMetadata;
+
+    /**
+     * 处理规则(选填)
+     */
+    private ProcessRule processRule;
+}

+ 20 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/UpdateMetadataRequest.java

@@ -0,0 +1,20 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 更新元数据
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class UpdateMetadataRequest {
+    /**
+     * 元数据名称,必填
+     */
+    private String name;
+}

+ 56 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/UpdateSegmentRequest.java

@@ -0,0 +1,56 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 更新分段请求
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class UpdateSegmentRequest {
+    /**
+     * 分段信息
+     */
+    private SegmentInfo segment;
+
+    /**
+     * 分段信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class SegmentInfo {
+        /**
+         * 文本内容/问题内容
+         */
+        private String content;
+
+        /**
+         * 答案内容
+         */
+        private String answer;
+
+        /**
+         * 关键字
+         */
+        private List<String> keywords;
+
+        /**
+         * 是否启用
+         */
+        private Boolean enabled;
+
+        /**
+         * 是否重新生成子分段
+         */
+        private Boolean regenerateChildChunks;
+    }
+}

+ 60 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/datasets/UploadFileResponse.java

@@ -0,0 +1,60 @@
+package com.ruoyi.common.core.model.datasets;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 上传文件响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class UploadFileResponse {
+    /**
+     * 文件ID
+     */
+    private String id;
+
+    /**
+     * 文件名
+     */
+    private String name;
+
+    /**
+     * 文件大小
+     */
+    private Long size;
+
+    /**
+     * 文件扩展名
+     */
+    private String extension;
+
+    /**
+     * 文件URL
+     */
+    private String url;
+
+    /**
+     * 文件下载URL
+     */
+    private String downloadUrl;
+
+    /**
+     * 文件MIME类型
+     */
+    private String mimeType;
+
+    /**
+     * 创建者
+     */
+    private String createdBy;
+
+    /**
+     * 创建时间
+     */
+    private Long createdAt;
+}

+ 37 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/file/FileInfo.java

@@ -0,0 +1,37 @@
+package com.ruoyi.common.core.model.file;
+
+import com.ruoyi.common.core.enums.dify.FileTransferMethod;
+import com.ruoyi.common.core.enums.dify.FileType;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 文件信息
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FileInfo {
+    /**
+     * 文件类型
+     */
+    private FileType type;
+
+    /**
+     * 传输方式
+     */
+    private FileTransferMethod transferMethod;
+
+    /**
+     * 文件 URL(当传输方式为 REMOTE_URL 时使用)
+     */
+    private String url;
+
+    /**
+     * 上传文件 ID(当传输方式为 LOCAL_FILE 时使用)
+     */
+    private String uploadFileId;
+}

+ 24 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/file/FileUploadRequest.java

@@ -0,0 +1,24 @@
+package com.ruoyi.common.core.model.file;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import okhttp3.MediaType;
+
+/**
+ * 文件上传请求
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FileUploadRequest {
+    /**
+     * 用户标识
+     */
+    private String user;
+
+    @Builder.Default
+    private MediaType mediaType = MediaType.parse("application/octet-stream");
+}

+ 50 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/file/FileUploadResponse.java

@@ -0,0 +1,50 @@
+package com.ruoyi.common.core.model.file;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 文件上传响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FileUploadResponse {
+    /**
+     * 文件 ID
+     */
+    private String id;
+
+    /**
+     * 文件名
+     */
+    private String name;
+
+    /**
+     * 文件大小(字节)
+     */
+    private Long size;
+
+    /**
+     * 文件扩展名
+     */
+    private String extension;
+
+    /**
+     * 文件 MIME 类型
+     */
+    private String mimeType;
+
+    /**
+     * 创建者 ID
+     */
+    private String createdBy;
+
+    /**
+     * 创建时间(时间戳)
+     */
+    private Long createdAt;
+}

+ 174 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/workflow/WorkflowLogsResponse.java

@@ -0,0 +1,174 @@
+package com.ruoyi.common.core.model.workflow;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 工作流日志响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WorkflowLogsResponse {
+    /**
+     * 当前页码
+     */
+    private Integer page;
+
+    /**
+     * 每页条数
+     */
+    private Integer limit;
+
+    /**
+     * 总条数
+     */
+    private Integer total;
+
+    /**
+     * 是否还有更多数据
+     */
+    private Boolean hasMore;
+
+    /**
+     * 当前页码的数据
+     */
+    private List<WorkflowLogItem> data;
+
+    /**
+     * 工作流日志项
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class WorkflowLogItem {
+        /**
+         * 标识
+         */
+        private String id;
+
+        /**
+         * Workflow 执行日志
+         */
+        private WorkflowRunInfo workflowRun;
+
+        /**
+         * 来源
+         */
+        private String createdFrom;
+
+        /**
+         * 角色
+         */
+        private String createdByRole;
+
+        /**
+         * 帐号
+         */
+        private String createdByAccount;
+
+        /**
+         * 用户
+         */
+        private EndUser createdByEndUser;
+
+        /**
+         * 创建时间
+         */
+        private Long createdAt;
+    }
+
+    /**
+     * 工作流执行信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class WorkflowRunInfo {
+        /**
+         * 标识
+         */
+        private String id;
+
+        /**
+         * 版本
+         */
+        private String version;
+
+        /**
+         * 执行状态, running / succeeded / failed / stopped
+         */
+        private String status;
+
+        /**
+         * 错误
+         */
+        private String error;
+
+        /**
+         * 耗时,单位秒
+         */
+        private Double elapsedTime;
+
+        /**
+         * 消耗的token数量
+         */
+        private Integer totalTokens;
+
+        /**
+         * 执行步骤长度
+         */
+        private Integer totalSteps;
+
+        /**
+         * 开始时间
+         */
+        private Long createdAt;
+
+        /**
+         * 结束时间
+         */
+        private Long finishedAt;
+    }
+
+    /**
+     * 终端用户
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class EndUser {
+        /**
+         * 标识
+         */
+        private String id;
+
+        /**
+         * 类型
+         */
+        private String type;
+
+        /**
+         * 是否匿名
+         */
+        private Boolean isAnonymous;
+
+        /**
+         * 会话标识
+         */
+        private String sessionId;
+
+        /**
+         * 创建时间
+         */
+        private Long createdAt;
+    }
+}

+ 40 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/workflow/WorkflowRunRequest.java

@@ -0,0 +1,40 @@
+package com.ruoyi.common.core.model.workflow;
+
+import com.ruoyi.common.core.enums.dify.ResponseMode;
+import com.ruoyi.common.core.model.file.FileInfo;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Workflow 执行请求
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WorkflowRunRequest {
+    /**
+     * 输入参数,允许传入 App 定义的各变量值
+     */
+    private Map<String, Object> inputs;
+
+    /**
+     * 响应模式
+     */
+    private ResponseMode responseMode;
+
+    /**
+     * 用户标识
+     */
+    private String user;
+
+    /**
+     * 文件列表
+     */
+    private List<FileInfo> files;
+}

+ 91 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/workflow/WorkflowRunResponse.java

@@ -0,0 +1,91 @@
+package com.ruoyi.common.core.model.workflow;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * Workflow 执行响应(阻塞模式)
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WorkflowRunResponse {
+    /**
+     * Workflow 执行 ID
+     */
+    private String workflowRunId;
+
+    /**
+     * 任务 ID
+     */
+    private String taskId;
+
+    /**
+     * 详细数据
+     */
+    private WorkflowRunData data;
+
+    /**
+     * Workflow 执行数据
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class WorkflowRunData {
+        /**
+         * Workflow 执行 ID
+         */
+        private String id;
+
+        /**
+         * 关联 Workflow ID
+         */
+        private String workflowId;
+
+        /**
+         * 执行状态
+         */
+        private String status;
+
+        /**
+         * 输出内容
+         */
+        private Map<String, Object> outputs;
+
+        /**
+         * 错误原因
+         */
+        private String error;
+
+        /**
+         * 耗时(秒)
+         */
+        private Double elapsedTime;
+
+        /**
+         * 总使用 tokens
+         */
+        private Integer totalTokens;
+
+        /**
+         * 总步数
+         */
+        private Integer totalSteps;
+
+        /**
+         * 开始时间
+         */
+        private Long createdAt;
+
+        /**
+         * 结束时间
+         */
+        private Long finishedAt;
+    }
+}

+ 70 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/workflow/WorkflowRunStatusResponse.java

@@ -0,0 +1,70 @@
+package com.ruoyi.common.core.model.workflow;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 工作流执行状态响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WorkflowRunStatusResponse {
+    /**
+     * 工作流执行 ID
+     */
+    private String id;
+
+    /**
+     * 关联的工作流 ID
+     */
+    private String workflowId;
+
+    /**
+     * 执行状态 running / succeeded / failed / stopped
+     */
+    private String status;
+
+    /**
+     * 任务输入内容
+     */
+    private Object inputs;
+
+    /**
+     * 任务输出内容
+     */
+    private Object outputs;
+
+    /**
+     * 错误原因
+     */
+    private String error;
+
+    /**
+     * 任务执行总步数
+     */
+    private Integer totalSteps;
+
+    /**
+     * 任务执行总 tokens
+     */
+    private Integer totalTokens;
+
+    /**
+     * 任务开始时间
+     */
+    private String createdAt;
+
+    /**
+     * 任务结束时间
+     */
+    private String finishedAt;
+
+    /**
+     * 耗时(s)
+     */
+    private Double elapsedTime;
+}

+ 20 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/model/workflow/WorkflowStopResponse.java

@@ -0,0 +1,20 @@
+package com.ruoyi.common.core.model.workflow;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 工作流停止响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WorkflowStopResponse {
+    /**
+     * 结果
+     */
+    private String result;
+}

+ 45 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/HttpClientUtils.java

@@ -0,0 +1,45 @@
+package com.ruoyi.common.core.utils;
+
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * HTTP客户端工具类
+ */
+public class HttpClientUtils {
+    /**
+     * JSON媒体类型
+     */
+    public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+
+    /**
+     * 创建默认的OkHttpClient
+     *
+     * @return OkHttpClient实例
+     */
+    public static OkHttpClient createDefaultClient() {
+        return new OkHttpClient.Builder()
+                .connectTimeout(30, TimeUnit.SECONDS)
+                .readTimeout(60, TimeUnit.SECONDS)
+                .writeTimeout(60, TimeUnit.SECONDS)
+                .build();
+    }
+
+    /**
+     * 创建自定义超时时间的OkHttpClient
+     *
+     * @param connectTimeout 连接超时时间(毫秒)
+     * @param readTimeout    读取超时时间(毫秒)
+     * @param writeTimeout   写入超时时间(毫秒)
+     * @return OkHttpClient实例
+     */
+    public static OkHttpClient createClient(int connectTimeout, int readTimeout, int writeTimeout) {
+        return new OkHttpClient.Builder()
+                .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
+                .readTimeout(readTimeout, TimeUnit.MILLISECONDS)
+                .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS)
+                .build();
+    }
+}

+ 119 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/JsonUtils.java

@@ -0,0 +1,119 @@
+package com.ruoyi.common.core.utils;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * JSON工具类
+ */
+@Slf4j
+public class JsonUtils {
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    static {
+        // 配置ObjectMapper
+        OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+        OBJECT_MAPPER.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
+    }
+
+    /**
+     * 判断字符串是否是合法的JSON格式
+     *
+     * @param jsonString 要检查的字符串
+     * @return 如果是合法的JSON返回true,否则返回false
+     */
+    public static boolean isValidJson(String jsonString) {
+        if (jsonString == null || jsonString.trim().isEmpty()) {
+            return false;
+        }
+        try {
+            OBJECT_MAPPER.readTree(jsonString);
+            return true;
+        } catch (JsonProcessingException e) {
+            return false;
+        }
+    }
+
+    /**
+     * 将对象转换为JSON字符串
+     *
+     * @param obj 要转换的对象
+     * @return JSON字符串
+     */
+    public static String toJson(Object obj) {
+        try {
+            return OBJECT_MAPPER.writeValueAsString(obj);
+        } catch (JsonProcessingException e) {
+            log.error("Failed to convert object to JSON", e);
+            throw new RuntimeException("Failed to convert object to JSON", e);
+        }
+    }
+
+    /**
+     * 将JSON字符串转换为指定类型的对象
+     *
+     * @param json  JSON字符串
+     * @param clazz 目标类型
+     * @param <T>   泛型类型
+     * @return 转换后的对象
+     */
+    public static <T> T fromJson(String json, Class<T> clazz) {
+        try {
+            return OBJECT_MAPPER.readValue(json, clazz);
+        } catch (IOException e) {
+            log.error("Failed to convert JSON to object", e);
+            return null;
+        }
+    }
+
+    /**
+     * 将JSON字符串转换为指定类型引用的对象
+     *
+     * @param json          JSON字符串
+     * @param typeReference 类型引用
+     * @param <T>           泛型类型
+     * @return 转换后的对象
+     */
+    public static <T> T fromJson(String json, TypeReference<T> typeReference) {
+        try {
+            return OBJECT_MAPPER.readValue(json, typeReference);
+        } catch (IOException e) {
+            log.error("Failed to convert JSON to object", e);
+            return null;
+        }
+    }
+
+    /**
+     * 将JSON字符串转换为Map
+     *
+     * @param json JSON字符串
+     * @return Map对象
+     */
+    public static Map<String, Object> jsonToMap(String json) {
+        try {
+            return OBJECT_MAPPER.readValue(json, new TypeReference<Map<String, Object>>() {
+            });
+        } catch (IOException e) {
+            log.error("Failed to convert JSON to Map", e);
+            return null;
+        }
+    }
+
+    /**
+     * 获取ObjectMapper实例
+     *
+     * @return ObjectMapper实例
+     */
+    public static ObjectMapper getObjectMapper() {
+        return OBJECT_MAPPER;
+    }
+}

+ 2 - 3
ruoyi-modules/ruoyi-system/pom.xml

@@ -8,11 +8,10 @@
         <version>3.6.5</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
-	
-    <artifactId>ruoyi-modules-system</artifactId>
 
+    <artifactId>ruoyi-system</artifactId>
     <description>
-        ruoyi-modules-system系统模块
+        ruoyi-system系统模块
     </description>
 	
     <dependencies>

+ 1 - 4
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/RuoYiSystemApplication.java

@@ -5,6 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
 import com.ruoyi.common.security.annotation.EnableCustomConfig;
 import com.ruoyi.common.security.annotation.EnableRyFeignClients;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
 import org.springframework.web.client.RestTemplate;
 
 /**
@@ -31,8 +32,4 @@ public class RuoYiSystemApplication
                 " |  |  \\    /  \\      /           \n" +
                 " ''-'   `'-'    `-..-'              ");
     }
-    @Bean
-    public RestTemplate restTemplate() {
-        return new RestTemplate();
-    }
 }

+ 1 - 1
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/config/RestTemplateConfig.java → ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/config/RestTemplateConfig.java

@@ -1,4 +1,4 @@
-package com.ruoyi.uniapp.config;
+package com.ruoyi.system.config;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;

+ 0 - 10
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java

@@ -1,30 +1,20 @@
 package com.ruoyi.system.service.impl;
 
 import java.util.*;
-import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import javax.servlet.http.HttpServletRequest;
 import javax.validation.Validator;
 
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
-import com.ruoyi.common.core.constant.CacheConstants;
-import com.ruoyi.common.core.constant.Constants;
-import com.ruoyi.common.core.constant.SecurityConstants;
 import com.ruoyi.common.core.domain.R;
-import com.ruoyi.common.core.utils.JwtUtils;
 import com.ruoyi.common.core.utils.ip.IpUtils;
-import com.ruoyi.common.core.utils.uuid.IdUtils;
-import com.ruoyi.common.redis.service.RedisService;
 import com.ruoyi.common.security.service.TokenService;
 import com.ruoyi.system.api.model.LoginUser;
 import com.ruoyi.system.domain.UniAppLoginBody;
-import io.jsonwebtoken.Jwts;
-import io.jsonwebtoken.SignatureAlgorithm;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;

+ 14 - 14
ruoyi-modules/ruoyi-uniapp/pom.xml

@@ -8,70 +8,70 @@
         <version>3.6.5</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
-	
-    <artifactId>ruoyi-modules-uniapp</artifactId>
+
+    <artifactId>ruoyi-uniapp</artifactId>
 
     <description>
         ruoyi-modules-uniapp小程序服务模块
     </description>
-	
+
     <dependencies>
 
         <!-- RuoYi System 模块 -->
         <dependency>
             <groupId>com.ruoyi</groupId>
-            <artifactId>ruoyi-modules-system</artifactId>
+            <artifactId>ruoyi-system</artifactId>
             <version>3.6.5</version>
         </dependency>
-        
+
     	<!-- SpringCloud Alibaba Nacos -->
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
         </dependency>
-        
+
         <!-- SpringCloud Alibaba Nacos Config -->
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
         </dependency>
-        
+
     	<!-- SpringCloud Alibaba Sentinel -->
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
         </dependency>
-        
+
     	<!-- SpringBoot Actuator -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-actuator</artifactId>
         </dependency>
-        
+
         <!-- Mysql Connector -->
         <dependency>
             <groupId>com.mysql</groupId>
             <artifactId>mysql-connector-j</artifactId>
         </dependency>
-        
+
         <!-- RuoYi Common DataSource -->
         <dependency>
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common-datasource</artifactId>
         </dependency>
-        
+
         <!-- RuoYi Common DataScope -->
         <dependency>
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common-datascope</artifactId>
         </dependency>
-        
+
         <!-- RuoYi Common Log -->
         <dependency>
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common-log</artifactId>
         </dependency>
-        
+
         <!-- RuoYi Common Swagger -->
         <dependency>
             <groupId>com.ruoyi</groupId>
@@ -104,5 +104,5 @@
             </plugin>
         </plugins>
     </build>
-   
+
 </project> 

+ 1 - 1
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/UniAppApplication.java

@@ -14,7 +14,7 @@ import org.springframework.context.annotation.ComponentScan;
 @EnableRyFeignClients
 @SpringBootApplication
 @ComponentScan(basePackages = {"com.ruoyi.system.*","com.ruoyi.uniapp.*"})
-@MapperScan("com.ruoyi.*.mapper")
+@MapperScan("com.ruoyi.**.mapper")
 public class UniAppApplication
 {
     public static void main(String[] args)

+ 15 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/config/DifyConfig.java

@@ -0,0 +1,15 @@
+package com.ruoyi.uniapp.config;
+
+import com.ruoyi.uniapp.utils.DifyChatClient;
+import com.ruoyi.uniapp.utils.DifyClientFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class DifyConfig {
+
+    @Bean
+    public DifyChatClient difyChatClient(DifyProperties config) {
+        return DifyClientFactory.createChatClient(config);
+    }
+}

+ 17 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/config/DifyProperties.java

@@ -0,0 +1,17 @@
+package com.ruoyi.uniapp.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "dify")
+public class DifyProperties {
+    private String baseUrl;
+    private String apiKey;
+
+    private int connectTimeout = 5000;
+    private int readTimeout = 60000;
+    private int writeTimeout = 30000;
+}

+ 196 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/controller/ChatController.java

@@ -0,0 +1,196 @@
+package com.ruoyi.uniapp.controller;
+
+import com.ruoyi.common.core.enums.dify.ResponseMode;
+import com.ruoyi.common.core.model.chat.ChatMessage;
+import com.ruoyi.common.core.callback.ChatStreamCallback;
+import com.ruoyi.common.core.event.*;
+import com.ruoyi.uniapp.utils.DifyChatClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RestController
+@RequestMapping("/dify/chat")
+public class ChatController {
+    @Autowired
+    DifyChatClient chatClient;
+
+    // 用于存储活跃的SSE连接
+    private final ConcurrentHashMap<String, SseEmitter> emitters = new ConcurrentHashMap<>();
+
+/*    @PostMapping("/send")
+    public ChatMessageResponse sendMessage(@RequestBody ChatMessage message) throws IOException {
+        return chatClient.sendChatMessage(message);
+    }*/
+
+    /**
+     * 发送流式聊天消息
+     * @param message 聊天消息
+     * @return SSE发射器
+     */
+    @PostMapping("/stream")
+    public SseEmitter sendMessageStream(@RequestBody ChatMessage message) {
+        // 设置响应模式为流式
+        message.setResponseMode(ResponseMode.STREAMING);
+
+        // 创建SSE发射器,设置超时时间为2分钟
+        SseEmitter emitter = new SseEmitter(120000L);
+        String emitterId = message.getUser() + "_" + System.currentTimeMillis();
+        message.setUser(emitterId);
+
+        try {
+            // 发送流式消息
+            chatClient.sendChatMessageStream(message, new ChatStreamCallback() {
+                @Override
+                public void onMessage(MessageEvent event) {
+                    try {
+                        // 发送消息片段
+                        emitter.send(SseEmitter.event()
+                                .name("message")
+                                .data(event.getAnswer()));
+
+                    } catch (IOException e) {
+                        onException(e);
+                    }
+                }
+
+                @Override
+                public void onMessageEnd(MessageEndEvent event) {
+                    try {
+                        // 发送消息结束事件
+                        emitter.send(SseEmitter.event()
+                                .name("message_end")
+                                .data(event));
+                        emitter.complete();
+
+                    } catch (IOException e) {
+                        onException(e);
+                    }
+                }
+
+                @Override
+                public void onMessageFile(MessageFileEvent event) {
+                    try {
+                        emitter.send(SseEmitter.event()
+                                .name("message_file")
+                                .data(event));
+                    } catch (IOException e) {
+                        onException(e);
+                    }
+                }
+
+                @Override
+                public void onTTSMessage(TtsMessageEvent event) {
+                    try {
+                        emitter.send(SseEmitter.event()
+                                .name("tts_message")
+                                .data(event));
+                    } catch (IOException e) {
+                        onException(e);
+                    }
+                }
+
+                @Override
+                public void onTTSMessageEnd(TtsMessageEndEvent event) {
+                    try {
+                        emitter.send(SseEmitter.event()
+                                .name("tts_message_end")
+                                .data(event));
+                    } catch (IOException e) {
+                        onException(e);
+                    }
+                }
+
+                @Override
+                public void onMessageReplace(MessageReplaceEvent event) {
+                    try {
+                        emitter.send(SseEmitter.event()
+                                .name("message_replace")
+                                .data(event));
+                    } catch (IOException e) {
+                        onException(e);
+                    }
+                }
+
+                @Override
+                public void onAgentMessage(AgentMessageEvent event) {
+                    try {
+                        emitter.send(SseEmitter.event()
+                                .name("agent_message")
+                                .data(event));
+                    } catch (IOException e) {
+                        onException(e);
+                    }
+                }
+
+                @Override
+                public void onAgentThought(AgentThoughtEvent event) {
+                    try {
+                        emitter.send(SseEmitter.event()
+                                .name("agent_thought")
+                                .data(event));
+                    } catch (IOException e) {
+                        onException(e);
+                    }
+                }
+
+                @Override
+                public void onError(ErrorEvent event) {
+                    try {
+                        emitter.send(SseEmitter.event()
+                                .name("error")
+                                .data(event));
+                        emitter.completeWithError(new RuntimeException(event.getMessage()));
+                    } catch (IOException e) {
+                        onException(e);
+                    }
+                }
+
+                @Override
+                public void onException(Throwable throwable) {
+                    emitter.completeWithError(throwable);
+                }
+
+                @Override
+                public void onPing(PingEvent event) {
+                    try {
+                        emitter.send(SseEmitter.event()
+                                .name("ping")
+                                .data("ping"));
+                    } catch (IOException e) {
+                        onException(e);
+                    }
+                }
+            });
+
+            // 设置超时回调
+            emitter.onTimeout(() -> {
+                emitter.complete();
+                System.out.println("SSE连接超时");
+            });
+
+            // 设置完成回调
+            emitter.onCompletion(() -> {
+                System.out.println("SSE连接完成");
+            });
+
+            // 设置错误回调
+            emitter.onError(throwable -> {
+                emitter.completeWithError(throwable);
+                System.err.println("SSE连接错误: " + throwable.getMessage());
+            });
+
+        } catch (Exception e) {
+            emitter.completeWithError(e);
+        }
+
+        return emitter;
+    }
+
+}

+ 235 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/controller/KnowledgeController.java

@@ -0,0 +1,235 @@
+package com.ruoyi.uniapp.controller;
+
+import java.util.List;
+import java.util.Date;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.http.MediaType;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.security.utils.SecurityUtils;
+import com.ruoyi.uniapp.domain.KnowledgeArticle;
+import com.ruoyi.uniapp.domain.KnowledgeImage;
+import com.ruoyi.uniapp.domain.vo.KnowledgeArticleVO;
+import com.ruoyi.uniapp.service.IKnowledgeService;
+
+/**
+ * 知识内容控制器
+ */
+@RestController
+@RequestMapping("/knowledge")
+public class KnowledgeController extends BaseController {
+    
+    @Autowired
+    private IKnowledgeService knowledgeService;
+    
+    /**
+     * 获取农技知识列表
+     */
+    @GetMapping("/tech")
+    public AjaxResult getTechList(@RequestParam(required = false, defaultValue = "1") Integer pageNum, 
+                                  @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        Integer userId = null;
+        try {
+            userId = SecurityUtils.getUserId().intValue();
+        } catch (Exception e) {
+            // 未登录或token无效
+        }
+        
+        List<KnowledgeArticleVO> list = knowledgeService.getTechList(pageNum, pageSize, userId);
+        return AjaxResult.success(list);
+    }
+    
+    /**
+     * 获取政策解读列表
+     */
+    @GetMapping("/policy")
+    public AjaxResult getPolicyList(@RequestParam(required = false, defaultValue = "1") Integer pageNum, 
+                                    @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        Integer userId = null;
+        try {
+            userId = SecurityUtils.getUserId().intValue();
+        } catch (Exception e) {
+            // 未登录或token无效
+        }
+        
+        List<KnowledgeArticleVO> list = knowledgeService.getPolicyList(pageNum, pageSize, userId);
+        return AjaxResult.success(list);
+    }
+    
+    /**
+     * 获取知识详情
+     */
+    @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public AjaxResult getArticleDetail(@PathVariable("id") Integer id) {
+        Integer userId = null;
+        try {
+            userId = SecurityUtils.getUserId().intValue();
+        } catch (Exception e) {
+            // 未登录或token无效
+        }
+        
+        KnowledgeArticleVO article = knowledgeService.getArticleDetail(id, userId);
+        if (article == null) {
+            return AjaxResult.error("文章不存在");
+        }
+        return AjaxResult.success(article);
+    }
+    
+    /**
+     * 获取文章相关图片
+     */
+    @GetMapping("/images/{articleId}")
+    public AjaxResult getArticleImages(@PathVariable("articleId") Integer articleId) {
+        List<KnowledgeImage> images = knowledgeService.getArticleImages(articleId);
+        return AjaxResult.success(images);
+    }
+    
+    /**
+     * 获取轮播图
+     */
+    @GetMapping("/carousel")
+    public AjaxResult getCarouselImages() {
+        List<KnowledgeImage> carouselImages = knowledgeService.getCarouselImages();
+        return AjaxResult.success(carouselImages);
+    }
+    
+    /**
+     * 创建文章
+     */
+    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
+    public AjaxResult createArticle(@RequestBody KnowledgeArticle article) {
+        try {
+            // 设置文章的创建者
+            Long userId = SecurityUtils.getUserId();
+            
+            // 设置发布日期和状态
+            article.setPublishDate(new Date());
+            if (article.getStatus() == null) {
+                article.setStatus(1); // 默认已发布状态
+            }
+            if (article.getViewCount() == null) {
+                article.setViewCount(0);
+            }
+            if (article.getLikeCount() == null) {
+                article.setLikeCount(0);
+            }
+            if (article.getIsRecommend() == null) {
+                article.setIsRecommend(0); // 默认不推荐
+            }
+            
+            // 确保HTML内容不被转义
+            // 使用原始内容,不做任何处理
+            
+            // 保存文章
+            int result = knowledgeService.insertKnowledgeArticle(article);
+            
+            if (result > 0) {
+                return AjaxResult.success("创建成功", article.getId());
+            } else {
+                return AjaxResult.error("创建失败");
+            }
+        } catch (Exception e) {
+            logger.error("创建文章失败: ", e);
+            return AjaxResult.error("创建文章失败: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 更新文章
+     */
+    @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
+    public AjaxResult updateArticle(@RequestBody KnowledgeArticle article) {
+        try {
+            if (article.getId() == null) {
+                return AjaxResult.error("文章ID不能为空");
+            }
+            
+            // 验证文章是否存在
+            KnowledgeArticle existingArticle = knowledgeService.selectKnowledgeArticleById(article.getId());
+            if (existingArticle == null) {
+                return AjaxResult.error("文章不存在");
+            }
+            
+            // 确保HTML内容不被转义
+            // 使用原始内容,不做任何处理
+            
+            // 更新文章
+            int result = knowledgeService.updateKnowledgeArticle(article);
+            
+            if (result > 0) {
+                return AjaxResult.success("更新成功");
+            } else {
+                return AjaxResult.error("更新失败");
+            }
+        } catch (Exception e) {
+            logger.error("更新文章失败: ", e);
+            return AjaxResult.error("更新文章失败: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 点赞文章
+     */
+    @PostMapping("/like/{id}")
+    public AjaxResult likeArticle(@PathVariable("id") Integer id) {
+        Integer userId;
+        try {
+            userId = SecurityUtils.getUserId().intValue();
+        } catch (Exception e) {
+            return AjaxResult.error("请先登录");
+        }
+        
+        int result = knowledgeService.likeArticle(id, userId, true);
+        return AjaxResult.success("点赞成功", result);
+    }
+    
+    /**
+     * 取消点赞
+     */
+    @DeleteMapping("/like/{id}")
+    public AjaxResult cancelLikeArticle(@PathVariable("id") Integer id) {
+        Integer userId;
+        try {
+            userId = SecurityUtils.getUserId().intValue();
+        } catch (Exception e) {
+            return AjaxResult.error("请先登录");
+        }
+        
+        int result = knowledgeService.likeArticle(id, userId, false);
+        return AjaxResult.success("取消点赞成功", result);
+    }
+    
+    /**
+     * 收藏文章
+     */
+    @PostMapping("/favorite/{id}")
+    public AjaxResult favoriteArticle(@PathVariable("id") Integer id) {
+        Integer userId;
+        try {
+            userId = SecurityUtils.getUserId().intValue();
+        } catch (Exception e) {
+            return AjaxResult.error("请先登录");
+        }
+        
+        int result = knowledgeService.favoriteArticle(id, userId, true);
+        return AjaxResult.success("收藏成功", result);
+    }
+    
+    /**
+     * 取消收藏
+     */
+    @DeleteMapping("/favorite/{id}")
+    public AjaxResult cancelFavoriteArticle(@PathVariable("id") Integer id) {
+        Integer userId;
+        try {
+            userId = SecurityUtils.getUserId().intValue();
+        } catch (Exception e) {
+            return AjaxResult.error("请先登录");
+        }
+        
+        int result = knowledgeService.favoriteArticle(id, userId, false);
+        return AjaxResult.success("取消收藏成功", result);
+    }
+} 

+ 0 - 1
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/controller/UniAppLoginController.java

@@ -8,7 +8,6 @@ import com.ruoyi.common.security.utils.SecurityUtils;
 import com.ruoyi.system.api.domain.SysUser;
 import com.ruoyi.system.api.model.LoginUser;
 import com.ruoyi.system.domain.UniAppLoginBody;
-import com.ruoyi.system.mapper.SysUserMapper;
 import com.ruoyi.system.service.ISysUserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;

+ 251 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/domain/KnowledgeArticle.java

@@ -0,0 +1,251 @@
+package com.ruoyi.uniapp.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.ruoyi.common.core.annotation.Excel;
+
+/**
+ * 知识文章对象 knowledge_article
+ */
+public class KnowledgeArticle implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** ID */
+    private Integer id;
+
+    /** 标题 */
+    @Excel(name = "标题")
+    private String title;
+
+    /** 简短描述 */
+    @Excel(name = "简短描述")
+    private String description;
+
+    /** 内容(HTML格式) */
+    private String content;
+
+    /** 分类: tech-农技知识, policy-政策解读 */
+    @Excel(name = "分类", readConverterExp = "tech=农技知识,policy=政策解读")
+    private String category;
+
+    /** 缩略图 */
+    private String thumbnail;
+
+    /** 内容类型: article-文章, video-视频 */
+    @Excel(name = "内容类型", readConverterExp = "article=文章,video=视频")
+    @JsonProperty("contentType")
+    private String contentType;
+
+    /** 视频URL,当content_type为video时使用 */
+    @JsonProperty("videoUrl")
+    private String videoUrl;
+
+    /** 视频时长,如"15:32" */
+    @JsonProperty("videoDuration")
+    private String videoDuration;
+
+    /** 来源,如"农业技术研究院" */
+    private String source;
+
+    /** 标签,多个标签用逗号分隔 */
+    private String tags;
+
+    /** 发布日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "发布日期", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    @JsonProperty("publishDate")
+    private Date publishDate;
+
+    /** 浏览次数 */
+    @Excel(name = "浏览次数")
+    @JsonProperty("viewCount")
+    private Integer viewCount;
+
+    /** 点赞次数 */
+    @Excel(name = "点赞次数")
+    @JsonProperty("likeCount")
+    private Integer likeCount;
+
+    /** 是否推荐: 0-否,1-是 */
+    @Excel(name = "是否推荐", readConverterExp = "0=否,1=是")
+    @JsonProperty("isRecommend")
+    private Integer isRecommend;
+
+    /** 状态: 0-草稿,1-已发布 */
+    @Excel(name = "状态", readConverterExp = "0=草稿,1=已发布")
+    private Integer status;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonProperty("createTime")
+    private Date createTime;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonProperty("updateTime")
+    private Date updateTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getCategory() {
+        return category;
+    }
+
+    public void setCategory(String category) {
+        this.category = category;
+    }
+
+    public String getThumbnail() {
+        return thumbnail;
+    }
+
+    public void setThumbnail(String thumbnail) {
+        this.thumbnail = thumbnail;
+    }
+
+    public String getContentType() {
+        return contentType;
+    }
+
+    public void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
+
+    public String getVideoUrl() {
+        return videoUrl;
+    }
+
+    public void setVideoUrl(String videoUrl) {
+        this.videoUrl = videoUrl;
+    }
+
+    public String getVideoDuration() {
+        return videoDuration;
+    }
+
+    public void setVideoDuration(String videoDuration) {
+        this.videoDuration = videoDuration;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    public String getTags() {
+        return tags;
+    }
+
+    public void setTags(String tags) {
+        this.tags = tags;
+    }
+
+    public Date getPublishDate() {
+        return publishDate;
+    }
+
+    public void setPublishDate(Date publishDate) {
+        this.publishDate = publishDate;
+    }
+
+    public Integer getViewCount() {
+        return viewCount;
+    }
+
+    public void setViewCount(Integer viewCount) {
+        this.viewCount = viewCount;
+    }
+
+    public Integer getLikeCount() {
+        return likeCount;
+    }
+
+    public void setLikeCount(Integer likeCount) {
+        this.likeCount = likeCount;
+    }
+
+    public Integer getIsRecommend() {
+        return isRecommend;
+    }
+
+    public void setIsRecommend(Integer isRecommend) {
+        this.isRecommend = isRecommend;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    @Override
+    public String toString() {
+        return "KnowledgeArticle{" +
+                "id=" + id +
+                ", title='" + title + '\'' +
+                ", description='" + description + '\'' +
+                ", category='" + category + '\'' +
+                ", thumbnail='" + thumbnail + '\'' +
+                ", contentType='" + contentType + '\'' +
+                ", source='" + source + '\'' +
+                ", publishDate=" + publishDate +
+                ", viewCount=" + viewCount +
+                ", likeCount=" + likeCount +
+                ", status=" + status +
+                '}';
+    }
+} 

+ 132 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/domain/KnowledgeImage.java

@@ -0,0 +1,132 @@
+package com.ruoyi.uniapp.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.ruoyi.common.core.annotation.Excel;
+
+/**
+ * 知识图片对象 knowledge_image
+ */
+public class KnowledgeImage implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** ID */
+    private Integer id;
+
+    /** 文章ID */
+    @Excel(name = "文章ID")
+    @JsonProperty("articleId")
+    private Integer articleId;
+
+    /** 图片URL */
+    private String url;
+
+    /** 图片标题 */
+    private String title;
+
+    /** 图片副标题 */
+    private String subtitle;
+
+    /** 背景颜色代码,如"#4CAF50" */
+    private String color;
+
+    /** 排序 */
+    @JsonProperty("sortOrder")
+    private Integer sortOrder;
+
+    /** 图片类型: carousel-轮播图, content-内容图片 */
+    @Excel(name = "图片类型", readConverterExp = "carousel=轮播图,content=内容图片")
+    @JsonProperty("imageType")
+    private String imageType;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonProperty("createTime")
+    private Date createTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getArticleId() {
+        return articleId;
+    }
+
+    public void setArticleId(Integer articleId) {
+        this.articleId = articleId;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getSubtitle() {
+        return subtitle;
+    }
+
+    public void setSubtitle(String subtitle) {
+        this.subtitle = subtitle;
+    }
+
+    public String getColor() {
+        return color;
+    }
+
+    public void setColor(String color) {
+        this.color = color;
+    }
+
+    public Integer getSortOrder() {
+        return sortOrder;
+    }
+
+    public void setSortOrder(Integer sortOrder) {
+        this.sortOrder = sortOrder;
+    }
+
+    public String getImageType() {
+        return imageType;
+    }
+
+    public void setImageType(String imageType) {
+        this.imageType = imageType;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    @Override
+    public String toString() {
+        return "KnowledgeImage{" +
+                "id=" + id +
+                ", articleId=" + articleId +
+                ", url='" + url + '\'' +
+                ", title='" + title + '\'' +
+                ", imageType='" + imageType + '\'' +
+                '}';
+    }
+} 

+ 87 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/domain/UserInteraction.java

@@ -0,0 +1,87 @@
+package com.ruoyi.uniapp.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.ruoyi.common.core.annotation.Excel;
+
+/**
+ * 用户交互对象 user_interaction
+ */
+public class UserInteraction implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** ID */
+    private Integer id;
+
+    /** 用户ID */
+    @Excel(name = "用户ID")
+    @JsonProperty("userId")
+    private Integer userId;
+
+    /** 文章ID */
+    @Excel(name = "文章ID")
+    @JsonProperty("articleId")
+    private Integer articleId;
+
+    /** 交互类型: favorite-收藏, like-点赞, view-浏览 */
+    @Excel(name = "交互类型", readConverterExp = "favorite=收藏,like=点赞,view=浏览")
+    private String type;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonProperty("createTime")
+    private Date createTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public Integer getArticleId() {
+        return articleId;
+    }
+
+    public void setArticleId(Integer articleId) {
+        this.articleId = articleId;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    @Override
+    public String toString() {
+        return "UserInteraction{" +
+                "id=" + id +
+                ", userId=" + userId +
+                ", articleId=" + articleId +
+                ", type='" + type + '\'' +
+                '}';
+    }
+} 

+ 200 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/domain/vo/KnowledgeArticleVO.java

@@ -0,0 +1,200 @@
+package com.ruoyi.uniapp.domain.vo;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.ruoyi.uniapp.domain.KnowledgeImage;
+
+/**
+ * 知识文章VO对象,用于前端展示
+ */
+public class KnowledgeArticleVO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** ID */
+    private Integer id;
+
+    /** 标题 */
+    private String title;
+
+    /** 简短描述 */
+    private String description;
+
+    /** 内容(HTML格式) */
+    private String content;
+
+    /** 分类: tech-农技知识, policy-政策解读 */
+    private String type;
+
+    /** 缩略图 */
+    private String image;
+
+    /** 内容类型: article-文章, video-视频 */
+    @JsonProperty("contentType")
+    private String contentType;
+
+    /** 视频URL,当content_type为video时使用 */
+    @JsonProperty("videoUrl")
+    private String videoUrl;
+
+    /** 视频时长,如"15:32" */
+    private String duration;
+
+    /** 来源,如"农业技术研究院" */
+    private String source;
+
+    /** 发布时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonProperty("publishTime")
+    private Date publishTime;
+
+    /** 浏览次数 */
+    @JsonProperty("viewCount")
+    private Integer viewCount;
+
+    /** 点赞次数 */
+    @JsonProperty("likeCount")
+    private Integer likeCount;
+
+    /** 是否已点赞: 0-否,1-是 */
+    @JsonProperty("isLiked")
+    private Integer isLiked;
+
+    /** 是否已收藏: 0-否,1-是 */
+    @JsonProperty("isFavorite")
+    private Integer isFavorite;
+    
+    /** 相关图片列表 */
+    private List<KnowledgeImage> images;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getImage() {
+        return image;
+    }
+
+    public void setImage(String image) {
+        this.image = image;
+    }
+
+    public String getContentType() {
+        return contentType;
+    }
+
+    public void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
+
+    public String getVideoUrl() {
+        return videoUrl;
+    }
+
+    public void setVideoUrl(String videoUrl) {
+        this.videoUrl = videoUrl;
+    }
+
+    public String getDuration() {
+        return duration;
+    }
+
+    public void setDuration(String duration) {
+        this.duration = duration;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    public Date getPublishTime() {
+        return publishTime;
+    }
+
+    public void setPublishTime(Date publishTime) {
+        this.publishTime = publishTime;
+    }
+
+    public Integer getViewCount() {
+        return viewCount;
+    }
+
+    public void setViewCount(Integer viewCount) {
+        this.viewCount = viewCount;
+    }
+
+    public Integer getLikeCount() {
+        return likeCount;
+    }
+
+    public void setLikeCount(Integer likeCount) {
+        this.likeCount = likeCount;
+    }
+
+    public Integer getIsLiked() {
+        return isLiked;
+    }
+
+    public void setIsLiked(Integer isLiked) {
+        this.isLiked = isLiked;
+    }
+
+    public Integer getIsFavorite() {
+        return isFavorite;
+    }
+
+    public void setIsFavorite(Integer isFavorite) {
+        this.isFavorite = isFavorite;
+    }
+
+    public List<KnowledgeImage> getImages() {
+        return images;
+    }
+
+    public void setImages(List<KnowledgeImage> images) {
+        this.images = images;
+    }
+} 

+ 79 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/mapper/KnowledgeArticleMapper.java

@@ -0,0 +1,79 @@
+package com.ruoyi.uniapp.mapper;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import com.ruoyi.uniapp.domain.KnowledgeArticle;
+
+/**
+ * 知识文章Mapper接口
+ */
+public interface KnowledgeArticleMapper {
+    /**
+     * 查询知识文章
+     * 
+     * @param id 知识文章主键
+     * @return 知识文章
+     */
+    public KnowledgeArticle selectKnowledgeArticleById(Integer id);
+
+    /**
+     * 查询知识文章列表
+     * 
+     * @param knowledgeArticle 知识文章
+     * @return 知识文章集合
+     */
+    public List<KnowledgeArticle> selectKnowledgeArticleList(KnowledgeArticle knowledgeArticle);
+    
+    /**
+     * 根据分类查询文章列表
+     * 
+     * @param category 分类
+     * @param pageNum 页码
+     * @param pageSize 每页条数
+     * @return 知识文章集合
+     */
+    public List<KnowledgeArticle> selectKnowledgeArticlesByCategory(@Param("category") String category, 
+                                                                  @Param("pageNum") Integer pageNum, 
+                                                                  @Param("pageSize") Integer pageSize);
+    
+    /**
+     * 新增知识文章
+     * 
+     * @param knowledgeArticle 知识文章
+     * @return 结果
+     */
+    public int insertKnowledgeArticle(KnowledgeArticle knowledgeArticle);
+    
+    /**
+     * 修改知识文章
+     * 
+     * @param knowledgeArticle 知识文章
+     * @return 结果
+     */
+    public int updateKnowledgeArticle(KnowledgeArticle knowledgeArticle);
+    
+    /**
+     * 删除知识文章
+     * 
+     * @param id 知识文章主键
+     * @return 结果
+     */
+    public int deleteKnowledgeArticleById(Integer id);
+    
+    /**
+     * 更新阅读数
+     * 
+     * @param id 文章ID
+     * @return 结果
+     */
+    public int updateViewCount(Integer id);
+    
+    /**
+     * 更新点赞数
+     * 
+     * @param id 文章ID
+     * @param count 增加的数量,可为负数
+     * @return 结果
+     */
+    public int updateLikeCount(@Param("id") Integer id, @Param("count") Integer count);
+} 

+ 78 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/mapper/KnowledgeImageMapper.java

@@ -0,0 +1,78 @@
+package com.ruoyi.uniapp.mapper;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import com.ruoyi.uniapp.domain.KnowledgeImage;
+
+/**
+ * 知识图片Mapper接口
+ */
+public interface KnowledgeImageMapper {
+    /**
+     * 查询知识图片
+     * 
+     * @param id 知识图片主键
+     * @return 知识图片
+     */
+    public KnowledgeImage selectKnowledgeImageById(Integer id);
+
+    /**
+     * 查询知识图片列表
+     * 
+     * @param knowledgeImage 知识图片
+     * @return 知识图片集合
+     */
+    public List<KnowledgeImage> selectKnowledgeImageList(KnowledgeImage knowledgeImage);
+    
+    /**
+     * 根据文章ID查询图片
+     * 
+     * @param articleId 文章ID
+     * @param imageType 图片类型
+     * @return 图片列表
+     */
+    public List<KnowledgeImage> selectKnowledgeImagesByArticleId(@Param("articleId") Integer articleId, 
+                                                               @Param("imageType") String imageType);
+    
+    /**
+     * 根据类型查询图片
+     * 
+     * @param imageType 图片类型
+     * @param limit 限制数量
+     * @return 图片列表
+     */
+    public List<KnowledgeImage> selectKnowledgeImagesByType(@Param("imageType") String imageType, 
+                                                          @Param("limit") Integer limit);
+    
+    /**
+     * 新增知识图片
+     * 
+     * @param knowledgeImage 知识图片
+     * @return 结果
+     */
+    public int insertKnowledgeImage(KnowledgeImage knowledgeImage);
+    
+    /**
+     * 修改知识图片
+     * 
+     * @param knowledgeImage 知识图片
+     * @return 结果
+     */
+    public int updateKnowledgeImage(KnowledgeImage knowledgeImage);
+    
+    /**
+     * 删除知识图片
+     * 
+     * @param id 知识图片主键
+     * @return 结果
+     */
+    public int deleteKnowledgeImageById(Integer id);
+    
+    /**
+     * 批量删除知识图片
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteKnowledgeImageByIds(Integer[] ids);
+} 

+ 86 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/mapper/UserInteractionMapper.java

@@ -0,0 +1,86 @@
+package com.ruoyi.uniapp.mapper;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import com.ruoyi.uniapp.domain.UserInteraction;
+
+/**
+ * 用户交互Mapper接口
+ */
+public interface UserInteractionMapper {
+    /**
+     * 查询用户交互
+     * 
+     * @param id 用户交互主键
+     * @return 用户交互
+     */
+    public UserInteraction selectUserInteractionById(Integer id);
+
+    /**
+     * 查询用户交互列表
+     * 
+     * @param userInteraction 用户交互
+     * @return 用户交互集合
+     */
+    public List<UserInteraction> selectUserInteractionList(UserInteraction userInteraction);
+    
+    /**
+     * 查询用户交互记录
+     * 
+     * @param userId 用户ID
+     * @param articleId 文章ID
+     * @param type 交互类型
+     * @return 用户交互
+     */
+    public UserInteraction selectUserInteraction(@Param("userId") Integer userId, 
+                                               @Param("articleId") Integer articleId, 
+                                               @Param("type") String type);
+    
+    /**
+     * 检查用户交互是否存在
+     * 
+     * @param userId 用户ID
+     * @param articleId 文章ID
+     * @param type 交互类型
+     * @return 是否存在
+     */
+    public boolean checkUserInteraction(@Param("userId") Integer userId, 
+                                      @Param("articleId") Integer articleId, 
+                                      @Param("type") String type);
+    
+    /**
+     * 新增用户交互
+     * 
+     * @param userInteraction 用户交互
+     * @return 结果
+     */
+    public int insertUserInteraction(UserInteraction userInteraction);
+    
+    /**
+     * 修改用户交互
+     * 
+     * @param userInteraction 用户交互
+     * @return 结果
+     */
+    public int updateUserInteraction(UserInteraction userInteraction);
+    
+    /**
+     * 删除用户交互
+     * 
+     * @param id 用户交互主键
+     * @return 结果
+     */
+    public int deleteUserInteractionById(Integer id);
+    
+    /**
+     * 删除用户交互记录
+     * 
+     * @param userId 用户ID
+     * @param articleId 文章ID
+     * @param type 交互类型
+     * @return 结果
+     */
+    public int deleteUserInteraction(@Param("userId") Integer userId, 
+                                   @Param("articleId") Integer articleId, 
+                                   @Param("type") String type);
+} 

+ 116 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/service/IKnowledgeService.java

@@ -0,0 +1,116 @@
+package com.ruoyi.uniapp.service;
+
+import java.util.List;
+import com.ruoyi.uniapp.domain.vo.KnowledgeArticleVO;
+import com.ruoyi.uniapp.domain.KnowledgeArticle;
+import com.ruoyi.uniapp.domain.KnowledgeImage;
+
+/**
+ * 知识服务接口
+ */
+public interface IKnowledgeService {
+    /**
+     * 获取农技知识列表
+     * 
+     * @param pageNum 页码
+     * @param pageSize 每页条数
+     * @param userId 用户ID
+     * @return 农技知识列表
+     */
+    public List<KnowledgeArticleVO> getTechList(Integer pageNum, Integer pageSize, Integer userId);
+    
+    /**
+     * 获取政策解读列表
+     * 
+     * @param pageNum 页码
+     * @param pageSize 每页条数
+     * @param userId 用户ID
+     * @return 政策解读列表
+     */
+    public List<KnowledgeArticleVO> getPolicyList(Integer pageNum, Integer pageSize, Integer userId);
+    
+    /**
+     * 获取知识文章详情
+     * 
+     * @param id 文章ID
+     * @param userId 用户ID
+     * @return 文章详情
+     */
+    public KnowledgeArticleVO getArticleDetail(Integer id, Integer userId);
+    
+    /**
+     * 获取文章相关图片
+     * 
+     * @param articleId 文章ID
+     * @return 图片列表
+     */
+    public List<KnowledgeImage> getArticleImages(Integer articleId);
+    
+    /**
+     * 获取轮播图
+     * 
+     * @return 轮播图列表
+     */
+    public List<KnowledgeImage> getCarouselImages();
+    
+    /**
+     * 新增知识文章
+     * 
+     * @param knowledgeArticle 知识文章
+     * @return 结果
+     */
+    public int insertKnowledgeArticle(KnowledgeArticle knowledgeArticle);
+    
+    /**
+     * 修改知识文章
+     * 
+     * @param knowledgeArticle 知识文章
+     * @return 结果
+     */
+    public int updateKnowledgeArticle(KnowledgeArticle knowledgeArticle);
+    
+    /**
+     * 删除知识文章
+     * 
+     * @param id 知识文章主键
+     * @return 结果
+     */
+    public int deleteKnowledgeArticleById(Integer id);
+    
+    /**
+     * 记录用户浏览文章
+     * 
+     * @param articleId 文章ID
+     * @param userId 用户ID
+     * @return 结果
+     */
+    public int recordUserView(Integer articleId, Integer userId);
+    
+    /**
+     * 用户点赞/取消点赞文章
+     * 
+     * @param articleId 文章ID
+     * @param userId 用户ID
+     * @param isLike true-点赞,false-取消点赞
+     * @return 结果
+     */
+    public int likeArticle(Integer articleId, Integer userId, boolean isLike);
+    
+    /**
+     * 用户收藏/取消收藏文章
+     * 
+     * @param articleId 文章ID
+     * @param userId 用户ID
+     * @param isFavorite true-收藏,false-取消收藏
+     * @return 结果
+     */
+    public int favoriteArticle(Integer articleId, Integer userId, boolean isFavorite);
+    
+    /**
+     * 根据ID查询知识文章
+     * 
+     * @param id 文章ID
+     * @return 知识文章
+     */
+    public KnowledgeArticle selectKnowledgeArticleById(Integer id);
+} 

+ 354 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/service/impl/AbstractDifyClient.java

@@ -0,0 +1,354 @@
+package com.ruoyi.uniapp.service.impl;
+
+import com.ruoyi.common.core.exception.DifyApiException;
+import com.ruoyi.common.core.utils.HttpClientUtils;
+import com.ruoyi.common.core.utils.JsonUtils;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Dify API 客户端抽象基类
+ * 提供通用的 HTTP 请求处理方法
+ */
+@Slf4j
+public abstract class AbstractDifyClient {
+    protected static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+    protected static final MediaType OCTET_STREAM = MediaType.parse("application/octet-stream");
+    protected static final MediaType AUDIO = MediaType.parse("audio/*");
+
+    protected final OkHttpClient httpClient;
+    protected final String baseUrl;
+    protected final String apiKey;
+
+    /**
+     * 构造函数
+     *
+     * @param baseUrl API基础URL
+     * @param apiKey  API密钥
+     */
+    public AbstractDifyClient(String baseUrl, String apiKey) {
+        this(baseUrl, apiKey, HttpClientUtils.createDefaultClient());
+    }
+
+    /**
+     * 构造函数
+     *
+     * @param baseUrl    API基础URL
+     * @param apiKey     API密钥
+     * @param httpClient HTTP客户端
+     */
+    public AbstractDifyClient(String baseUrl, String apiKey, OkHttpClient httpClient) {
+        this.baseUrl = baseUrl;
+        this.apiKey = apiKey;
+        this.httpClient = httpClient;
+    }
+
+    /**
+     * 执行GET请求
+     *
+     * @param path 请求路径
+     * @param responseClass 响应类型
+     * @param <T> 响应类型
+     * @return 响应对象
+     * @throws IOException IO异常
+     * @throws DifyApiException API异常
+     */
+    protected <T> T executeGet(String path, Class<T> responseClass) throws IOException, DifyApiException {
+        Request request = createGetRequest(path);
+        return executeRequest(request, responseClass);
+    }
+
+    /**
+     * 执行POST请求
+     *
+     * @param path 请求路径
+     * @param body 请求体
+     * @param responseClass 响应类型
+     * @param <T> 响应类型
+     * @return 响应对象
+     * @throws IOException IO异常
+     * @throws DifyApiException API异常
+     */
+    protected <T> T executePost(String path, Object body, Class<T> responseClass) throws IOException, DifyApiException {
+        RequestBody requestBody = createJsonRequestBody(body);
+        Request request = createPostRequest(path, requestBody);
+        return executeRequest(request, responseClass);
+    }
+
+    /**
+     * 执行Patch请求
+     *
+     * @param path 请求路径
+     * @param body 请求体
+     * @param responseClass 响应类型
+     * @param <T> 响应类型
+     * @return 响应对象
+     * @throws IOException IO异常
+     * @throws DifyApiException API异常
+     */
+    protected <T> T executePatch(String path, Object body, Class<T> responseClass) throws IOException, DifyApiException {
+        RequestBody requestBody = createJsonRequestBody(body);
+        Request request = createPatchRequest(path, requestBody);
+        return executeRequest(request, responseClass);
+    }
+
+    /**
+     * 执行DELETE请求
+     *
+     * @param path 请求路径
+     * @param body 请求体
+     * @param responseClass 响应类型
+     * @param <T> 响应类型
+     * @return 响应对象
+     * @throws IOException IO异常
+     * @throws DifyApiException API异常
+     */
+    protected <T> T executeDelete(String path, Object body, Class<T> responseClass) throws IOException, DifyApiException {
+        RequestBody requestBody = createJsonRequestBody(body);
+        Request request = createDeleteRequest(path, requestBody);
+        return executeRequest(request, responseClass);
+    }
+
+    /**
+     * 执行请求并处理响应
+     *
+     * @param request 请求对象
+     * @param responseClass 响应类型
+     * @param <T> 响应类型
+     * @return 响应对象
+     * @throws IOException IO异常
+     * @throws DifyApiException API异常
+     */
+    protected <T> T executeRequest(Request request, Class<T> responseClass) throws IOException, DifyApiException {
+        try (Response response = httpClient.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                String errorBody = response.body() != null ? response.body().string() : "";
+                throw createApiException(response.code(), errorBody);
+            }
+
+            String responseBody = Objects.requireNonNull(response.body()).string();
+            return JsonUtils.fromJson(responseBody, responseClass);
+        }
+    }
+
+    /**
+     * 处理HTTP响应
+     *
+     * @param response HTTP响应
+     * @param clazz    目标类型
+     * @param <T>      目标类型
+     * @return 响应对象
+     * @throws IOException      IO异常
+     * @throws DifyApiException API异常
+     */
+    protected <T> T handleResponse(Response response, Class<T> clazz) throws IOException, DifyApiException {
+        if (!response.isSuccessful()) {
+            String errorBody = response.body() != null ? response.body().string() : "";
+            log.error("API请求失败: {}, 状态码: {}, 错误信息: {}", response.request().url(), response.code(), errorBody);
+            throw new DifyApiException(response.code(), "api_error", errorBody);
+        }
+
+        if (response.body() == null) {
+            return null;
+        }
+
+        String responseBody = response.body().string();
+        return JsonUtils.fromJson(responseBody, clazz);
+    }
+
+    /**
+     * 执行请求并返回字节数组
+     *
+     * @param request 请求对象
+     * @return 字节数组
+     * @throws IOException IO异常
+     * @throws DifyApiException API异常
+     */
+    protected byte[] executeRequestForBytes(Request request) throws IOException, DifyApiException {
+        try (Response response = httpClient.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                String errorBody = response.body() != null ? response.body().string() : "";
+                throw createApiException(response.code(), errorBody);
+            }
+
+            return Objects.requireNonNull(response.body()).bytes();
+        }
+    }
+
+    /**
+     * 创建GET请求
+     *
+     * @param path 请求路径
+     * @return 请求对象
+     */
+    protected Request createGetRequest(String path) {
+        return new Request.Builder()
+                .url(baseUrl + path)
+                .get()
+                .header("Authorization", "Bearer " + apiKey)
+                .build();
+    }
+
+    /**
+     * 创建POST请求
+     *
+     * @param path 请求路径
+     * @param body 请求体
+     * @return 请求对象
+     */
+    protected Request createPostRequest(String path, RequestBody body) {
+        return new Request.Builder()
+                .url(baseUrl + path)
+                .post(body)
+                .header("Authorization", "Bearer " + apiKey)
+                .header("Content-Type", "application/json")
+                .build();
+    }
+
+    /**
+     * 创建PATCH请求
+     *
+     * @param path 请求路径
+     * @param body 请求体
+     * @return 请求对象
+     */
+    protected Request createPatchRequest(String path, RequestBody body) {
+        return new Request.Builder()
+                .url(baseUrl + path)
+                .patch(body)
+                .header("Authorization", "Bearer " + apiKey)
+                .header("Content-Type", "application/json")
+                .build();
+    }
+
+    /**
+     * 创建DELETE请求
+     *
+     * @param path 请求路径
+     * @param body 请求体
+     * @return 请求对象
+     */
+    protected Request createDeleteRequest(String path, RequestBody body) {
+        Request.Builder builder = new Request.Builder()
+                .url(baseUrl + path)
+                .delete()
+                .header("Authorization", "Bearer " + apiKey);
+
+        if (body != null) {
+            builder.delete(body).header("Content-Type", "application/json");
+        }
+
+        return builder.build();
+    }
+
+    /**
+     * 创建JSON请求体
+     *
+     * @param body 请求体对象
+     * @return 请求体
+     */
+    protected RequestBody createJsonRequestBody(Object body) {
+        if (body == null) {
+            return null;
+        }
+        return RequestBody.create(JSON, JsonUtils.toJson(body));
+    }
+
+    /**
+     * 创建API异常
+     *
+     * @param code HTTP状态码
+     * @param message 错误消息
+     * @return API异常
+     */
+    protected DifyApiException createApiException(int code, String message) {
+        String errorCode = "unknown_error";
+        String errorMessage = message;
+
+        try {
+            // 尝试解析错误响应体为JSON
+            if (message != null && !message.isEmpty() && JsonUtils.isValidJson(message)) {
+                Map<String, Object> errorJson = JsonUtils.fromJson(message, Map.class);
+                if (errorJson != null) {
+                    if (errorJson.containsKey("error_code")) {
+                        errorCode = (String) errorJson.get("error_code");
+                    } else if (errorJson.containsKey("code")) {
+                        errorCode = String.valueOf(errorJson.get("code"));
+                    }
+
+                    if (errorJson.containsKey("error_message")) {
+                        errorMessage = (String) errorJson.get("error_message");
+                    } else if (errorJson.containsKey("message")) {
+                        errorMessage = (String) errorJson.get("message");
+                    }
+
+                    if (errorJson.containsKey("params")) {
+                        errorMessage += " 【" + errorJson.get("params") + "】";
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.warn("解析错误响应体失败: {}", message, e);
+        }
+
+        return new DifyApiException(code, errorCode, errorMessage);
+    }
+
+    /**
+     * 构建URL查询参数
+     *
+     * @param path 请求路径
+     * @param params 参数映射
+     * @return 完整URL
+     */
+    protected String buildUrlWithParams(String path, Map<String, Object> params) {
+        if (params == null || params.isEmpty()) {
+            return path;
+        }
+
+        StringBuilder urlBuilder = new StringBuilder(path);
+        boolean isFirstParam = true;
+
+        for (Map.Entry<String, Object> entry : params.entrySet()) {
+            if (entry.getValue() != null) {
+                urlBuilder.append(isFirstParam ? "?" : "&")
+                        .append(entry.getKey())
+                        .append("=")
+                        .append(entry.getValue());
+                isFirstParam = false;
+            }
+        }
+
+        return urlBuilder.toString();
+    }
+
+    /**
+     * 添加非空字符串参数
+     *
+     * @param params 参数映射
+     * @param key 键
+     * @param value 值
+     */
+    protected void addIfNotEmpty(Map<String, Object> params, String key, String value) {
+        if (value != null && !value.isEmpty()) {
+            params.put(key, value);
+        }
+    }
+
+    /**
+     * 添加非空参数
+     *
+     * @param params 参数映射
+     * @param key 键
+     * @param value 值
+     */
+    protected void addIfNotNull(Map<String, Object> params, String key, Object value) {
+        if (value != null) {
+            params.put(key, value);
+        }
+    }
+}

+ 447 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/service/impl/DefaultDifyClient.java

@@ -0,0 +1,447 @@
+package com.ruoyi.uniapp.service.impl;
+
+import com.ruoyi.common.core.callback.*;
+import com.ruoyi.common.core.enums.dify.EventType;
+import com.ruoyi.common.core.enums.dify.ResponseMode;
+import com.ruoyi.common.core.event.BaseEvent;
+import com.ruoyi.common.core.event.PingEvent;
+import com.ruoyi.common.core.exception.DifyApiException;
+import com.ruoyi.common.core.model.chat.*;
+import com.ruoyi.common.core.model.workflow.*;
+import com.ruoyi.common.core.model.common.SimpleResponse;
+import com.ruoyi.common.core.model.completion.CompletionRequest;
+import com.ruoyi.common.core.model.completion.CompletionResponse;
+import com.ruoyi.uniapp.utils.DifyClient;
+import com.ruoyi.common.core.utils.JsonUtils;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Dify API 客户端默认实现
+ * 提供对话型应用、文本生成型应用和工作流应用的完整功能
+ */
+@Slf4j
+public class DefaultDifyClient extends DifyBaseClientImpl implements DifyClient {
+
+    // 流式响应相关常量
+    private static final String DATA_PREFIX = "data:";
+    private static final String PING_EVENT = "event: ping";
+
+    // API 路径常量
+    // 对话型应用相关路径
+    private static final String CHAT_MESSAGES_PATH = "/chat-messages";
+    private static final String MESSAGES_PATH = "/messages";
+    private static final String CONVERSATIONS_PATH = "/conversations";
+    private static final String AUDIO_TO_TEXT_PATH = "/audio-to-text";
+    private static final String TEXT_TO_AUDIO_PATH = "/text-to-audio";
+    private static final String META_PATH = "/meta";
+    private static final String STOP_PATH = "/stop";
+    private static final String FEEDBACKS_PATH = "/feedbacks";
+    private static final String SUGGESTED_QUESTIONS_PATH = "/suggested";
+    private static final String NAME_PATH = "/name";
+
+    // 文本生成型应用相关路径
+    private static final String COMPLETION_MESSAGES_PATH = "/completion-messages";
+
+    // 工作流应用相关路径
+    private static final String WORKFLOWS_PATH = "/workflows";
+    private static final String WORKFLOWS_RUN_PATH = "/workflows/run";
+    private static final String WORKFLOWS_TASKS_PATH = "/workflows/tasks";
+    private static final String WORKFLOWS_LOGS_PATH = "/workflows/logs";
+
+    /**
+     * 构造函数
+     *
+     * @param baseUrl API基础URL
+     * @param apiKey  API密钥
+     */
+    public DefaultDifyClient(String baseUrl, String apiKey) {
+        super(baseUrl, apiKey);
+    }
+
+    /**
+     * 构造函数
+     *
+     * @param baseUrl    API基础URL
+     * @param apiKey     API密钥
+     * @param httpClient HTTP客户端
+     */
+    public DefaultDifyClient(String baseUrl, String apiKey, OkHttpClient httpClient) {
+        super(baseUrl, apiKey, httpClient);
+    }
+
+    // ==================== 对话型应用相关方法 ====================
+
+    @Override
+    public ChatMessageResponse sendChatMessage(ChatMessage message) throws IOException, DifyApiException {
+        log.debug("发送对话消息: {}", message);
+        return executePost(CHAT_MESSAGES_PATH, message, ChatMessageResponse.class);
+    }
+
+    @Override
+    public void sendChatMessageStream(ChatMessage message, ChatStreamCallback callback) throws IOException, DifyApiException {
+        log.debug("发送流式对话消息: user={}, inputs={}", message.getUser(), message.getInputs() != null ? message.getInputs().keySet() : null);
+        // 确保请求模式为流式
+        message.setResponseMode(ResponseMode.STREAMING);
+
+        // 执行流式请求
+        executeStreamRequest(CHAT_MESSAGES_PATH, message, (line) -> processStreamLine(line, callback, (data, eventType) -> {
+            StreamEventDispatcher.dispatchChatEvent(callback, data, eventType);
+        }), callback::onException);
+    }
+
+    @Override
+    public void sendChatMessageStream(ChatMessage message, ChatflowStreamCallback callback) throws IOException, DifyApiException {
+        log.debug("发送流式对话消息: user={}, inputs={}", message.getUser(), message.getInputs() != null ? message.getInputs().keySet() : null);
+        // 确保请求模式为流式
+        message.setResponseMode(ResponseMode.STREAMING);
+
+        // 执行流式请求
+        executeStreamRequest(CHAT_MESSAGES_PATH, message, (line) -> processStreamLine(line, callback, (data, eventType) -> {
+            StreamEventDispatcher.dispatchChatFlowEvent(callback, data, eventType);
+        }), callback::onException);
+    }
+
+    @Override
+    public SimpleResponse stopChatMessage(String taskId, String user) throws IOException, DifyApiException {
+        log.debug("停止对话消息: taskId={}, user={}", taskId, user);
+        Map<String, String> body = new HashMap<>();
+        body.put("user", user);
+        return executePost(CHAT_MESSAGES_PATH + "/" + taskId + STOP_PATH, body, SimpleResponse.class);
+    }
+
+    @Override
+    public SimpleResponse feedbackMessage(String messageId, String rating, String user, String content) throws IOException, DifyApiException {
+        log.debug("消息反馈: messageId={}, rating={}, user={}", messageId, rating, user);
+        Map<String, String> body = new HashMap<>();
+        body.put("rating", rating);
+        body.put("user", user);
+        if (content != null) {
+            body.put("content", content);
+        }
+        return executePost(MESSAGES_PATH + "/" + messageId + FEEDBACKS_PATH, body, SimpleResponse.class);
+    }
+
+    @Override
+    public SuggestedQuestionsResponse getSuggestedQuestions(String messageId, String user) throws IOException, DifyApiException {
+        log.debug("获取建议问题: messageId={}, user={}", messageId, user);
+        return executeGet(MESSAGES_PATH + "/" + messageId + SUGGESTED_QUESTIONS_PATH + "?user=" + user, SuggestedQuestionsResponse.class);
+    }
+
+    @Override
+    public MessageListResponse getMessages(String conversationId, String user, String firstId, Integer limit) throws IOException, DifyApiException {
+        log.debug("获取消息列表: conversationId={}, user={}, firstId={}, limit={}", conversationId, user, firstId, limit);
+        Map<String, Object> params = new HashMap<>();
+        params.put("conversation_id", conversationId);
+        params.put("user", user);
+        params.put("first_id", firstId);
+        params.put("limit", limit);
+
+        String url = buildUrlWithParams(MESSAGES_PATH, params);
+        Request request = createGetRequest(url);
+        return executeRequest(request, MessageListResponse.class);
+    }
+
+    @Override
+    public ConversationListResponse getConversations(String user, String lastId, Integer limit, String sortBy) throws IOException, DifyApiException {
+        log.debug("获取会话列表: user={}, lastId={}, limit={}, sortBy={}", user, lastId, limit, sortBy);
+        Map<String, Object> params = new HashMap<>();
+        params.put("user", user);
+        params.put("last_id", lastId);
+        params.put("limit", limit);
+        params.put("sort_by", sortBy);
+
+        String url = buildUrlWithParams(CONVERSATIONS_PATH, params);
+        Request request = createGetRequest(url);
+        return executeRequest(request, ConversationListResponse.class);
+    }
+
+    @Override
+    public SimpleResponse deleteConversation(String conversationId, String user) throws IOException, DifyApiException {
+        log.debug("删除会话: conversationId={}, user={}", conversationId, user);
+        Map<String, String> body = new HashMap<>();
+        body.put("user", user);
+        return executeDelete(CONVERSATIONS_PATH + "/" + conversationId, body, SimpleResponse.class);
+    }
+
+    @Override
+    public Conversation renameConversation(String conversationId, String name, Boolean autoGenerate, String user) throws IOException, DifyApiException {
+        log.debug("重命名会话: conversationId={}, name={}, autoGenerate={}, user={}", conversationId, name, autoGenerate, user);
+        Map<String, Object> body = new HashMap<>();
+        body.put("name", name);
+        body.put("auto_generate", autoGenerate);
+        body.put("user", user);
+        return executePost(CONVERSATIONS_PATH + "/" + conversationId + NAME_PATH, body, Conversation.class);
+    }
+
+    @Override
+    public AudioToTextResponse audioToText(File file, String user) throws IOException, DifyApiException {
+        log.debug("语音转文字: fileName={}, user={}", file.getName(), user);
+        RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart("file", file.getName(), RequestBody.create(AUDIO, file)).addFormDataPart("user", user).build();
+
+        Request request = new Request.Builder().url(baseUrl + AUDIO_TO_TEXT_PATH).post(requestBody).header("Authorization", "Bearer " + apiKey).build();
+
+        return executeRequest(request, AudioToTextResponse.class);
+    }
+
+    @Override
+    public AudioToTextResponse audioToText(InputStream inputStream, String fileName, String user) throws IOException, DifyApiException {
+        log.debug("语音转文字: fileName={}, user={}", fileName, user);
+
+        // 创建自定义 RequestBody,避免一次性读取整个文件
+        RequestBody fileBody = new RequestBody() {
+            @Override
+            public MediaType contentType() {
+                return AUDIO;
+            }
+
+            @Override
+            public void writeTo(okio.BufferedSink sink) throws IOException {
+                try (okio.Source source = okio.Okio.source(inputStream)) {
+                    sink.writeAll(source);
+                }
+            }
+        };
+
+        RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart("file", fileName, fileBody).addFormDataPart("user", user).build();
+
+        Request request = new Request.Builder().url(baseUrl + AUDIO_TO_TEXT_PATH).post(requestBody).header("Authorization", "Bearer " + apiKey).build();
+
+        return executeRequest(request, AudioToTextResponse.class);
+    }
+
+    @Override
+    public byte[] textToAudio(String messageId, String text, String user) throws IOException, DifyApiException {
+        log.debug("文字转语音: messageId={}, text={}, user={}", messageId, text, user);
+        Map<String, String> body = new HashMap<>();
+        if (messageId != null) {
+            body.put("message_id", messageId);
+        }
+        if (text != null) {
+            body.put("text", text);
+        }
+        body.put("user", user);
+
+        RequestBody requestBody = createJsonRequestBody(body);
+        Request request = createPostRequest(TEXT_TO_AUDIO_PATH, requestBody);
+        return executeRequestForBytes(request);
+    }
+
+    @Override
+    public AppMetaResponse getAppMeta() throws IOException, DifyApiException {
+        return executeGet(META_PATH, AppMetaResponse.class);
+    }
+
+    // ==================== 文本生成型应用相关方法 ====================
+
+    @Override
+    public CompletionResponse sendCompletionMessage(CompletionRequest request) throws IOException, DifyApiException {
+        log.debug("发送文本生成请求: {}", request);
+        return executePost(COMPLETION_MESSAGES_PATH, request, CompletionResponse.class);
+    }
+
+    @Override
+    public void sendCompletionMessageStream(CompletionRequest request, CompletionStreamCallback callback) throws IOException, DifyApiException {
+        log.debug("发送流式文本生成请求: {}", request);
+        // 确保请求模式为流式
+        request.setResponseMode(ResponseMode.STREAMING);
+
+        // 执行流式请求
+        executeStreamRequest(COMPLETION_MESSAGES_PATH, request, (line) -> processStreamLine(line, callback, (data, eventType) -> {
+            // 分发事件
+            StreamEventDispatcher.dispatchCompletionEvent(callback, data);
+        }), callback::onException);
+    }
+
+    @Override
+    public SimpleResponse stopCompletion(String taskId, String user) throws IOException, DifyApiException {
+        log.debug("停止文本生成: taskId={}, user={}", taskId, user);
+        Map<String, String> body = new HashMap<>();
+        body.put("user", user);
+        return executePost(COMPLETION_MESSAGES_PATH + "/" + taskId + STOP_PATH, body, SimpleResponse.class);
+    }
+
+    // ==================== Workflow应用相关方法 ====================
+
+    @Override
+    public WorkflowRunResponse runWorkflow(WorkflowRunRequest request) throws IOException, DifyApiException {
+        log.debug("执行工作流: {}", request);
+        return executePost(WORKFLOWS_RUN_PATH, request, WorkflowRunResponse.class);
+    }
+
+    @Override
+    public void runWorkflowStream(WorkflowRunRequest request, WorkflowStreamCallback callback) throws IOException, DifyApiException {
+        log.debug("执行流式工作流: {}", request);
+        // 确保请求模式为流式
+        request.setResponseMode(ResponseMode.STREAMING);
+
+        // 执行流式请求
+        executeStreamRequest(WORKFLOWS_RUN_PATH, request, (line) -> processStreamLine(line, callback, (data, eventType) -> {
+            // 分发事件
+            StreamEventDispatcher.dispatchWorkflowEvent(callback, data);
+        }), callback::onException);
+    }
+
+    @Override
+    public WorkflowStopResponse stopWorkflow(String taskId, String user) throws IOException, DifyApiException {
+        log.debug("停止工作流: taskId={}, user={}", taskId, user);
+        Map<String, String> body = new HashMap<>();
+        body.put("user", user);
+        return executePost(WORKFLOWS_TASKS_PATH + "/" + taskId + STOP_PATH, body, WorkflowStopResponse.class);
+    }
+
+    @Override
+    public WorkflowRunStatusResponse getWorkflowRun(String workflowId) throws IOException, DifyApiException {
+        log.debug("获取工作流执行状态: workflowId={}", workflowId);
+        return executeGet(WORKFLOWS_PATH + "/run/" + workflowId, WorkflowRunStatusResponse.class);
+    }
+
+    @Override
+    public WorkflowLogsResponse getWorkflowLogs(String keyword, String status, Integer page, Integer limit) throws IOException, DifyApiException {
+        log.debug("获取工作流日志: keyword={}, status={}, page={}, limit={}", keyword, status, page, limit);
+        Map<String, Object> params = new HashMap<>();
+        params.put("keyword", keyword);
+        params.put("status", status);
+        params.put("page", page);
+        params.put("limit", limit);
+
+        String url = buildUrlWithParams(WORKFLOWS_LOGS_PATH, params);
+        Request request = createGetRequest(url);
+        return executeRequest(request, WorkflowLogsResponse.class);
+    }
+
+    /**
+     * 执行流式请求
+     *
+     * @param path 请求路径
+     * @param body 请求体
+     * @param lineProcessor 行处理器,返回false表示停止处理
+     * @param errorHandler 错误处理器
+     */
+    private void executeStreamRequest(String path, Object body, LineProcessor lineProcessor, Consumer<Exception> errorHandler) {
+        // 创建请求
+        RequestBody requestBody = createJsonRequestBody(body);
+        Request httpRequest = new Request.Builder().url(baseUrl + path).post(requestBody).header("Authorization", "Bearer " + apiKey).header("Content-Type", "application/json").header("Accept", "text/event-stream").build();
+
+        // 执行请求并处理流式响应
+        Call call = httpClient.newCall(httpRequest);
+        call.enqueue(new Callback() {
+            @Override
+            public void onFailure(Call call, IOException e) {
+                log.error("流式请求失败: {}", e.getMessage());
+                errorHandler.accept(e);
+            }
+
+            @Override
+            public void onResponse(Call call, Response response) {
+                if (!response.isSuccessful()) {
+                    try {
+                        String errorBody = response.body() != null ? response.body().string() : "";
+                        DifyApiException exception = createApiException(response.code(), errorBody);
+                        log.error("流式请求失败: {}", exception.getMessage());
+                        errorHandler.accept(exception);
+                    } catch (IOException e) {
+                        log.error("读取错误响应失败", e);
+                        errorHandler.accept(e);
+                    }
+                    return;
+                }
+
+                try (ResponseBody responseBody = response.body()) {
+                    if (responseBody == null) {
+                        IOException exception = new IOException("空响应体");
+                        log.error("流式请求失败: {}", exception.getMessage());
+                        errorHandler.accept(exception);
+                        return;
+                    }
+
+                    try (BufferedReader reader = new BufferedReader(new InputStreamReader(responseBody.byteStream(), StandardCharsets.UTF_8))) {
+                        String line;
+                        while ((line = reader.readLine()) != null) {
+                            if (line.isEmpty()) {
+                                continue;
+                            }
+
+                            // 处理行,如果返回false则停止处理
+                            if (!lineProcessor.process(line)) {
+                                break;
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("处理流式响应失败: {}", e.getMessage(), e);
+                    errorHandler.accept(e);
+                }
+            }
+        });
+    }
+
+    /**
+     * 行处理器接口
+     */
+    @FunctionalInterface
+    private interface LineProcessor {
+        /**
+         * 处理一行数据
+         *
+         * @param line 行数据
+         * @return 是否继续处理
+         */
+        boolean process(String line);
+    }
+
+    /**
+     * 处理流式数据行
+     *
+     * @param line 数据行
+     * @param callback 回调接口
+     * @param eventProcessor 事件处理器
+     * @return 是否继续处理
+     */
+    private boolean processStreamLine(String line, BaseStreamCallback callback, EventProcessor eventProcessor) {
+        if (line.startsWith(DATA_PREFIX)) {
+            String data = line.substring(DATA_PREFIX.length()).trim();
+
+            try {
+                // 解析事件类型
+                BaseEvent baseEvent = JsonUtils.fromJson(data, BaseEvent.class);
+                if (baseEvent == null) {
+                    log.warn("解析事件数据为null: {}", data);
+                    return true; // 继续处理
+                }
+
+                // 处理事件
+                eventProcessor.process(data, baseEvent.getEvent());
+            } catch (Exception e) {
+                log.error("解析事件数据失败: {}", data, e);
+                callback.onException(e);
+            }
+        } else if (PING_EVENT.equalsIgnoreCase(line)) {
+            // 心跳事件与 Dify API 文档中描述不一致,返回的不是data开头
+            PingEvent pingEvent = new PingEvent();
+            pingEvent.setEvent(EventType.PING.getValue());
+            callback.onPing(pingEvent);
+        }
+        return true; // 继续处理
+    }
+
+    /**
+     * 事件处理器接口
+     */
+    @FunctionalInterface
+    private interface EventProcessor {
+        /**
+         * 处理事件
+         *
+         * @param data 事件数据
+         * @param eventType 事件类型
+         */
+        void process(String data, String eventType);
+    }
+}

+ 306 - 0
ruoyi-modules/ruoyi-uniapp/src/main/java/com/ruoyi/uniapp/service/impl/DefaultDifyDatasetsClient.java

@@ -0,0 +1,306 @@
+package com.ruoyi.uniapp.service.impl;
+
+import com.ruoyi.common.core.model.datasets.*;
+import com.ruoyi.uniapp.utils.DifyDatasetsClient;
+import com.ruoyi.common.core.exception.DifyApiException;
+import com.ruoyi.common.core.model.common.SimpleResponse;
+import com.ruoyi.common.core.utils.JsonUtils;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Dify 知识库客户端默认实现
+ * 提供知识库相关的操作
+ */
+@Slf4j
+public class DefaultDifyDatasetsClient extends AbstractDifyClient implements DifyDatasetsClient {
+    // API 路径常量
+    private static final String DATASETS_PATH = "/datasets";
+    private static final String DOCUMENTS_PATH = "/documents";
+    private static final String SEGMENTS_PATH = "/segments";
+    private static final String DOCUMENT_CREATE_BY_TEXT_PATH = "/document/create-by-text";
+    private static final String DOCUMENT_CREATE_BY_FILE_PATH = "/document/create-by-file";
+    private static final String UPDATE_BY_TEXT_PATH = "/update-by-text";
+    private static final String UPDATE_BY_FILE_PATH = "/update-by-file";
+    private static final String INDEXING_STATUS_PATH = "/indexing-status";
+    private static final String UPLOAD_FILE_PATH = "/upload-file";
+    private static final String RETRIEVE_PATH = "/retrieve";
+    private static final String METADATA_PATH = "/metadata";
+
+    /**
+     * 构造函数
+     *
+     * @param baseUrl API基础URL
+     * @param apiKey  API密钥
+     */
+    public DefaultDifyDatasetsClient(String baseUrl, String apiKey) {
+        super(baseUrl, apiKey);
+    }
+
+    /**
+     * 构造函数
+     *
+     * @param baseUrl    API基础URL
+     * @param apiKey     API密钥
+     * @param httpClient HTTP客户端
+     */
+    public DefaultDifyDatasetsClient(String baseUrl, String apiKey, OkHttpClient httpClient) {
+        super(baseUrl, apiKey, httpClient);
+    }
+
+    @Override
+    public DatasetResponse createDataset(CreateDatasetRequest request) throws IOException, DifyApiException {
+        return executePost(DATASETS_PATH, request, DatasetResponse.class);
+    }
+
+    @Override
+    public DatasetListResponse getDatasets(Integer page, Integer limit) throws IOException, DifyApiException {
+        Map<String, Object> queryParams = new HashMap<>();
+        addIfNotNull(queryParams, "page", page);
+        addIfNotNull(queryParams, "limit", limit);
+
+        String url = buildUrlWithParams(DATASETS_PATH, queryParams);
+        return executeGet(url, DatasetListResponse.class);
+    }
+
+    @Override
+    public SimpleResponse deleteDataset(String datasetId) throws IOException, DifyApiException {
+        String path = DATASETS_PATH + "/" + datasetId;
+        Request httpRequest = createDeleteRequest(path, null);
+
+        try (Response response = httpClient.newCall(httpRequest).execute()) {
+            if (response.code() == 204) {
+                SimpleResponse simpleResponse = new SimpleResponse();
+                simpleResponse.setResult("success");
+                return simpleResponse;
+            }
+            return handleResponse(response, SimpleResponse.class);
+        }
+    }
+
+    @Override
+    public DocumentResponse createDocumentByText(String datasetId, CreateDocumentByTextRequest request) throws IOException, DifyApiException {
+        String path = DATASETS_PATH + "/" + datasetId + DOCUMENT_CREATE_BY_TEXT_PATH;
+        return executePost(path, request, DocumentResponse.class);
+    }
+
+    @Override
+    public DocumentResponse createDocumentByFile(String datasetId, CreateDocumentByFileRequest request, File file) throws IOException, DifyApiException {
+        String path = DATASETS_PATH + "/" + datasetId + DOCUMENT_CREATE_BY_FILE_PATH;
+
+        // 构建multipart请求
+        MultipartBody.Builder multipartBuilder = createMultipartBuilder(request, file);
+        return executeMultipartRequest(path, multipartBuilder.build(), DocumentResponse.class);
+    }
+
+    @Override
+    public DocumentResponse createDocumentByFile(String datasetId, CreateDocumentByFileRequest request, InputStream inputStream, String fileName) throws IOException, DifyApiException {
+        String path = DATASETS_PATH + "/" + datasetId + DOCUMENT_CREATE_BY_FILE_PATH;
+
+        // 读取输入流内容
+        byte[] bytes = readAllBytes(inputStream);
+
+        // 构建multipart请求
+        MultipartBody.Builder multipartBuilder = new MultipartBody.Builder()
+                .setType(MultipartBody.FORM)
+                .addFormDataPart("data", JsonUtils.toJson(request))
+                .addFormDataPart("file", fileName, RequestBody.create(bytes, OCTET_STREAM));
+
+        return executeMultipartRequest(path, multipartBuilder.build(), DocumentResponse.class);
+    }
+
+    @Override
+    public DocumentResponse updateDocumentByText(String datasetId, String documentId, UpdateDocumentByTextRequest request) throws IOException, DifyApiException {
+        String path = buildDocumentPath(datasetId, documentId) + UPDATE_BY_TEXT_PATH;
+        return executePost(path, request, DocumentResponse.class);
+    }
+
+    @Override
+    public DocumentResponse updateDocumentByFile(String datasetId, String documentId, UpdateDocumentByFileRequest request, File file) throws IOException, DifyApiException {
+        String path = buildDocumentPath(datasetId, documentId) + UPDATE_BY_FILE_PATH;
+
+        // 构建multipart请求
+        MultipartBody.Builder multipartBuilder = createMultipartBuilder(request, file);
+        return executeMultipartRequest(path, multipartBuilder.build(), DocumentResponse.class);
+    }
+
+    @Override
+    public IndexingStatusResponse getIndexingStatus(String datasetId, String batch) throws IOException, DifyApiException {
+        String path = DATASETS_PATH + "/" + datasetId + DOCUMENTS_PATH + "/" + batch + INDEXING_STATUS_PATH;
+        return executeGet(path, IndexingStatusResponse.class);
+    }
+
+    @Override
+    public SimpleResponse deleteDocument(String datasetId, String documentId) throws IOException, DifyApiException {
+        String path = buildDocumentPath(datasetId, documentId);
+        return executeDelete(path, null, SimpleResponse.class);
+    }
+
+    @Override
+    public DocumentListResponse getDocuments(String datasetId, String keyword, Integer page, Integer limit) throws IOException, DifyApiException {
+        Map<String, Object> queryParams = new HashMap<>();
+        addIfNotEmpty(queryParams, "keyword", keyword);
+        addIfNotNull(queryParams, "page", page);
+        addIfNotNull(queryParams, "limit", limit);
+
+        String path = DATASETS_PATH + "/" + datasetId + DOCUMENTS_PATH;
+        String url = buildUrlWithParams(path, queryParams);
+        return executeGet(url, DocumentListResponse.class);
+    }
+
+    @Override
+    public SegmentResponse createSegments(String datasetId, String documentId, CreateSegmentsRequest request) throws IOException, DifyApiException {
+        String path = buildDocumentPath(datasetId, documentId) + SEGMENTS_PATH;
+        return executePost(path, request, SegmentResponse.class);
+    }
+
+    @Override
+    public SegmentListResponse getSegments(String datasetId, String documentId, String keyword, String status) throws IOException, DifyApiException {
+        Map<String, Object> queryParams = new HashMap<>();
+        addIfNotEmpty(queryParams, "keyword", keyword);
+        addIfNotEmpty(queryParams, "status", status);
+
+        String path = buildDocumentPath(datasetId, documentId) + SEGMENTS_PATH;
+        String url = buildUrlWithParams(path, queryParams);
+        return executeGet(url, SegmentListResponse.class);
+    }
+
+    @Override
+    public SimpleResponse deleteSegment(String datasetId, String documentId, String segmentId) throws IOException, DifyApiException {
+        String path = buildSegmentPath(datasetId, documentId, segmentId);
+        return executeDelete(path, null, SimpleResponse.class);
+    }
+
+    @Override
+    public SegmentResponse updateSegment(String datasetId, String documentId, String segmentId, UpdateSegmentRequest request) throws IOException, DifyApiException {
+        String path = buildSegmentPath(datasetId, documentId, segmentId);
+        return executePost(path, request, SegmentResponse.class);
+    }
+
+    @Override
+    public UploadFileResponse getUploadFile(String datasetId, String documentId) throws IOException, DifyApiException {
+        String path = buildDocumentPath(datasetId, documentId) + UPLOAD_FILE_PATH;
+        return executeGet(path, UploadFileResponse.class);
+    }
+
+    @Override
+    public RetrieveResponse retrieveDataset(String datasetId, RetrieveRequest request) throws IOException, DifyApiException {
+        String path = DATASETS_PATH + "/" + datasetId + RETRIEVE_PATH;
+        return executePost(path, request, RetrieveResponse.class);
+    }
+
+    @Override
+    public MetadataResponse createMetadata(String datasetId, CreateMetadataRequest request) throws IOException, DifyApiException {
+        String path = DATASETS_PATH + "/" + datasetId + METADATA_PATH;
+        return executePost(path, request, MetadataResponse.class);
+    }
+
+    @Override
+    public MetadataResponse updateMetadata(String datasetId, String metadataId, UpdateMetadataRequest request) throws IOException, DifyApiException {
+        String path = DATASETS_PATH + "/" + datasetId + METADATA_PATH + "/" + metadataId;
+        return executePatch(path, request, MetadataResponse.class);
+    }
+
+    @Override
+    public String deleteMetadata(String datasetId, String metadataId) throws IOException, DifyApiException {
+        String path = DATASETS_PATH + "/" + datasetId + METADATA_PATH + "/" + metadataId;
+        return executeDelete(path, null, String.class);
+    }
+
+    /**
+     * 执行Multipart请求
+     *
+     * @param path 请求路径
+     * @param requestBody 请求体
+     * @param responseClass 响应类型
+     * @param <T> 响应类型
+     * @return 响应对象
+     * @throws IOException IO异常
+     * @throws DifyApiException API异常
+     */
+    private <T> T executeMultipartRequest(String path, RequestBody requestBody, Class<T> responseClass) throws IOException, DifyApiException {
+        Request httpRequest = new Request.Builder()
+                .url(baseUrl + path)
+                .post(requestBody)
+                .header("Authorization", "Bearer " + apiKey)
+                .build();
+
+        try (Response response = httpClient.newCall(httpRequest).execute()) {
+            return handleResponse(response, responseClass);
+        }
+    }
+
+    /**
+     * 创建Multipart请求构建器
+     *
+     * @param request 请求对象
+     * @param file 文件
+     * @return Multipart请求构建器
+     */
+    private MultipartBody.Builder createMultipartBuilder(Object request, File file) {
+        return new MultipartBody.Builder()
+                .setType(MultipartBody.FORM)
+                .addFormDataPart("data", JsonUtils.toJson(request))
+                .addFormDataPart("file", file.getName(), RequestBody.create(file, OCTET_STREAM));
+    }
+
+    /**
+     * 构建文档路径
+     *
+     * @param datasetId 知识库ID
+     * @param documentId 文档ID
+     * @return 文档路径
+     */
+    private String buildDocumentPath(String datasetId, String documentId) {
+        return DATASETS_PATH + "/" + datasetId + DOCUMENTS_PATH + "/" + documentId;
+    }
+
+    /**
+     * 构建分段路径
+     *
+     * @param datasetId 知识库ID
+     * @param documentId 文档ID
+     * @param segmentId 分段ID
+     * @return 分段路径
+     */
+    private String buildSegmentPath(String datasetId, String documentId, String segmentId) {
+        return buildDocumentPath(datasetId, documentId) + SEGMENTS_PATH + "/" + segmentId;
+    }
+
+    /**
+     * 读取输入流的所有字节 (Java 8兼容方法)
+     *
+     * @param inputStream 输入流
+     * @return 字节数组
+     * @throws IOException IO异常
+     */
+    private byte[] readAllBytes(InputStream inputStream) throws IOException {
+        try {
+            // 创建缓冲区
+            byte[] buffer = new byte[8192];
+            int bytesRead;
+            java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream();
+            
+            // 读取数据直到输入流结束
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                output.write(buffer, 0, bytesRead);
+            }
+            
+            return output.toByteArray();
+        } finally {
+            // 确保关闭输入流
+            try {
+                inputStream.close();
+            } catch (IOException e) {
+                // 忽略关闭异常
+            }
+        }
+    }
+
+}

Some files were not shown because too many files changed in this diff