上一篇把 RuoYi 和 RAGFlow 的底座搭起来了,Docker Compose 部署、内网登录简化这些基础工作做完后,很多朋友在问同一个问题:底座有了,接下来数据到底怎么进去?怎么在 RuoYi 后台里让用户上传文档、触发解析、最后还能像聊天一样问出自己的私有知识库?这篇就是冲着这些问题来的。
这篇实践覆盖的范围比较明确:RuoYi 后端怎么封装 RAGFlow 的 OpenAPI、登录用户信息从哪里拿、文档解析怎么选模板、批量导入怎么不把流程写死、本地大模型选 Llama 还是国产开源模型、知识库问答怎么进一步做成智能体,最后附上这段时间踩过的坑汇总。适合已经部署完 RAGFlow、想在 RuoYi 框架里做私有化 AI 应用的后端开发,也适合正在做开源 RAG 方案选型的朋友参考。
1. 这一篇要补上什么
1.1 为什么单独把集成拿出来写
RuoYi 本身的定位是后台管理框架,用户、角色、菜单、权限这一套很成熟,但里面没有“知识库”这种东西。RAGFlow 定位是 RAG 引擎,文档解析和向量检索很强,但它自己的账号体系、权限模型比较弱,也不适合让业务人员直接用它的界面去管理企业知识库。两者拼在一起,本质上就是让 RuoYi 当入口和权限层,把 RAGFlow 当被调用的服务,所有文档上传、解析、问答操作都在 RuoYi 的菜单和按钮权限控制下完成。
第一次做这个集成时容易犯一个错:直接在 RAGFlow 界面里建数据集、传文件,然后 RuoYi 只负责跳个链接。这种做法的后果是权限失控,任何人都能进 RAGFlow 后台看到所有知识库,审计也没法做。这次我写的所有操作都走 RuoYi 后端转发,RAGFlow 的 API Key 只保存在后端,前端永远接触不到,这是私有化知识库最基本的安全底线。
1.2 目标架构和这次实践的范围
先明确整个集成的目标架构,方便后面理解每一步在干什么。
- 展示层:RuoYi-Vue 前端,新增“知识库管理”和“知识问答”两个菜单。
- 控制层:RuoYi 后端(Spring Boot),负责鉴权、日志、数据落库,同时封装 RAGFlow 的 HTTP 接口。
- RAG 引擎:RAGFlow 服务,负责文档解析、切片、向量化、检索和问答补全。
- 模型层:本地 Ollama 或 vLLM 提供的开源大模型,也可以是公司采购的 API,但私有化场景我默认用内网部署。
这篇默认你已经把 RAGFlow 容器跑起来了,RuoYi 后端也能正常登录。如果还没有,先回看系列第一篇,Docker 部署那部分已经写得很详细,这里不再重复。
2. RuoYi 后端接入 RAGFlow:接口和数据流设计
2.1 先搞清楚 RAGFlow 的 OpenAPI 到底能干什么
RAGFlow 从很早期版本就提供了 OpenAPI,前缀一般是/api/v1,用 Bearer Token 鉴权。不同小版本之间参数有过调整,所以真实联调时以你们部署版本的 OpenAPI 文档为准,但常用接口的形态基本稳定。
我平时高频用到的就这么几个:
- 创建数据集:传数据集名称、权限级别、关联的 Embedding 模型。
- 上传文档:往指定数据集里传文件,支持 PDF、DOCX、Excel、TXT、Markdown、图片等。
- 触发解析:对已上传的文档发起异步解析任务。
- 查询解析状态:返回文档的进度、状态、报错信息。
- 执行检索:给定问题,从指定数据集里召回相关片段。
- Chat 对话:调用已配置好的 Chat Agent,直接返回答案和引用。
在做 RuoYi 封装之前,建议先用 curl 把这几个接口全部通一遍。这一步能帮你提前区分是 RAGFlow 本身的问题还是后面 RuoYi 代码的问题。我在本地调试时最常用的一条命令大概长这样:
curl -X POST "http://localhost:9380/api/v1/datasets" \ -H "Authorization: Bearer ragflow_xxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"name":"测试数据集","embedding_model":"BAAI/bge-zh-v1.5"}'2.2 登录用户信息从哪里来,怎么和操作记录串起来
标题里追问的“RuoYi 在哪里写入登录用户的信息”是后端开发绕不开的点。RuoYi 登录成功后,用户信息会在SysLoginService里被封装成LoginUser对象,通过TokenService.createToken()生成 Token 并写入 Redis。之后每个请求进来,拦截器根据 Header 里的 Authorization 从 Redis 取回LoginUser,再放到 Spring Security 上下文里。
业务代码里拿当前用户,标准姿势是调SecurityUtils.getLoginUser().getUserId()和SecurityUtils.getUsername()。后面所有和 RAGFlow 相关的操作,我都会把这些信息写入业务表。
以文档上传为例,RuoYi 里新建一张rag_file表,字段包括文件ID、所属数据集ID、RAGFlow 文档ID、文件名、解析状态、创建人。创建人字段不是手填的,直接从SecurityUtils.getLoginUser()拿。这样做的好处是所有操作都能追溯到人,也符合企业做审计的要求。
2.3 核心代码:RuoYi 后端 RAGFlow 客户端的封装思路
RuoYi 本身没有专门的 RAGFlow 客户端,我建议新建一个RagFlowClientService,统一封装 HTTP 调用,避免业务 Controller 里到处重复写 RestTemplate。
先定义一个配置类,把 RAGFlow 的地址和 API Key 放到application.yml里,不要硬编码:
ragflow: base-url: http://localhost:9380 api-key: ragflow_xxxxxxxx然后是创建数据集的核心代码:
@Service public class RagFlowClient { private final RestTemplate restTemplate; private final RagFlowProperties props; public RagFlowClient(RestTemplate restTemplate, RagFlowProperties props) { this.restTemplate = restTemplate; this.props = props; } public String createDataset(String name, String embeddingModel) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(props.getApiKey()); Map<String, Object> body = new HashMap<>(); body.put("name", name); body.put("embedding_model", embeddingModel); body.put("permission", "team"); HttpEntity<Map<String, Object>> request = new HttpEntity<>(body, headers); ResponseEntity<String> response = restTemplate.postForEntity( props.getBaseUrl() + "/api/v1/datasets", request, String.class); if (!response.getStatusCode().is2xxSuccessful()) { throw new ServiceException("RAGFlow 创建数据集失败"); } return response.getBody(); } }上传文件时 RestTemplate 处理 multipart 比较啰嗦,我实际项目里用的是 OkHttp。核心逻辑就是构造一个MultipartBody,附上file字段和 Bearer Token,把文件流发给 RAGFlow。解析触发则是 POST 到/api/v1/datasets/{datasetId}/documents/{docId}/parse,这个接口是异步的,返回后不代表解析完成,需要轮询状态。
2.4 权限控制和操作日志不能省
RuoYi 的价值就在这。每个和 RAGFlow 相关的接口,都给配上按钮权限点,比如rag:dataset:add、rag:file:upload、rag:chat:send。Controller 上直接加@PreAuthorize("@ss.hasPermi('rag:file:upload')"),只有被分配了权限的用户才能上传文件。
同时加上@Log注解,记录操作人、操作模块、操作内容。我第一次做的版本没加日志,后来出了个问题:有人删了某个数据集,整个业务线的知识库都没了,但完全查不到是谁删的。从那以后我定了个规矩,RAGFlow 相关的写操作一律记日志,而且日志里必须带数据集名称。
3. 文件解析:最容易拉胯的环节
3.1 RAGFlow 解析模板怎么选
RAGFlow 让我觉得最值钱的就是解析模板,这也是“ragflow 解析技巧”里最值得先说的一条。它支持通用、问答、书籍、论文、手册、表格、法律、财报、代码等多种模板,不同模板影响的是切分策略。
实际选择时,我的经验是:
- 全电子版 PDF、Word,用通用模板,最稳。
- 扫描版 PDF,必须开 OCR,建议用深度解析类模板,不然出来的 chunk 全是乱码。
- 表格密集的 Excel 或单据,用表格模板,RAGFlow 对表格结构还原做得比较细。
- 代码文件,用代码模板,它会按函数或逻辑块切分,而不是死板按字符数切。
- FAQ 类的文档,用问答模板,检索时直接返回问题和答案对,效果比通用模板好很多。
很多人忽略一个关键动作:解析完一定要抽查 chunk。RAGFlow 管理界面里能看到文档切出来的所有片段,如果发现某个 PDF 解析出来的是乱码或者内容顺序错乱,别急着调提示词,先换模板或者开 OCR。
3.2 批量处理文件时怎么设计流程
企业知识库一开始导入就是成百上千个文件,不可能让用户一个个在 RAGFlow 界面里操作。RuoYi 这边的批量流程,我建议做成这样:
- 前端支持多选文件,逐个上传到 RuoYi 临时目录。
- RuoYi 后端完成格式校验后,调用 RAGFlow 上传文档接口,把文件流转发过去。
- 上传成功后,再统一触发解析。
- 前端通过轮询接口查看每个文件的解析状态,展示“排队中、解析中、已完成、失败”。
这里有三个容易踩的坑。第一,不要等 RAGFlow 解析完成后再传下一个文件,RAGFlow 有异步任务队列,批量提交没问题,串行反而慢。第二,轮询间隔不要低于 10 秒,解析任务在队列里和实际执行中变化很快,太频繁只会增加后端压力。第三,解析不是实时完成的,大 PDF 可能要好几分钟,同步接口会很容易超时。
我在项目里用 Spring 的@Scheduled写了一个定时任务,每 15 秒扫描状态为“解析中”的记录,调用 RAGFlow 查询接口更新进度,超过 30 分钟还没完成的直接标记异常并重试一次。这个方案实测下来稳定,也不需要在 RAGFlow 那边额外配置 Webhook。
3.3 解析失败的三种典型场景和处理办法
解析失败在私有化部署里非常常见,不要慌,按状态排查就行。
- 一直处于“待解析”:说明任务没被消费,重启 RAGFlow 的 server 容器或者检查 Redis 是否正常。
- 状态变成了失败,报错信息和 PDF 内容相关:多半是文件本身的问题,比如扫描版 PDF 没开 OCR、文件后缀名和真实格式不一致。建议上传时不要只按扩展名判断,用 magic bytes 校验真实文件类型。
- 解析成功但检索不到内容:检查数据集捆绑的 Embedding 模型是否配置成功,索引构建有延迟。
某次客户现场导入一批合同扫描件,解析失败率高达 40%,后来发现是这批 PDF 没有文本层,OCR 又没开。处理办法是统一走深度解析模板加 OCR,失败率降到 3% 以下。扫描版文档千万别图省事用通用模板。
4. LLM 选型:国内私有化部署到底怎么选
4.1 Llama 适合国内企业吗
“Llama 适合国内企业拿来搞知识库问答和私有化 Agent 部署吗”这个问题我一次次被问到,直接说结论:不太适合。
Llama 3.1 的英文能力确实强,但中文语料占比低,直接拿来做中文知识库问答,经常出现表达生硬、理解偏差的问题。更关键的是 Llama 的中文指令遵循能力一般,做 Agent 工具调用时更容易出幺蛾子。国产模型里,Qwen 系列、DeepSeek 系列、智谱 GLM 系列的中文底子和社区生态都更合适。
如果团队的 GPU 资源紧张,首选 Qwen2.5-7B 的 4bit 量化版,一张 12G 显存的卡就能跑起来;显存有 24G 的两张,直接上 Qwen2.5-14B 或 DeepSeek-R1-Distill-Qwen-14B。真要追求深度推理,可以在回答链路外面套一层大模型反思,但这不是私有化知识库的第一步。
4.2 RAGFlow 本地化部署时怎么接模型
RAGFlow 支持的模型接入方式很多,最省事的是在它的管理后台配置一个 OpenAI 兼容地址,指向本地部署的 vLLM 或 Ollama 服务。填 Base URL 的时候注意,要填到/v1这一级,比如http://10.0.0.15:8000/v1,API Key 填一个自定义值就行了,本地服务一般不校验。
Embedding 模型我建议用BAAI/bge-zh-v1.5或bge-m3,中文场景比text-embedding-ada-002稳。如果内网完全隔离,Embedding 模型文件需要在部署阶段提前拉取到本地,否则容器启动后连不上外网就会一直失败。这个属于 RAGFlow 本地化部署里最容易被忽略的细节。
4.3 开源方案对比:Dify、RAGFlow、FastGPT 怎么选
很多人在 RuoYi 集成前会纠结选 Dify 还是 RAGFlow 还是 FastGPT,我从企业落地的角度做个对比,方便按场景选。
| 产品 | 最强项 | 明显短板 | 适合场景 |
|---|---|---|---|
| RAGFlow | DeepDoc 解析能力强,RAG 调参细,召回效果好 | Agent 编排和业务流程能力相对弱 | 企业文档多且杂,核心诉求是私有知识库问答 |
| Dify | 工作流和 Agent 编排丰富,插件生态多,做成产品很快 | 深度解析和召回调优不如 RAGFlow 细,部分企业功能在商业版 | 要快速搭建对话应用、Agent 流程 |
| FastGPT | 中文产品体验好,知识库加工作流一体,部署简单 | 大规模知识库要额外维护 ES,解析能力中规中矩 | 中小团队快速上线知识库助手 |
如果主应用是 RuoYi,我更建议把 RAGFlow 当纯 RAG 引擎来用,不要纠结它的 Agent 编排够不够强。上层业务编排完全可以由 RuoYi 自己来,这样权限、审批、流程都可以用自己的逻辑,不冗余。
5. 从知识库问答到智能体:RAGFlow 在 RuoYi 里的落地方式
5.1 在 RAGFlow 里配置一个可用的 Chat Agent
RAGFlow 新版里应用管理区分了 Chat Assistant 和 Agent 两类。Chat Assistant 适合直接做知识库问答,配置起来最快:选一个数据集、选一个大模型、写一段 Prompt,保存后就有 chat_id。
Prompt 不要写得太大而空,就按企业知识库客服的标准写,明确“只根据提供的知识库片段回答,不要编造事实,无法回答时直接说明不知道”。示例 Prompt 里最好带上引用要求,让流程后面的引用解析有据可依。
5.2 RuoYi 后端封装问答接口
RuoYi 后端封装问答接口,核心就是转发 RAGFlow 的 Chat 接口。我提供一个简化示例,Java 里用 RestTemplate 发起一片问答请求:
public String chat(String question, String sessionId) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(props.getApiKey()); Map<String, Object> body = new HashMap<>(); body.put("question", question); body.put("stream", false); if (sessionId != null) { body.put("session_id", sessionId); } HttpEntity<Map<String, Object>> request = new HttpEntity<>(body, headers); ResponseEntity<String> response = restTemplate.postForEntity( props.getBaseUrl() + "/api/v1/chats/" + props.getChatId() + "/completions", request, String.class); return response.getBody(); }返回结果里除了答案,还有引用文件列表。前端拿到引用后要展示出来,因为企业内部知识库问答,用户会追问“这句话出自哪份文档”,没有引用,回答的可信度会大打折扣。我建议把引用解析成结构化数据再传给前端,别直接扔原始 JSON。
5.3 更进一步:在 RuoYi 里做 Function Calling 式智能体
如果 RAGFlow 自带 Agent 编排满足不了需求,可以在 RuoYi 后端自己做一套轻量智能体。思路是:RuoYi 后端直接对接支持 Function Calling 的大模型,把查询知识库注册成一个工具函数,模型决定需要检索时,后端调 RAGFlow 的检索接口,把召回片段作为上下文拼进 Prompt,最后再由模型生成答复。
这样做的最大好处是,知识的检索变成了业务系统里的一个能力,可以和 RuoYi 自己的业务接口连起来。比如用户在问“最近项目的进度”时,模型可以同时检索 RAGFlow 里的项目文档,再调 RuoYi 的业务接口拿实时进度数据,答案来自两套系统。
这种方案对模型的要求是必须支持工具调用。实测下来 Qwen 和 DeepSeek 系列都比较稳,太小的 7B 模型可能在复杂工具调用上不稳定,可以考虑先用 14B,后面再根据实际效果做量化压缩。
6. 常见问题与排查实录
6.1 Windows 11 下跑 RAGFlow 的注意事项
很多开发者的本机是 Windows 11,RAGFlow 用 Docker Desktop 跑起来没问题,但有几个坑必须先说。WSL2 的内存一定要给足,我推荐至少给 6GB 到 8GB,否则容器启动中途会被直接杀掉。Docker Desktop 的配置不只是调界面里的内存,还要检查.wslconfig文件,否则改完不生效。
另一个坑是路径别带中文和空格。Windows 下把项目放到“桌面/新建文件夹”这种路径,Docker 挂载目录很容易出问题,日志里报的错误有时候还很难看懂。我自己的开发目录直接放在D:\dev\ragflow,全程没踩路径问题。
最后就是性能,Windows 11 下解析大批量 PDF 的速度确实慢,适合做调试,不适合做生产环境。生产环境老老实实放 Linux 服务器。
6.2 RuoYi-Vue 去掉验证码的坑
私有化内网系统想把验证码去掉是很正常的诉求,RuoYi 的配置项是数据库参数sys.account.captchaEnabled。但很多人在改造时只改前端,把登录页的验证码组件隐藏了,结果后端还在校验验证码,登录请求带着空的 code 过去,直接报“验证码错误”。
正确的做法是前后端一起改。后端在登录逻辑里读取SysConfig的captchaEnabled,如果关闭了就跳过校验;前端登录页不再加载验证码组件。改完一定要测两件事:一是登录成功后用户信息能正常写入 Redis,二是退出登录后 Token 失效逻辑不受影响。
必须提醒一点:验证码是防暴力破解的重要防线,去掉只适合可信内网环境。一旦系统暴露在公网,强烈建议保留或者至少做登录频率限制。RuoYi 的登录日志接口也能帮我们快速发现异常尝试,别把这条道堵死。
6.3 集成排查三板斧
RuoYi 集成 RAGFlow 出问题时,我惯用的排查顺序是这样的。
先看 RuoYi 后端日志,找不到明显报错就去翻 RAGFlow 的容器日志,命令是docker logs ragflow-server -f。这两条加起来能解决大部分问题。
如果还不明确,直接用 curl 测 RAGFlow 原始接口。比如手动触发一次解析再查询状态,如果原始接口都失败,问题基本就在 RAGFlow 侧,别再折腾 RuoYi 的代码。
最后看数据库里的状态记录。RuoYi 侧维护的解析状态表和 RAGFlow 的文档状态可能不一致,这种数据对不上是集成时最常见的问题。我用一句话总结:先确保 RAGFlow 自己能完成全流程,再去查 RuoYi 的封装逻辑。
一点个人经验收尾
这套 RuoYi 加 RAGFlow 的集成做到能稳定问答之后,我最大的体会是:知识库项目真正的成败点不在大模型,而在文档解析和检索调优。模型再强,解析出来一堆乱码 chunk,回答质量也不可能好。我建议所有刚开始做私有化知识库的团队,先用真实业务文档跑一轮全链路测试,仔细看召回片段和引用来源,再逐步调解析模板、chunk 大小和提示词。如果后续有机会做第三篇,我会重点写召回阶段的具体调优:chunk 切多长、重排序加不加、混合检索怎么配。先把解析和检索的地基打牢,剩下的模型选型反而不用太焦虑。