第五章:与 Spring Boot 集成与部署
5.1 引言
在前四章中,我们已经完成了 RAG 系统的开发和封装:第一章介绍了 RAG 概念和开发环境,第二章实现了知识库构建和向量化,第三章整合了 RAG 核心逻辑,第四章使用 FastAPI 封装了 RESTful API。本章将完成整个教程的最后一步,展示如何在 Spring Boot 项目中调用 RAG API,实现前后端交互,并讨论如何部署整个系统。
本章的目标是帮助 Java 程序员实现以下内容:
- 创建一个 Spring Boot 项目,集成 RAG API。
- 实现前后端交互,展示 RAG 的查询和检索功能。
- 讨论 RAG 系统和 Spring Boot 项目的部署方案,包括本地部署和云端部署。
- 解决集成和部署中的常见问题。
我们将通过详细的代码示例和步骤,带你完成从 Spring Boot 项目配置到系统部署的完整流程。本章假设你已经按照第四章的说明运行了 FastAPI 服务(默认在 http://localhost:8000
),并熟悉 Spring Boot 和 Java Web 开发。
5.2 Spring Boot 项目配置
5.2.1 创建 Spring Boot 项目
我们将使用 Spring Initializr 创建一个新的 Spring Boot 项目,包含必要的依赖。
-
访问 Spring Initializr:
-
配置项目:
-
Project:Maven
-
Language:Java
-
Spring Boot:3.2.x(最新稳定版)
-
Group:
com.example
-
Artifact:
rag-springboot
-
Java:17
-
Dependencies:
- Spring Web
- Spring Boot DevTools
- Thymeleaf(用于前端模板)
-
-
下载项目并解压。
-
导入 IDE:
- 使用 IntelliJ IDEA 或 Eclipse 导入项目。
- 确保 Maven 依赖已正确下载。
-
项目结构:
项目目录结构如下:rag-springboot/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/ragspringboot/ │ │ │ ├── RagSpringbootApplication.java │ │ │ ├── controller/ │ │ │ ├── service/ │ │ │ ├── model/ │ │ │ ├── config/ │ │ ├── resources/ │ │ │ ├── templates/ │ │ │ ├── static/ │ │ │ ├── application.properties ├── pom.xml
5.2.2 配置 Maven 依赖
确保 pom.xml
包含以下核心依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>rag-springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rag springboot</name>
<description>Spring Boot project integrating RAG API</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
说明:
spring-boot-starter-web
:提供 REST 客户端和控制器支持。spring-boot-starter-thymeleaf
:用于渲染前端页面。jackson-databind
:处理 JSON 数据。
5.2.3 配置 application.properties
在 src/main/resources/application.properties
中配置应用的基本设置:
spring.application.name=rag springboot
server.port=8080
# RAG API 配置
rag.api.base-url=http://localhost:8000
说明:
server.port=8080
:Spring Boot 应用运行在 8080 端口,避免与 FastAPI(8000 端口)冲突。rag.api.base-url
:RAG API 的基础 URL,后续将在代码中使用。
5.3 实现 RAG API 调用
5.3.1 定义数据模型
我们需要定义 Java 类来映射 RAG API 的请求和响应,与第四章的 Pydantic 模型保持一致。
model/RagModels.java:
package com.example.ragspringboot.model;
import java.util.List;
public class RagModels {
public static class QueryRequest {
private String query;
private Integer topK;
public QueryRequest() {
}
public QueryRequest(String query, Integer topK) {
this.query = query;
this.topK = topK;
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public Integer getTopK() {
return topK;
}
public void setTopK(Integer topK) {
this.topK = topK;
}
}
public static class DocumentResponse {
private String file;
private String chunkId;
private String content;
private Double distance;
// Getters and setters
public String getFile() {
return file;
}
public void setFile(String file) {
this.file = file;
}
public String getChunkId() {
return chunkId;
}
public void setChunkId(String chunkId) {
this.chunkId = chunkId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Double getDistance() {
return distance;
}
public void setDistance(Double distance) {
this.distance = distance;
}
}
public static class QueryResponse {
private String question;
private String answer;
private List<DocumentResponse> retrievedDocs;
// Getters and setters
public String getQuestion() {
return question;
}
public void setQuestion(String question) {
this.question = question;
}
public String getAnswer() {
return answer;
}
public void setAnswer(String answer) {
this.answer = answer;
}
public List<DocumentResponse> getRetrievedDocs() {
return retrievedDocs;
}
public void setRetrievedDocs(List<DocumentResponse> retrievedDocs) {
this.retrievedDocs = retrievedDocs;
}
}
public static class RetrieveResponse {
private String query;
private List<DocumentResponse> documents;
// Getters and setters
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public List<DocumentResponse> getDocuments() {
return documents;
}
public void setDocuments(List<DocumentResponse> documents) {
this.documents = documents;
}
}
public static class UploadResponse {
private String status;
private String message;
private String fileName;
// Getters and setters
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}
}
说明:
QueryRequest
、QueryResponse
等类对应第四章的 Pydantic 模型。- 使用嵌套类组织代码,保持清晰。
5.3.2 实现服务层
服务层负责调用 RAG API,使用 Spring 的 RestTemplate
发送 HTTP 请求。
service/RagService.java:
package com.example.ragspringboot.service;
import com.example.ragspringboot.model.RagModels.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.io.File;
@Service
public class RagService {
private final RestTemplate restTemplate;
private final String ragApiBaseUrl;
public RagService(RestTemplate restTemplate, @Value("${rag.api.base-url}") String ragApiBaseUrl) {
this.restTemplate = restTemplate;
this.ragApiBaseUrl = ragApiBaseUrl;
}
public QueryResponse query(String query, Integer topK) {
QueryRequest request = new QueryRequest(query, topK);
HttpEntity<QueryRequest> entity = new HttpEntity<>(request, createHeaders());
ResponseEntity<QueryResponse> response = restTemplate.exchange(
ragApiBaseUrl + "/query",
HttpMethod.POST,
entity,
QueryResponse.class
);
return response.getBody();
}
public RetrieveResponse retrieve(String query, Integer topK) {
QueryRequest request = new QueryRequest(query, topK);
HttpEntity<QueryRequest> entity = new HttpEntity<>(request, createHeaders());
ResponseEntity<RetrieveResponse> response = restTemplate.exchange(
ragApiBaseUrl + "/retrieve",
HttpMethod.POST,
entity,
RetrieveResponse.class
);
return response.getBody();
}
public UploadResponse uploadFile(File file) {
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", new FileSystemResource(file));
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, createHeaders());
ResponseEntity<UploadResponse> response = restTemplate.exchange(
ragApiBaseUrl + "/upload",
HttpMethod.POST,
entity,
UploadResponse.class
);
return response.getBody();
}
public String healthCheck() {
ResponseEntity<String> response = restTemplate.getForEntity(
ragApiBaseUrl + "/health",
String.class
);
return response.getBody();
}
private HttpHeaders createHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
}
说明:
RestTemplate
:Spring 提供的 HTTP 客户端,用于调用 RAG API。query
和retrieve
方法:发送 POST 请求,调用/query
和/retrieve
端点。uploadFile
:发送 multipart/form-data 请求,调用/upload
端点。healthCheck
:调用/health
端点,检查 API 状态。
5.3.3 配置 RestTemplate
为了确保 RestTemplate
可用,我们在配置类中定义其 Bean。
config/AppConfig.java:
package com.example.ragspringboot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
5.4 实现前端交互
我们使用 Thymeleaf 模板创建一个简单的 Web 界面,允许用户输入查询、查看结果和上传文档。
5.4.1 创建控制器
controller/RagController.java:
package com.example.ragspringboot.controller;
import com.example.ragspringboot.model.RagModels.*;
import com.example.ragspringboot.service.RagService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@Controller
public class RagController {
private final RagService ragService;
public RagController(RagService ragService) {
this.ragService = ragService;
}
@GetMapping("/")
public String index(Model model) {
model.addAttribute("queryRequest", new QueryRequest());
return "index";
}
@PostMapping("/query")
public String query(@ModelAttribute QueryRequest queryRequest, Model model) {
QueryResponse response = ragService.query(queryRequest.getQuery(), queryRequest.getTopK());
model.addAttribute("queryResponse", response);
model.addAttribute("queryRequest", queryRequest);
return "index";
}
@PostMapping("/retrieve")
public String retrieve(@ModelAttribute QueryRequest queryRequest, Model model) {
RetrieveResponse response = ragService.retrieve(queryRequest.getQuery(), queryRequest.getTopK());
model.addAttribute("retrieveResponse", response);
model.addAttribute("queryRequest", queryRequest);
return "index";
}
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file, Model model) {
try {
File tempFile = File.createTempFile("upload-", file.getOriginalFilename());
file.transferTo(tempFile);
UploadResponse response = ragService.uploadFile(tempFile);
model.addAttribute("uploadResponse", response);
tempFile.delete();
} catch (IOException e) {
model.addAttribute("uploadResponse", new UploadResponse("error", "File upload failed: " + e.getMessage(), null));
}
model.addAttribute("queryRequest", new QueryRequest());
return "index";
}
}
说明:
/
:渲染主页面,显示查询表单。/query
:处理查询请求,显示 RAG 回答和检索文档。/retrieve
:处理检索请求,仅显示检索文档。/upload
:处理文件上传,显示上传结果。
5.4.2 创建 Thymeleaf 模板
resources/templates/index.html:
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>RAG 查询系统</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.form-group { margin-bottom: 15px; }
.result { margin-top: 20px; padding: 10px; border: 1px solid #ddd; }
.error { color: red; }
</style>
</head>
<body>
<h1>RAG 查询系统</h1>
<!-- 查询表单 -->
<form th:action="@{/query}" th:object="${queryRequest}" method="post" class="form-group">
<label for="query">查询内容:</label>
<input type="text" id="query" th:field="*{query}" required style="width: 300px;">
<label for="topK">Top K:</label>
<input type="number" id="topK" th:field="*{topK}" value="3" min="1" max="10">
<button type="submit">查询</button>
<button type="button" onclick="document.forms[0].action='/retrieve'; document.forms[0].submit();">仅检索</button>
</form>
<!-- 文件上传表单 -->
<form th:action="@{/upload}" method="post" enctype="multipart/form-data" class="form-group">
<label for="file">上传文档:</label>
<input type="file" id="file" name="file" accept=".txt,.pdf,.md">
<button type="submit">上传</button>
</form>
<!-- 查询结果 -->
<div th:if="${queryResponse}" class="result">
<h3>查询结果</h3>
<p><strong>问题:</strong> <span th:text="${queryResponse.question}"></span></p>
<p><strong>回答:</strong> <span th:text="${queryResponse.answer}"></span></p>
<h4>检索到的文档:</h4>
<ul>
<li th:each="doc : ${queryResponse.retrievedDocs}">
<strong>文件:</strong> <span th:text="${doc.file}"></span><br>
<strong>片段 ID:</strong> <span th:text="${doc.chunkId}"></span><br>
<strong>内容:</strong> <span th:text="${doc.content}"></span><br>
<strong>距离:</strong> <span th:text="${doc.distance}"></span>
</li>
</ul>
</div>
<!-- 检索结果 -->
<div th:if="${retrieveResponse}" class="result">
<h3>检索结果</h3>
<p><strong>查询:</strong> <span th:text="${retrieveResponse.query}"></span></p>
<h4>检索到的文档:</h4>
<ul>
<li th:each="doc : ${retrieveResponse.documents}">
<strong>文件:</strong> <span th:text="${doc.file}"></span><br>
<strong>片段 ID:</strong> <span th:text="${doc.chunkId}"></span><br>
<strong>内容:</strong> <span th:text="${doc.content}"></span><br>
<strong>距离:</strong> <span th:text="${doc.distance}"></span>
</li>
</ul>
</div>
<!-- 上传结果 -->
<div th:if="${uploadResponse}" class="result">
<h3>上传结果</h3>
<p th:class="${uploadResponse.status == 'error'} ? 'error'"><strong>状态:</strong> <span th:text="${uploadResponse.status}"></span></p>
<p><strong>消息:</strong> <span th:text="${uploadResponse.message}"></span></p>
<p th:if="${uploadResponse.fileName}"><strong>文件名:</strong> <span th:text="${uploadResponse.fileName}"></span></p>
</div>
</body>
</html>
说明:
- 提供查询、检索和文件上传表单。
- 使用 Thymeleaf 动态渲染查询结果、检索结果和上传状态。
- 包含简单的 CSS 样式,改善用户体验。
5.5 测试前后端交互
5.5.1 启动服务
-
启动 FastAPI 服务:
在rag_api
目录下运行:uvicorn app.main:app --host 0.0.0.0 --port 8000
-
启动 Spring Boot 应用:
在rag springboot
目录下运行:mvn spring-boot:run
-
访问 Web 界面:
打开浏览器,访问http://localhost:8080
。
5.5.2 测试流程
-
查询测试:
- 输入查询:“Which operating systems are supported by the product?”
- 设置 Top K 为 3。
- 提交查询,页面显示回答和检索到的文档。
-
检索测试:
- 输入查询:“How to contact technical support?”
- 点击“仅检索”,页面显示相关文档。
-
上传测试:
- 上传一个新的
.txt
文件(如包含“Test content”)。 - 页面显示上传成功的消息。
- 再次查询,确认新文档内容已生效。
- 上传一个新的
示例结果:
-
查询结果:
- 问题:Which operating systems are supported by the product?
- 回答:Our product supports Windows 10/11, macOS 12+, and Ubuntu 20.04+.
- 检索文档:包含 FAQ 文件的相关片段。
-
上传结果:
- 状态:success
- 消息:Document added successfully.
- 文件名:test_doc.txt
5.6 部署方案
5.6.1 本地部署
步骤:
-
打包 FastAPI 应用:
-
确保
rag_api
目录包含所有依赖和知识库文件。 -
使用 Gunicorn 替换 Uvicorn 提高生产环境性能:
pip install gunicorn gunicorn -w 4 -k uvicorn.workers.UvicornWorker app.main:app --bind 0.0.0.0:8000
-
-
打包 Spring Boot 应用:
-
在
rag springboot
目录下运行:mvn clean package
-
运行生成的 JAR 文件:
java -jar target/rag springboot-0.0.1-SNAPSHOT.jar
-
-
配置 Nginx 反向代理(可选):
-
安装 Nginx:
sudo apt-get install nginx
-
配置
/etc/nginx/sites-available/rag
:server { listen 80; server_name your-domain.com; location / { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /api/ { proxy_pass http://localhost:8000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
-
启用配置:
sudo ln -s /etc/nginx/sites-available/rag /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl restart nginx
-
说明:
- FastAPI 运行在 8000 端口,Spring Boot 运行在 8080 端口。
- Nginx 作为反向代理,统一入口,
/api/
路由到 FastAPI,其他请求路由到 Spring Boot。
5.6.2 云端部署
云平台选择:
- AWS:使用 EC2 实例部署 FastAPI 和 Spring Boot,Elastic Beanstalk 简化应用管理。
- Google Cloud:使用 Compute Engine 或 Cloud Run。
- Azure:使用 Azure App Service 或 Virtual Machines。
AWS EC2 部署示例:
-
启动 EC2 实例:
- 选择 Ubuntu 22.04,推荐 t3.medium(2 vCPU,4GB 内存)。
- 配置安全组,开放 80(HTTP)、8000(FastAPI)、8080(Spring Boot)端口。
-
安装依赖:
sudo apt-get update sudo apt-get install python3-pip nginx openjdk-17-jdk maven pip3 install gunicorn uvicorn fastapi
-
部署 FastAPI:
-
上传
rag_api
目录到 EC2。 -
安装 Python 依赖:
pip3 install -r requirements.txt
-
使用 Gunicorn 运行:
gunicorn -w 4 -k uvicorn.workers.UvicornWorker app.main:app --bind 0.0.0.0:8000 --daemon
-
-
部署 Spring Boot:
-
上传
rag springboot
目录。 -
构建并运行:
mvn clean package java -jar target/rag springboot-0.0.1-SNAPSHOT.jar &
-
-
配置 Nginx:
- 参考本地部署的 Nginx 配置。
- 更新
application.properties
中的rag.api.base-url
为 EC2 的公网 IP 或域名。
-
访问应用:
- 通过 EC2 公网 IP 或绑定的域名访问
http://<your-ec2-ip>
。
- 通过 EC2 公网 IP 或绑定的域名访问
优化建议:
- 使用 Docker 容器化 FastAPI 和 Spring Boot,简化部署。
- 配置 Auto Scaling 和 Load Balancer,支持高并发。
- 使用 RDS 或 S3 存储知识库和 FAISS 索引。
5.6.3 Docker 部署
Dockerfile for FastAPI(rag_api/Dockerfile
):
FROM python:3.10-slim
WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 8000
CMD ["gunicorn", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "app.main:app", "--bind", "0.0.0.0:8000"]
Dockerfile for Spring Boot(rag springboot/Dockerfile
):
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/rag springboot-0.0.1-SNAPSHOT.jar /app/app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
docker-compose.yml(根目录):
version: '3.8'
services:
rag-api:
build: ./rag_api
ports:
- "8000:8000"
volumes:
- ./rag_api/rag_knowledge_base:/app/rag_knowledge_base
- ./rag_api/rag_knowledge_base_index.faiss:/app/rag_knowledge_base_index.faiss
environment:
- PYTHONUNBUFFERED=1
rag springboot:
build: ./rag springboot
ports:
- "8080:8080"
environment:
- rag.api.base-url=http://rag-api:8000
depends_on:
- rag-api
运行:
docker-compose up --build
说明:
docker-compose
同时启动 FastAPI 和 Spring Boot 容器。- FastAPI 容器挂载知识库和索引文件,确保数据持久化。
- Spring Boot 通过容器网络访问 FastAPI(
http://rag-api:8000
)。
5.7 常见问题与解决方案
-
API 调用失败:
-
问题:Spring Boot 无法连接到 FastAPI。
-
解决方案:
- 检查
application.properties
中的rag.api.base-url
。 - 确保 FastAPI 服务在运行(
curl http://localhost:8000/health
)。 - 检查防火墙或安全组设置,开放 8000 端口。
- 检查
-
-
文件上传失败:
-
问题:上传文档时返回 500 错误。
-
解决方案:
- 检查 FastAPI 日志(
rag_api.log
)。 - 确保上传文件格式正确(
.txt
、.pdf
、.md
)。 - 增加 FastAPI 的超时时间(参考第四章)。
- 检查 FastAPI 日志(
-
-
前端页面显示异常:
-
问题:Thymeleaf 模板未正确渲染。
-
解决方案:
- 检查
index.html
中的 Thymeleaf 语法。 - 确保控制器正确设置了 Model 属性。
- 检查
-
-
部署性能问题:
-
问题:云端部署后响应缓慢。
-
解决方案:
- 升级 EC2 实例类型(如 t3.large)。
- 使用 Redis 缓存频繁查询(参考第四章)。
- 优化 FAISS 索引(使用
IndexIVFFlat
)。
-
5.8 本章总结
本章完成了 RAG 系统与 Spring Boot 的集成和部署,涵盖了以下内容:
- 创建 Spring Boot 项目,配置 REST 客户端调用 RAG API。
- 实现前后端交互,使用 Thymeleaf 渲染查询、检索和上传结果。
- 提供本地和云端部署方案,包括 Nginx 反向代理和 Docker 容器化。
- 解决集成和部署中的常见问题,如 API 连接、文件上传和性能优化。
通过本教程,你已经掌握了从零开始构建一个 RAG 系统并将其集成到 Spring Boot 项目的完整流程。你可以根据实际需求扩展功能,例如添加用户认证、支持多语言或优化知识库管理。