概述
本文档详细说明了如何将Markdown文件上传、切片并存储到Milvus向量数据库的完整流程,包括所有关键代码节点和配置说明。
系统架构
用户上传MD文件↓
DocumentController (接收请求)↓
DocumentService (业务处理)↓
TokenTextSplitter (文档切片)↓
EmbeddingModel (向量化) ← DashScope API↓
MilvusVectorStore (存储)↓
Milvus数据库
技术栈
| 组件 | 版本 | 说明 |
|---|---|---|
| Spring Boot | 3.4.3 | 应用框架 |
| Spring AI | 1.0.2 | AI集成框架 |
| Milvus | 2.4.8 | 向量数据库 |
| DashScope | text-embedding-v3 | 阿里云Embedding服务 |
核心配置
1. application.yml
spring:ai:openai:# DashScope API密钥api-key: ${OPENAI_API_KEY:sk-your-api-key}# 基础URL(不含/v1,会自动添加)base-url: ${OPENAI_API_BASE:https://dashscope.aliyuncs.com/compatible-mode}embedding:enabled: trueoptions:model: text-embedding-v3 # 使用v3模型milvus:enabled: truehost: 10.0.0.15port: 19530
关键点:
base-url末尾不要包含/v1,OpenAiApi会自动添加- 使用
text-embedding-v3模型,支持1024维度
2. pom.xml依赖
<!-- Spring AI OpenAI -->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai</artifactId><version>1.0.2</version>
</dependency><!-- Spring AI Milvus -->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-milvus-store</artifactId><version>1.0.2</version>
</dependency><!-- Milvus SDK -->
<dependency><groupId>io.milvus</groupId><artifactId>milvus-sdk-java</artifactId><version>2.4.8</version>
</dependency>
MilvusConfig配置类详解
配置类的作用
MilvusConfig是一个配置工厂类,在Spring Boot启动时自动执行,负责创建和配置所有需要的工具对象。
执行时机:
Spring Boot启动↓
扫描@Configuration类↓
发现MilvusConfig↓
检查条件: milvus.enabled=true?↓ (是)
按顺序执行@Bean方法,创建对象↓
将创建的对象放入Spring容器↓
其他类可以通过@Autowired使用这些对象
四个核心Bean及其依赖关系
MilvusConfig (配置类)│├─ ① milvusClient (Milvus数据库连接)│ └─ 连接到10.0.0.15:19530│├─ ② embeddingModel (文本向量化工具)│ └─ 调用DashScope API将文本转为1024维向量│├─ ③ vectorStore (向量存储管理器)│ ├─ 依赖: milvusClient (用于存储数据)│ └─ 依赖: embeddingModel (用于生成向量)│└─ ④ textSplitter (文档切片工具)└─ 将长文档切分成300 token的小块DocumentServiceImpl (使用者)├─ 注入 vectorStore ← 来自MilvusConfig└─ 注入 textSplitter ← 来自MilvusConfig
代码实现流程
节点1: 接收上传请求
文件: DocumentController.java
@PostMapping("/import")
public ResponseEntity<Map<String, Object>> importMarkdownFile(@RequestParam("file") MultipartFile file) {try {// 1. 验证文件if (file.isEmpty()) {throw new IllegalArgumentException("上传的文件为空");}String fileName = file.getOriginalFilename();log.info("正在导入Markdown文件: {}", fileName);// 2. 验证文件类型if (fileName != null && !fileName.toLowerCase().endsWith(".md")) {throw new IllegalArgumentException("只支持.md格式的Markdown文件");}// 3. 读取文件内容String content = new String(file.getBytes(), StandardCharsets.UTF_8);// 4. 调用服务层处理: 切片 → 向量化 → 存储int chunksImported = documentService.importMarkdownContent(content, fileName);// 5. 返回成功响应Map<String, Object> response = new HashMap<>();response.put("success", true);response.put("message", "Markdown文件导入成功");response.put("chunksImported", chunksImported);response.put("fileName", fileName);return ResponseEntity.ok(response);} catch (Exception e) {log.error("导入Markdown文件出错", e);Map<String, Object> response = new HashMap<>();response.put("success", false);response.put("message", "导入失败: " + e.getMessage());return ResponseEntity.badRequest().body(response);}
}
作用:
- 接收前端上传的Markdown文件
- 验证文件格式和内容
- 读取文件内容并转换为UTF-8字符串
- 调用服务层处理
- 返回导入结果(包含成功导入的块数量)
节点2: Bean① - 创建Milvus数据库连接
文件: MilvusConfig.java
@Bean
@ConditionalOnProperty(name = "milvus.enabled", havingValue = "true")
public MilvusServiceClient milvusClient() {log.info("正在连接Milvus服务器: {}:{}", milvusHost, milvusPort);MilvusServiceClient client = new MilvusServiceClient(ConnectParam.newBuilder().withHost(milvusHost) // 10.0.0.15.withPort(milvusPort) // 19530.build());log.info("Milvus客户端连接成功");return client;
}
作用:
- 创建Milvus数据库的连接客户端
- 类比: 就像JDBC连接MySQL数据库
- 连接到
10.0.0.15:19530
何时使用:
vectorStore内部使用这个客户端来操作Milvus数据库- 执行插入、查询等数据库操作
实际调用:
// vectorStore内部会这样使用:
milvusClient.insert("zhi_yan", vector, metadata); // 插入向量数据
节点3: Bean② - 创建文本向量化工具
文件: MilvusConfig.java
@Bean
@ConditionalOnProperty(name = "milvus.enabled", havingValue = "true")
public EmbeddingModel embeddingModel() {log.info("正在创建OpenAI EmbeddingModel,Base URL: {}, 模型: {}", openaiBaseUrl, embeddingModelName);// 步骤1: 配置DashScope API连接OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(openaiBaseUrl) // https://dashscope.aliyuncs.com/compatible-mode.apiKey(openaiApiKey) // sk-b7cbae5635ff49cba56c45a66ba9dafa.build();// 步骤2: 配置Embedding参数OpenAiEmbeddingOptions options = OpenAiEmbeddingOptions.builder().model(embeddingModelName) // text-embedding-v3.dimensions(1024) // 生成1024维向量.build();// 步骤3: 创建EmbeddingModelOpenAiEmbeddingModel model = new OpenAiEmbeddingModel(openAiApi, // API连接MetadataMode.EMBED, // 模式options // 配置参数);log.info("OpenAI EmbeddingModel创建成功");return model;
}
作用:
- 创建文本向量化工具
- 功能: 将文本转换为1024维的数字向量
- 通过DashScope API调用阿里云的embedding服务
何时使用:
vectorStore内部使用这个工具将文本转换为向量
实际调用示例:
// vectorStore内部会这样使用:
String text = "这是一段文本";
float[] vector = embeddingModel.embed(text);
// 结果: [0.123, 0.456, ..., 0.789] (1024个浮点数)
重要说明:
baseUrl不含/v1,OpenAiApi会自动拼接成/v1/embeddingsdimensions(1024)必须设置,text-embedding-v3支持的维度范围: [64, 128, 256, 512, 768, 1024]
节点4: Bean③ - 创建向量存储管理器
文件: MilvusConfig.java
@Bean(name = "zyVectorStore")
@ConditionalOnProperty(name = "milvus.enabled", havingValue = "true")
public VectorStore zyVectorStore(MilvusServiceClient milvusClient, // Spring自动传入Bean①EmbeddingModel embeddingModel) { // Spring自动传入Bean②log.info("正在创建Milvus VectorStore,集合名称: zhi_yan");// 整合milvusClient和embeddingModelMilvusVectorStore vectorStore = MilvusVectorStore.builder(milvusClient, // 用于连接Milvus数据库embeddingModel) // 用于文本向量化.collectionName("zhi_yan") // 指定集合名称.databaseName("default") // 数据库名.initializeSchema(true) // 自动创建集合(如果不存在).build();log.info("Milvus VectorStore创建成功");return vectorStore;
}
作用:
- 创建向量存储管理器,整合Milvus连接和Embedding工具
- 自动创建
zhi_yan集合(如果不存在) - 集合的embedding字段维度自动从
embeddingModel.dimensions()获取(1024)
何时使用:
DocumentServiceImpl通过@Autowired注入使用- 提供
add()方法来存储文档向量
实际使用:
// 在DocumentServiceImpl中
vectorStore.add(chunks); // vectorStore.add()内部执行流程:
for (Document chunk : chunks) {// 1. 调用embeddingModel生成向量float[] vector = embeddingModel.embed(chunk.getContent());// 2. 调用milvusClient存储到数据库milvusClient.insert("zhi_yan", vector, chunk.getMetadata());
}
集合Schema:
zhi_yan集合结构:
├── id (VARCHAR, 主键, 36字符)
├── embedding (FLOAT_VECTOR, dim=1024)
├── metadata (JSON, 存储文件名、类型等)
└── content (VARCHAR, 存储文档内容)
节点5: Bean④ - 创建文档切片工具
文件: MilvusConfig.java
@Bean
@ConditionalOnProperty(name = "milvus.enabled", havingValue = "true")
public DocumentTransformer textSplitter() {return new TokenTextSplitter(300, // 每块大小(tokens)200, // 块之间重叠(tokens)5, // 最小块大小10000, // 最大块大小true // 保持分隔符);
}
作用:
- 创建文档切片工具
- 将长文档切分成多个小块
- 每块约300个token,块之间重叠200个token
何时使用:
DocumentServiceImpl通过@Autowired注入使用- 在存储前先切片,避免单个文档过长
实际使用:
// 在DocumentServiceImpl中
String longText = "很长的文档内容...";
Document doc = new Document(longText);List<Document> chunks = textSplitter.apply(List.of(doc));
// 结果: [chunk1(300 tokens), chunk2(300 tokens), chunk3(300 tokens), ...]
切片示例:
原文档(1000 tokens)↓
切片1: tokens 0-300
切片2: tokens 100-400 (与切片1重叠100 tokens)
切片3: tokens 200-500 (与切片2重叠100 tokens)
切片4: tokens 300-600
...
为什么要重叠?
- 确保上下文连贯性
- 避免重要信息被切断
- 提高检索准确度
节点6: 业务处理 - 导入文档
文件: DocumentServiceImpl.java
@Override
public int importMarkdownContent(String content, String fileName) {if (vectorStore == null || textSplitter == null) {log.warn("VectorStore或TextSplitter未配置,无法导入文档");throw new UnsupportedOperationException("向量存储功能未启用,请配置Milvus");}try {log.info("开始导入Markdown内容,文件名: {}", fileName);// 1. 创建Document对象,添加元数据Map<String, Object> metadata = new HashMap<>();metadata.put("source", fileName);metadata.put("type", "markdown");metadata.put("imported_at", System.currentTimeMillis());Document document = new Document(content, metadata);// 2. 文档切片 (TokenTextSplitter)List<Document> chunks = textSplitter.apply(List.of(document));log.info("文档已分割成 {} 个块", chunks.size());// 3. 向量化并存储到Milvus// vectorStore.add()内部会:// - 调用embeddingModel.embed()生成向量// - 将向量存储到Milvus的zhi_yan集合vectorStore.add(chunks);log.info("成功导入 {} 个文档块到Milvus", chunks.size());return chunks.size();} catch (Exception e) {log.error("导入Markdown内容出错,文件名: {}", fileName, e);throw new RuntimeException("导入Markdown内容失败", e);}
}
处理流程:
-
创建Document对象
- 包装原始内容
- 添加元数据(文件名、来源、时间等)
-
文档切片
- 调用
TokenTextSplitter切分文档 - 返回多个Document块
- 调用
-
向量化与存储
vectorStore.add(chunks)触发以下操作:- 对每个chunk调用
embeddingModel.embed()生成向量 - 向量通过DashScope API生成(1024维)
- 将向量和内容存储到Milvus
- 对每个chunk调用
完整数据流与调用链路
用户上传文件后的完整执行流程
// ========== 阶段1: 接收上传 ==========
用户上传文件: POST /api/documents/import↓
DocumentController.importMarkdownFile(file)├─ 验证文件格式(.md)├─ 读取文件内容(UTF-8)└─ 调用服务层↓// ========== 阶段2: 业务处理 ==========
DocumentServiceImpl.importMarkdownContent(content, fileName)│├─ 步骤1: 创建Document对象│ Document doc = new Document(content, metadata);│├─ 步骤2: 文档切片 (使用Bean④ textSplitter)│ List<Document> chunks = textSplitter.apply(List.of(doc));│ // 原文档 → [chunk1, chunk2, chunk3, ...]│└─ 步骤3: 向量化并存储 (使用Bean③ vectorStore)vectorStore.add(chunks);↓// ========== 阶段3: 向量化 (vectorStore内部) ==========
for (Document chunk : chunks) {// 3.1 调用Bean② embeddingModel生成向量float[] vector = embeddingModel.embed(chunk.getContent());↓// embeddingModel内部执行:HTTP POST https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings{"model": "text-embedding-v3","input": "文档块内容","dimensions": 1024}↓响应: {"data": [{"embedding": [0.123, 0.456, ..., 0.789] // 1024个浮点数}]}// 3.2 调用Bean① milvusClient存储到数据库milvusClient.insert("zhi_yan", {id: UUID.randomUUID(),embedding: vector, // [1024维向量]metadata: {source: fileName,type: "markdown",imported_at: timestamp},content: chunk.getContent() // 文档块文本});
}↓// ========== 阶段4: 存储到Milvus ==========
Milvus数据库 zhi_yan集合
├─ 记录1: {id, embedding[1024], metadata, content}
├─ 记录2: {id, embedding[1024], metadata, content}
├─ 记录3: {id, embedding[1024], metadata, content}
└─ ...↓// ========== 阶段5: 返回结果 ==========
返回给用户: {"success": true,"message": "Markdown文件导入成功","chunksImported": 4,"fileName": "example.md"
}
各个Bean在流程中的作用
| 执行阶段 | 使用的Bean | 作用 |
|---|---|---|
| 启动时 | Bean① milvusClient | 连接Milvus数据库 |
| 启动时 | Bean② embeddingModel | 配置DashScope API |
| 启动时 | Bean③ vectorStore | 整合①②,提供统一接口 |
| 启动时 | Bean④ textSplitter | 创建切片工具 |
| 运行时 | textSplitter | 切分文档 |
| 运行时 | vectorStore | 协调向量化和存储 |
| 运行时 | embeddingModel | 生成向量(通过vectorStore调用) |
| 运行时 | milvusClient | 存储数据(通过vectorStore调用) |
对象依赖关系
启动时创建(MilvusConfig):Bean① milvusClient ────┐├──→ Bean③ vectorStore ──→ 注入到DocumentServiceImplBean② embeddingModel ──┘Bean④ textSplitter ────────────────────→ 注入到DocumentServiceImpl运行时使用(DocumentServiceImpl):textSplitter.apply() → 切片vectorStore.add() → 内部调用embeddingModel和milvusClient
关键问题与解决方案
问题1: 404错误
原因:
base-url配置为https://dashscope.aliyuncs.com/compatible-mode/v1- OpenAiApi自动添加
/v1,导致请求URL变成/v1/v1/embeddings
解决方案:
# 错误配置
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 ❌# 正确配置
base-url: https://dashscope.aliyuncs.com/compatible-mode ✅
问题2: 维度不匹配
错误信息:
Incorrect dimension for field 'embedding':
the no.0 vector's dimension: 1024 is not equal to field's dimension: 1536
原因:
- text-embedding-v3默认生成1024维向量
- Milvus集合配置为1536维(OpenAI标准)
解决方案:
// 在EmbeddingModel配置中显式指定维度
OpenAiEmbeddingOptions options = OpenAiEmbeddingOptions.builder().model("text-embedding-v3").dimensions(1024) // 明确设置为1024.build();
问题3: ApiKey类型错误
错误信息:
'ApiKey' is abstract; cannot be instantiated
原因:
ApiKey是抽象类,不能直接new ApiKey()
解决方案:
// 使用builder方式,直接传String
OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(openaiBaseUrl).apiKey(openaiApiKey) // 直接传String,不需要包装.build();
验证与测试
1. 启动应用
查看日志确认配置成功:
正在连接Milvus服务器: 10.0.0.15:19530
Milvus客户端连接成功
正在创建OpenAI EmbeddingModel,Base URL: https://dashscope.aliyuncs.com/compatible-mode, 模型: text-embedding-v3
OpenAI EmbeddingModel创建成功
正在创建Milvus VectorStore,集合名称: zhi_yan
Milvus VectorStore创建成功
2. 测试导入
使用Postman或curl测试:
curl -X POST http://localhost:8084/api/documents/import \-F "file=@test.md"
成功日志:
正在导入Markdown文件: test.md
开始导入Markdown内容,文件名: test.md
文档已分割成 4 个块
成功导入 4 个文档块到向量数据库
3. 验证Milvus
from pymilvus import connections, Collectionconnections.connect(host="10.0.0.15", port=19530)
collection = Collection("zhi_yan")# 查看集合信息
print(f"集合数量: {collection.num_entities}")
print(f"Schema: {collection.schema}")
性能优化建议
1. 批量处理
// 批量添加,减少网络开销
List<Document> allChunks = new ArrayList<>();
for (String file : files) {allChunks.addAll(processFile(file));
}
vectorStore.add(allChunks); // 一次性添加
2. 异步处理
@Async
public CompletableFuture<Void> importMarkdownContentAsync(String fileName, String content) {importMarkdownContent(fileName, content);return CompletableFuture.completedFuture(null);
}
3. 缓存Embedding
对于重复内容,可以缓存embedding结果避免重复调用API。
故障排查
日志级别配置
logging:level:com.example.assistant: DEBUGorg.springframework.ai: DEBUGio.milvus: DEBUG
常见错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
| 404 Not Found | base-url包含/v1 | 移除base-url末尾的/v1 |
| 维度不匹配 | embedding维度与集合不符 | 设置dimensions(1024) |
| 连接超时 | Milvus服务不可达 | 检查网络和Milvus状态 |
| API Key无效 | DashScope密钥错误 | 验证API Key有效性 |
总结
核心要点
- 配置正确的base-url: 不含
/v1后缀 - 明确指定维度:
dimensions(1024) - 自动创建集合:
initializeSchema(true) - 合理的切片策略: 300 tokens/块,200 tokens重叠
数据流向
MD文件 → 切片 → 向量化(DashScope) → 存储(Milvus)
关键代码节点
- DocumentController: 接收上传
- MilvusConfig.embeddingModel(): 配置向量化模型
- MilvusConfig.zyVectorStore(): 配置向量存储
- MilvusConfig.textSplitter(): 配置文档切片
- DocumentServiceImpl.importMarkdownContent(): 业务处理
附录
text-embedding-v3模型说明
| 属性 | 值 |
|---|---|
| 支持维度 | 64, 128, 256, 512, 768, 1024 |
| 默认维度 | 1024 |
| 最大输入 | 8192 tokens |
| 输出类型 | float32向量 |
Milvus索引配置
index_params = {"metric_type": "COSINE", # 余弦相似度"index_type": "IVF_FLAT", # 索引类型"params": {"nlist": 128} # 聚类中心数
}