概述
本文档详细说明了如何将Markdown文件上传、切片并存储到Milvus向量数据库的完整流程,包括所有关键代码节点和配置说明。
系统架构
- 用户上传MD文件
- ↓
- DocumentController (接收请求)
- ↓
- DocumentService (业务处理)
- ↓
- TokenTextSplitter (文档切片)
- ↓
- EmbeddingModel (向量化) ← DashScope API
- ↓
- MilvusVectorStore (存储)
- ↓
- Milvus数据库
复制代码 技术栈
组件版本说明Spring Boot3.4.3应用框架Spring AI1.0.2AI集成框架Milvus2.4.8向量数据库DashScopetext-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: true
- options:
- model: text-embedding-v3 # 使用v3模型
- milvus:
- enabled: true
- host: 10.0.0.15
- port: 19530
复制代码 关键点:
- base-url末尾不要包含/v1,OpenAiApi会自动添加
- 使用text-embedding-v3模型,支持1024维度
2. pom.xml依赖
- <dependency>
- <groupId>org.springframework.ai</groupId>
- spring-ai-openai</artifactId>
- <version>1.0.2</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.ai</groupId>
- spring-ai-milvus-store</artifactId>
- <version>1.0.2</version>
- </dependency>
- <dependency>
- <groupId>io.milvus</groupId>
- 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: 创建EmbeddingModel
- OpenAiEmbeddingModel 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/embeddings
- dimensions(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和embeddingModel
- MilvusVectorStore 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
完整数据流与调用链路
用户上传文件后的完整执行流程
- // ========== 阶段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 ──→ 注入到DocumentServiceImpl
- Bean② 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, Collection
- connections.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: DEBUG
- org.springframework.ai: DEBUG
- io.milvus: DEBUG
复制代码 常见错误
错误原因解决方案404 Not Foundbase-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} # 聚类中心数
- }
复制代码 来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |