基于 Spring Boot 2.6 + LangChain4j 0.35 构建的企业级 RAG(Retrieval-Augmented Generation)问答系统,支持多轮对话、多源文档知识库、语义检索重排序、飞书知识库自动同步,以及多实例水平扩展。
⚠ JDK 8 兼容说明: LangChain4j 从 0.36.0 起要求 JDK 17,本项目使用最后支持 JDK 8 的 0.35.0。因此 Chroma 服务端锁定为 0.4.24(0.6.x+ API 不兼容),Milvus 使用 2.3.x。如需升级新版,需同时升级 JDK 17 + Spring Boot 3.x。
┌──────────────────────────────────────────────────────────────┐
│ Web UI (index.html) │
│ SSE 流式渲染 · Markdown 解析 · 会话管理 · 知识库面板 │
│ 深色/亮色主题 · 移动端适配 · 消息复制 · 自定义提示框 │
│ 统一 Results JSON 解析 · SSE error 事件处理 │
└───────────────────────┬──────────────────────────────────────┘
│ POST /api/chat {sessionId, question}
▼
┌──────────────────────────────────────────────────────────────┐
│ RateLimitAspect (@RateLimit) │
│ Redisson 令牌桶(IP 30次/分 + 每日 10,000 次) │
│ 429 → 统一 Results 格式返回 │
└───────────────────────┬──────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ ChatController │
│ SseEmitter (120s timeout) · Jackson 序列化 │
└───────────────────────┬──────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ RAG Pipeline │
│ │
│ ┌──────────────┐ ┌───────────────┐ ┌─────────────────┐ │
│ │ 会话历史 │──▶│ Query │──▶│ Embedding │ │
│ │ Redis │ │ Rewriting │ │ BGE-large-zh │ │
│ └──────────────┘ └───────────────┘ └───────┬─────────┘ │
│ ▼ │
│ ┌────────────┐ │
│ │ Vector │ │
│ │ Store │ │
│ │ top 20 │ │
│ └─────┬──────┘ │
│ ▼ │
│ ┌──────────────┐ ┌───────────────┐ ┌─────────────────┐ │
│ │ LLM 回答 │◀──│ Prompt 组装 │◀──│ Rerank │ │
│ │ DeepSeek / │ │ 引用 + 约束 │ │ BGE-reranker │ │
│ │ OpenAI 兼容 │ │ │ │ top 5 │ │
│ └──────────────┘ └───────────────┘ └─────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 文档来源 │ │
│ │ ├─ Web 上传(Multipart 文件上传,SHA256 去重) │ │
│ │ ├─ 目录扫描(data/docs/ 自动扫描) │ │
│ │ └─ 飞书知识库同步(Feishu Wiki Sync) │ │
│ └───────────────┬───────────────────────────────────────┘ │
│ │ 元数据 │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ MySQL + MyBatis-Plus │ │
│ │ documents(文档元数据) · document_chunks(分块映射) │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
┌────────────┐
│ Redis │ ← 多实例共享会话 + 分布式限流计数器
└────────────┘
- Docker(推荐)或 JDK 8+(本地运行)
- API Key:Chat 模型 + Embedding 模型的密钥
默认使用 DeepSeek 作为对话模型、SiliconFlow 作为 Embedding/Rerank 服务商。 可通过环境变量切换任意 OpenAI 兼容 API,参考 LLM 配置。
# 1. 复制环境变量模板并填入密钥
cp .env.example .env
# 2. 选择向量库并启动
# InMemory(零外部依赖,重启数据丢失)
docker compose up -d
# 或 Chroma(持久化,中型项目)
docker compose -f docker-compose-chroma.yml up -d
# 或 Milvus(分布式,生产级)
docker compose -f docker-compose-milvus.yml up -d
# 3. 查看日志
docker compose logs -f
# 4. 访问 http://localhost:8080Docker Compose 会同时启动以下服务:
| 服务 | 镜像 | 说明 |
|---|---|---|
| app | 本地构建 | Spring Boot 应用,端口 8080 |
| mysql | mysql:8.0 | 文档元数据存储,首次启动自动建表 |
| redis | redis:7-alpine | 多实例对话上下文共享 |
| chroma / milvus | — | 向量数据库(按所选 compose 文件) |
# 1. 确保 MySQL 和 Redis 服务已启动
# 2. 创建数据库并初始化表结构
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS rag_study_helper;"
mysql -u root -p rag_study_helper < init.sql
# 3. 编辑 application.yml 或通过环境变量传入 API Key
# 4. 运行
mvn spring-boot:run
# 5. 访问 http://localhost:8080所有配置均可通过环境变量覆盖。复制 .env.example 为 .env 并填入密钥:
cp .env.example .env核心变量:
| 环境变量 | 说明 | 默认值 |
|---|---|---|
APP_RAG_CHAT_API_KEY |
Chat 模型 API Key | — |
APP_RAG_EMBEDDING_API_KEY |
Embedding 模型 API Key | — |
APP_RAG_RERANK_API_KEY |
Rerank API Key(不设则复用 Embedding Key) | — |
APP_RATE_LIMIT_IP_RATE |
IP 令牌桶容量及补充速率(次/分钟) | 20 |
APP_RATE_LIMIT_DAILY_MAX |
全局每日调用上限 | 10000 |
MYSQL_ROOT_PASSWORD |
MySQL 密码 | root |
SPRING_REDIS_HOST |
Redis 地址 | redis |
.env文件已加入.gitignore,不会提交到代码仓库。
系统支持切换任意 OpenAI 兼容 API 的模型,无需修改代码。
# 切换 Chat 模型到 GPT-4o
APP_RAG_CHAT_API_KEY=sk-openai-xxx
APP_RAG_CHAT_BASE_URL=https://api.openai.com/v1
APP_RAG_CHAT_MODEL_NAME=gpt-4o
# 切换 Embedding 模型
APP_RAG_EMBEDDING_API_KEY=sk-xxx
APP_RAG_EMBEDDING_BASE_URL=https://api.siliconflow.cn/v1
APP_RAG_EMBEDDING_MODEL_NAME=BAAI/bge-large-zh-v1.5
# 切换 Rerank 模型
APP_RAG_RERANK_BASE_URL=https://api.siliconflow.cn/v1
APP_RAG_RERANK_MODEL_NAME=BAAI/bge-reranker-v2-m3| 服务商 | Chat Base URL | Embedding / Rerank |
|---|---|---|
| DeepSeek | https://api.deepseek.com |
— |
| OpenAI | https://api.openai.com/v1 |
text-embedding-3-small |
| SiliconFlow | https://api.siliconflow.cn/v1 |
BAAI/bge-large-zh-v1.5 |
| 阿里百炼 | https://dashscope.aliyuncs.com/compatible-mode/v1 |
text-embedding-v2 |
| 智谱 GLM | https://open.bigmodel.cn/api/paas/v4 |
embedding-2 |
非 OpenAI 兼容协议(如 Anthropic Claude、Google Gemini)需要额外的 LangChain4j 依赖和配置 Bean。
系统支持三种向量存储方案,通过 docker-compose 文件切换。
| 方案 | 文件 | 适用场景 |
|---|---|---|
| InMemory | docker-compose.yml |
开发调试,零外部依赖 |
| Chroma | docker-compose-chroma.yml |
中型项目,持久化存储 |
| Milvus | docker-compose-milvus.yml |
大型/生产,分布式 |
Chroma 服务端锁定为 0.4.24(与 langchain4j 0.35.x 兼容,0.6.x+ API 有 breaking change):
docker compose -f docker-compose-chroma.yml up -dMilvus 依赖 etcd(元数据存储)和 MinIO(数据持久化),首次启动需等待 1-2 分钟:
docker compose -f docker-compose-milvus.yml up -d
# 检查 Milvus 是否就绪
docker compose -f docker-compose-milvus.yml ps维度配置:
application.yml中milvus.dimension必须与 Embedding 模型匹配。当前使用BAAI/bge-large-zh-v1.5(1024 维),如切换模型(如 OpenAItext-embedding-3-small为 1536 维)需同步修改。更改维度后需重建集合(down -v清数据卷或换collection-name)。
| 方式 | 说明 | 去重策略 |
|---|---|---|
| Web 上传 | 知识库面板拖拽或选择文件上传 | 内容 SHA256 哈希 |
| 目录扫描 | 将文档放入 data/docs/,点击「扫描目录」 |
内容 SHA256 哈希 |
| 飞书同步 | 配置飞书应用后自动同步知识库文档 | nodeToken + updateTime |
接收文档 → 去重检查(查询 documents 表)
├─ 已存在 → 跳过,返回已有记录
└─ 不存在 → 解析 → 分块(300 字符/块,60 字符重叠)
→ 批量 Embedding(10 条/批)
→ 写入向量库 → 捕获 vectorId
→ INSERT documents + INSERT document_chunks
| 格式 | 解析方式 |
|---|---|
| Apache PDFBox | |
| TXT / MD / CSV / JSON / XML | 文本解析 |
| XLSX / XLS | Apache POI |
| DOCX | Apache POI |
| PPTX | Apache POI |
| HTML / HTM | JSoup |
- 上传文档重复上传:SHA256 哈希一致则跳过
- 飞书文档更新:自动删除旧向量,重新入库并更新 MySQL 记录
- 飞书文档远程删除:定时同步时自动清理本地对应的向量和记录
- API 删除:
DELETE /api/documents/{id}同步删除向量库和 MySQL 数据
在 飞书开放平台 创建企业自建应用,添加以下权限并发布:
| 权限 | 用途 | 必需 |
|---|---|---|
wiki:wiki:readonly |
遍历知识库 | ✅ |
docx:document:readonly |
读取文档内容 | ✅ |
sheets:sheet:readonly |
读取电子表格 | 可选 |
bitable:app:readonly |
读取多维表格 | 可选 |
发布后,将应用添加到知识库的成员中,赋予「阅读」权限。
app:
feishu:
app-id: cli_xxx
app-secret: xxx
space-id: 7327625190885801988
sync-enabled: true
cron: 0 0 */12 * * ?或通过环境变量:
APP_FEISHU_APP_ID=cli_xxx
APP_FEISHU_APP_SECRET=xxx
APP_FEISHU_SPACE_ID=7327625190885801988
APP_FEISHU_SYNC_ENABLED=true| 飞书对象类型 | 支持 | 说明 |
|---|---|---|
| 文档(doc/docx) | ✅ | 同步为 Markdown 入库 |
| 电子表格(sheet) | ✅ | 逐工作表读取,转为 Markdown 表格 |
| 多维表格(bitable) | ✅ | 逐表逐记录读取字段值 |
| 思维导图(mindnote) | ❌ | 飞书 API 无稳定导出接口 |
增量同步: 通过 feishu_update_time 对比判断文档是否变更,仅同步有变动的文档。
文档更新: 文档在飞书侧修改后,自动删除旧向量、清除 MySQL 中的旧分块映射,重新入库。
反向删除: 同步时对比飞书远程节点列表,自动清理本地已不存在的文档及其向量数据。
---
## API 参考
所有接口统一返回 `Results<T>` 格式,前端通过 `resCode` 判断状态、`msg` 显示提示、`obj` 获取数据。
```json
{
"resCode": "200",
"msg": "成功",
"obj": { ... }
}
| resCode | 含义 |
|---|---|
200 |
成功 |
400 |
参数错误 |
404 |
资源不存在 |
429 |
请求过于频繁(限流) |
500 |
服务器内部错误 |
| 接口 | 方法 | 请求格式 | 返回格式 | 说明 |
|---|---|---|---|---|
/api/chat |
POST | {"sessionId","question"} |
SSE text/event-stream |
流式问答,event:error 时数据为 Results 格式 |
/api/documents/upload |
POST | multipart/form-data |
Results<DocumentInfo> |
上传文档 |
/api/documents |
GET | — | Results<List<String>> |
已入库文档列表 |
/api/documents/scan |
POST | — | Results<List<DocumentInfo>> |
扫描 data/docs/ 目录 |
/api/documents/{id} |
DELETE | — | Results<Void> |
删除文档及其向量数据 |
/api/chat 使用 Server-Sent Events,数据格式:
event: message
data: {"token":"逐","token":"步","token":"输","token":"出"}
event: message
data: {"token":"完"}
data: [DONE]
错误事件:
event: error
data: {"resCode":"500","msg":"流式处理失败: Redis 连接异常"}
data: [DONE]
/api/chat 接口受分布式限流保护,防止滥用导致 LLM 调用超支。
| 层级 | 方式 | 参数 | 目的 |
|---|---|---|---|
| IP 令牌桶 | Redisson RRateLimiter(分布式) |
容量 20,补充 20次/分 | 控制单 IP 请求速率 |
| 全局每日计数 | Redis INCR + EXPIRE | 10,000 次/天 | 成本兜底 |
使用 @RateLimit 注解 + AOP 切面:
@RateLimit
@PostMapping(value = "/api/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter chat(@RequestBody ChatRequest request) {
// ...
}- 注解标记需要限流的方法
RateLimitAspect环绕通知在方法执行前检查令牌桶和每日计数- 超限时返回
resCode=429的ResultsJSON,前端统一展示提示
热更新设计: 限流参数编码进 Redis Key(如 rl:ip:192.168.1.1:20),
配置变更后自动使用新 Key,旧 Key 随 24h TTL 过期,无需重启或重建即可生效。
app:
rate-limit:
ip-rate: 20 # IP 令牌桶容量及补充速率(次/分钟)
daily-max: 10000 # 每日调用上限所有参数支持环境变量覆盖(APP_RATE_LIMIT_IP_RATE 等)。
当 Redis 不可用时(如测试环境),RateLimitAspect 自动跳过限流逻辑,
不影响业务正常调用。
src/main/java/com/rag/studyhelper/
├── controller/
│ ├── ChatController.java # SSE 流式问答接口
│ └── DocumentController.java # 文档管理接口
├── service/
│ ├── RagQueryService.java # RAG 核心流程编排
│ ├── DocumentIngestionService.java # 文档解析、去重、向量化与 MySQL 持久化
│ ├── RedisConversationStore.java # Redis 会话存储(多实例共享)
│ ├── ConversationStore.java # 会话存储接口
│ ├── RateLimitService.java # 分布式限流(Redisson 令牌桶 + 每日计数)
│ ├── RerankService.java # SiliconFlow Rerank 调用
│ └── QueryRewriteService.java # 多轮查询改写
├── config/
│ ├── LangChain4jConfig.java # LLM / Embedding / VectorStore 配置
│ ├── MyBatisPlusConfig.java # MyBatis-Plus 分页插件
│ ├── MyMetaObjectHandler.java # 自动填充创建/更新时间
│ ├── GlobalExceptionHandler.java # @RestControllerAdvice 统一异常处理
│ ├── RedissonConfig.java # Redisson 客户端(复用 spring.redis.*)
│ ├── RateLimit.java # @RateLimit 限流注解
│ └── RateLimitAspect.java # 限流切面(AOP 环绕通知)
├── utils/
│ └── Results.java # 统一 API 响应体(resCode / msg / obj)
├── mapper/
│ ├── DocumentsMapper.java # 文档元数据 Mapper
│ └── DocumentChunksMapper.java # 文档分块映射 Mapper
├── model/
│ ├── ChatRequest.java
│ ├── ChatMessage.java
│ ├── DocumentInfo.java
│ ├── Documents.java # documents 表实体
│ └── DocumentChunks.java # document_chunks 表实体
├── feishu/
│ ├── client/
│ │ ├── FeishuClient.java # 飞书 API 封装(文档/表格/多维表格)
│ │ └── WikiNode.java # 知识库节点模型
│ ├── service/
│ │ └── FeishuSyncService.java # 同步编排 + @Scheduled 定时任务
│ └── config/
│ ├── FeishuProperties.java # 配置绑定
│ └── FeishuConfig.java # Spring Bean 装配
└── RagStudyHelperApplication.java
src/main/resources/
├── application.yml # 主配置
└── static/index.html # 前端页面
项目根目录/
├── init.sql # MySQL DDL(首次启动自动执行)
src/test/java/com/rag/studyhelper/
├── config/
│ ├── GlobalExceptionHandlerTest.java # 异常处理单元测试
│ └── ExceptionTestController.java # 测试辅助 Controller
├── controller/
│ └── ChatControllerIntegrationTest.java
├── service/
│ ├── DocumentSplitterTest.java
│ ├── RagQueryServiceTest.java
│ └── RerankServiceTest.java
├── feishu/
│ ├── client/FeishuClientTest.java
│ └── service/FeishuSyncServiceTest.java
└── RagStudyHelperApplicationTests.java
# 运行全部单元测试(排除集成测试)
mvn test
# 手动运行集成测试(调用真实 LLM,需配置 API Key)
mvn test -Dtest=ChatControllerIntegrationTest| 测试类 | 用例数 | 覆盖范围 |
|---|---|---|
GlobalExceptionHandlerTest |
3 | 400/404/500 全局异常处理 |
RagQueryServiceTest |
4 | RAG 流程编排、Rerank 调用、Prompt 组装 |
RerankServiceTest |
3 | SiliconFlow Rerank 调用与降级 |
DocumentSplitterTest |
2 | 文档分块逻辑 |
FeishuClientTest |
5 | 飞书 API 调用 |
FeishuSyncServiceTest |
6 | 增量同步、文档更新、反向删除 |
ChatControllerIntegrationTest |
1 | SSE 流式集成测试(手动运行) |
| 组件 | 技术选型 | 说明 |
|---|---|---|
| 框架 | Spring Boot 2.6.13 | Java 8,稳定生产版本 |
| AI 编排 | LangChain4j 0.35.0 | 最后支持 JDK 8 的版本系列 |
| 对话模型 | OpenAI 兼容 API | 默认 DeepSeek,可切换 GPT / GLM 等 |
| Embedding | BAAI/bge-large-zh-v1.5 | 中文优化,1024 维 |
| Rerank | BAAI/bge-reranker-v2-m3 | 交叉编码器重排序 |
| 向量存储 | InMemory / Chroma 0.4.24 / Milvus 2.3.3 | 配置切换,适应不同规模 |
| 会话缓存 | Redis | 多实例共享,TTL 自动过期 |
| 文档元数据 | MySQL 8.0 + MyBatis-Plus 3.5.2 | 入库去重、文档管理、分块映射 |
| 文档解析 | Apache POI 5.1.0 + JSoup + PDFBox | Excel / Word / PPT / HTML / PDF |
| 定时调度 | Spring @Scheduled | 飞书知识库定期同步 |
| 构建 | Maven | Surefire 排除集成测试 |
| 统一响应 | Results<T> + GlobalExceptionHandler |
所有同步 API 返回 resCode/msg/obj 格式,SSE 错误走 event:error 通道 |
| 限流 | @RateLimit + AOP + Redisson RRateLimiter |
分布式令牌桶,多实例共享;注解式声明,无 Redis 时自动降级 |
| 分布式工具 | Redisson 3.24.3 | 限流令牌桶,低配连接池避免资源竞争 |
MIT
- LINUX DO —— 新的理想型社区,技术爱好者的聚集地。