refactor(knowledge): KnowledgeIndexService 从数据库加载索引
## 改动内容
### 修改前:从文件系统扫描
```java
@Value("${knowledge.base-path}")
private String knowledgeBasePath;
@Autowired
private FrontmatterParser frontmatterParser;
@PostConstruct
public void loadIndex() {
// 1. 扫描 knowledge_base 目录
// 2. 读取每个 .md 文件
// 3. 解析 frontmatter
// 4. 构建内存索引
}
```
**问题**:
- 依赖文件系统,无法利用数据库已有数据
- 启动时需要重新扫描和解析所有文件
- 文件和数据库可能不一致
---
### 修改后:从数据库加载
```java
@Autowired
private ApiDocumentRepository apiDocumentRepository;
@PostConstruct
public void loadIndex() {
// 1. 从数据库读取所有文档
List<ApiDocument> documents = apiDocumentRepository.findAll();
// 2. 解析 metadata JSON
// 3. 构建内存索引
}
```
**优势**:
- ✅ 数据源统一:数据库是唯一真实数据源
- ✅ 启动更快:无需重新扫描文件和解析 frontmatter
- ✅ 数据一致:L0 索引与数据库完全同步
- ✅ 支持动态更新:初始化接口更新数据库后,L0 索引也会更新
---
## 核心方法
### 1. parseDocumentToEntry
从 `ApiDocument` 转换为 `KnowledgeEntry`:
```java
private KnowledgeEntry parseDocumentToEntry(ApiDocument doc) {
String metadata = doc.getMetadata();
String title = extractJsonValue(metadata, "title");
String summary = extractJsonValue(metadata, "summary");
String category = extractJsonValue(metadata, "category");
List<String> keywords = extractJsonArray(metadata, "keywords");
return KnowledgeEntry.builder()
.filePath(doc.getFilePath())
.title(title)
.keywords(keywords)
.summary(summary)
.category(category)
.build();
}
```
### 2. 简单的 JSON 解析
```java
private String extractJsonValue(String json, String key) {
// 提取 "key":"value" 格式
}
private List<String> extractJsonArray(String json, String key) {
// 提取 "key":["v1","v2"] 格式
}
```
**注意**:使用简单的字符串解析,避免引入 JSON 库依赖
---
## 数据流
```
应用启动
↓
KnowledgeIndexService.loadIndex()
↓
apiDocumentRepository.findAll()
↓
读取所有 api_document 记录
↓
解析每条记录的 metadata JSON
↓
构建 KnowledgeEntry
↓
加入内存索引(CopyOnWriteArrayList)
↓
L0 索引就绪
```
---
## 启动日志
```
[INFO] 开始从数据库加载知识库索引
[INFO] 知识库索引加载完成,共 6 个文档
```
---
## 与初始化接口的配合
### 流程 1:首次启动(数据库为空)
```
1. 应用启动
2. KnowledgeIndexService.loadIndex() → 0 个文档
3. 调用 POST /api/knowledge/init
4. 写入数据库 + 调用 knowledgeIndexService.addToIndex()
5. L0 索引更新为 6 个文档
```
### 流程 2:重启应用(数据库有数据)
```
1. 应用启动
2. KnowledgeIndexService.loadIndex() → 从数据库加载 6 个文档
3. L0 索引已就绪,无需再调用初始化接口
```
---
## 兼容性
### metadata JSON 示例
```json
{
"title": "支付网关错误码定义",
"summary": "记录了支付网关所有核心错误码的含义及排查方向",
"category": "api",
"keywords": ["ERR_TIMEOUT","超时","支付网关"]
}
```
### 字段映射
| metadata | KnowledgeEntry | 说明 |
|----------|---------------|------|
| title | title | 文档标题 |
| summary | summary | 文档摘要 |
| category | category | 文档分类 |
| keywords | keywords | 关键词列表 |
---
## 删除的代码
- ❌ `@Value("${knowledge.base-path}")`:不再需要文件路径配置
- ❌ `FrontmatterParser` 依赖:不再扫描文件
- ❌ `indexFile()` 方法:不再读取文件
- ❌ `extractCategoryFromPath()` 方法:从 metadata 获取
---
## 验证
```bash
# 1. 清空数据库
# DELETE FROM api_document;
# 2. 启动应用
mvn spring-boot:run
# 3. 查看日志
# [INFO] 知识库索引加载完成,共 0 个文档
# 4. 初始化
curl -X POST http://localhost:9900/api/knowledge/init
# 5. 重启应用
# [INFO] 知识库索引加载完成,共 6 个文档
```