【Java手搓RAGFlow】-12- BaoRAGFlow最终项目展示

【Java手搓RAGFlow】-12- BaoRAGFlow最终项目展示

  • [1 引言](#1 引言)
  • [2 前端](#2 前端)
    • [2.1 启动前端](#2.1 启动前端)
    • [2.2 前端Login展示](#2.2 前端Login展示)
  • [3 后端修改](#3 后端修改)
    • [3.1 修改SecurityConfig](#3.1 修改SecurityConfig)
    • [3.2 修改ChatWebSocketHandler](#3.2 修改ChatWebSocketHandler)
    • [3.3 后端启动](#3.3 后端启动)
  • [4 功能展示](#4 功能展示)
    • [4.1 上传文件并解析](#4.1 上传文件并解析)
    • [4.2 RAG 流式对话实测](#4.2 RAG 流式对话实测)
      • [4.2.1 知识库增强对话](#4.2.1 知识库增强对话)
      • [4.2.2 非知识库问答](#4.2.2 非知识库问答)
  • [5 总结](#5 总结)

1 引言

在之前的系列文章中,我们一步一个脚印,硬核手搓了 RAGFlow 的核心后端架构:从登录注册的安全校验,到文件上传解析的流程,再到Kafka 异步解耦与实时通信。后端的大厦已经落成,现在只差最后一块拼图------前端交互界面。

为了验证我们后端的健壮性并快速落地产品,本次我尝试使用 Cursor 的 Agent 模式,通过自然语言交互,为我们的 Java 后端生成了一套现代化的前端 UI。

本文将展示最终的项目成果,并记录在前后端联调过程中,为了适配前端数据流而进行的后端架构微调。

2 前端

在这个环节,我们没有手写繁琐的 CSS 和 HTML,而是利用 Cursor Agent 根据后端接口文档直接生成 Vue 代码。

生成好后

2.1 启动前端

前端生成完毕后,通过标准的 Node.js 流程启动:

java 复制代码
npm install
npm dev run 

2.2 前端Login展示

3 后端修改

在前后端联调的过程中,我们发现 Cursor 生成的前端代码虽然美观,但在数据交互格式上与后端存在细微差异。为了提升系统的兼容性和鲁棒性,我们对后端进行了以下关键修改。

cursor自己为了适配前端,也做了许多后端上的修改

3.1 修改SecurityConfig

修改com.alibaba.config.SecurityConfig,添加bean

由于前后端分离部署(前端运行在 Node服务器,后端运行在 Tomcat),跨域资源共享(CORS)是必须解决的问题。我们在 SecurityConfig 中添加了全局 CORS 配置,放行了常用的 HTTP 方法,并允许携带凭证。

java 复制代码
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOriginPatterns(List.of("*"));
        configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
        configuration.setAllowedHeaders(List.of("*"));
        configuration.setAllowCredentials(true);
        configuration.setMaxAge(Duration.ofHours(1));

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

3.2 修改ChatWebSocketHandler

修改com.alibaba.handler.ChatWebSocketHandler#handleTextMessageyi

主要是为了解决 WebSocket 收到的 JSON 里 message/userId 可能不是纯字符串时导致的崩溃。

  • 原来用 Map<String, String> 直接映射,如果前端传数字或嵌套对象(例如 userId: 123),Jackson 会尝试把它硬转换成 String,在运行期抛 ClassCastException,从而导致整个消息处理失败。
  • 改成 Map<String, Object> 后,先拿到 Object 再调用 toString()/trim(),就可以兼容数值和字符串输入,并且在缺失时主动给出错误提示,不再因为类型不匹配就断开连接。
  • 同时检查空消息和缺失的 userId,把错误以 WebSocket 返回给前端,避免进入业务逻辑后再报错。

所以这是为了增强后端对前端数据格式的容错能力,并显式告知错误原因,而不是让 Handler 直接抛异常。

java 复制代码
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        try {
            logger.info("收到消息,会话ID: {}, 消息: {}", session.getId(), message.getPayload());

            // 1. 解析消息
            Map<String, Object> request = objectMapper.readValue(
                    message.getPayload(),
                    Map.class
            );
            Object messageObj = request.get("message");
            String userMessage = messageObj instanceof String ? (String) messageObj :
                    (messageObj != null ? messageObj.toString() : null);
            if (userMessage != null) {
                userMessage = userMessage.trim();
            }
            Object userIdObj = request.get("userId"); // 简化版,实际应该从认证中获取
            String userId = userIdObj instanceof String ? (String) userIdObj :
                    (userIdObj != null ? userIdObj.toString() : null);

            if (userMessage == null || userMessage.isEmpty()) {
                sendError(session, "消息不能为空");
                return;
            }
            if (userId == null || userId.isEmpty()) {
                sendError(session, "用户信息缺失");
                return;
            }

            // 存储用户ID
            sessionUserMap.put(session.getId(), userId);

            // 2. 处理消息并流式返回
            chatHandler.processMessage(userId, userMessage, session);
        } catch (Exception e) {
            logger.error("处理消息失败", e);
            sendError(session, "处理失败: " + e.getMessage());
        }
    }

3.3 后端启动

启动成功后我们的索引会正常创建,可以看到 Elasticsearch 的索引初始化日志正常打印,说明数据库连接与 ORM 映射无误。

4 功能展示

4.1 上传文件并解析

用户在前端上传技术文档,后端通过 Tika组件进行解析,并将文本切片向量化存储。

观察后端控制台日志,可以看到文件流被正确接收并触发了对应的任务:

4.2 RAG 流式对话实测

这是系统的核心价值所在:基于检索增强生成的对话。

4.2.1 知识库增强对话

我们向系统提问:"Collection与Collections的区别?"

这是一个典型的 Java 面试题,也是我们上传的文档中包含的内容。

可以看到,AI 并没有胡言乱语,而是精准地引用了知识库中的内容进行回答,且响应速度极快(流式输出)。

4.2.2 非知识库问答

为了验证 Prompt Engineering 的效果,我们询问一个知识库范围外的问题。

系统严格遵守了预设的 System Prompt:"如果知识库中没有相关信息,请告知不知道,不要编造。"

这种严谨的回答机制,对于企业级知识库应用至关重要,有效避免了"AI 幻觉"。

5 总结

至此,【Java手搓RAGFlow】系列教程的主体功能已全部实现。我们从零开始,构建了一个包含Spring Boot、Elasticsearch、Kafka、WebSocket 以及 LLM 接入的完整 RAG 系统。

回顾整个项目:

  • 架构层面:实现了计算密集型任务(解析)与IO密集型任务(对话)的解耦。
  • 数据层面:打通了非结构化数据到向量数据库的 ETL 链路。
  • 交互层面:完成了 WebSocket 全双工流式对话。

BaoRAGFlow 它涵盖了 RAG 应用开发中最核心的痛点与解决方案。希望这一系列文章能为你自己动手实现 AI 应用提供有价值的参考。

我们下一个手搓openManus见!

相关推荐
~无忧花开~4 小时前
JavaScript实现PDF本地预览技巧
开发语言·前端·javascript
yumgpkpm4 小时前
数据可视化AI、BI工具,开源适配 Cloudera CMP 7.3(或类 CDP 的 CMP 7.13 平台,如华为鲲鹏 ARM 版)值得推荐?
人工智能·hive·hadoop·信息可视化·kafka·开源·hbase
靠沿4 小时前
Java数据结构初阶——LinkedList
java·开发语言·数据结构
4***99744 小时前
Kotlin序列处理
android·开发语言·kotlin
froginwe114 小时前
Scala 提取器(Extractor)
开发语言
t***D2644 小时前
Kotlin在服务端开发中的生态建设
android·开发语言·kotlin
qq_12498707534 小时前
基于springboot的建筑业数据管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·毕业设计
亚马逊云开发者4 小时前
通过Amazon Q CLI 集成DynamoDB MCP 实现游戏场景智能数据建模
人工智能
nix.gnehc5 小时前
PyTorch
人工智能·pytorch·python