fix: 修复 CORS allow_credentials 与 allow_origins=* 冲突

This commit is contained in:
2026-07-10 15:18:39 +08:00
parent 3b8b585f31
commit 284a4b9e09
6 changed files with 3663 additions and 5 deletions
@@ -0,0 +1,142 @@
# 多格式文档支持 — 设计文档
**日期**: 2026-07-10
**版本**: 1.0
## 目标
将 md-vector-db 从"仅 Markdown"扩展为支持 `.txt``.pdf``.html` 的多格式文档向量数据库。
## 非目标
- 不支持 `.docx``.pptx``.epub` 等 Office/电子书格式(留待后续扩展)
- 不改变现有的嵌入和检索流程
- 不改变 HTTP API 的请求/响应模型
## 架构
### 模块结构
```
src/core/
├── splitters/ # 新建目录
│ ├── __init__.py # 导出 registry + 所有 Splitter
│ ├── base.py # Splitter Protocol + BaseTextSplitter (ABC)
│ ├── markdown.py # MarkdownSplitter (从 ingest.py 移入)
│ ├── text.py # TextSplitter (纯文本按段落+标点硬切)
│ ├── pdf.py # PDFSplitter (pymupdf → TextSplitter)
│ ├── html.py # HTMLSplitter (bs4 → TextSplitter)
│ └── registry.py # 工厂 + 扩展名→Splitter 映射表
├── ingest.py # 精简,用 registry.get_splitter() 自动选择
```
### 类继承
```
Splitter (Protocol)
└── BaseTextSplitter (ABC) # max_size, overlap, _split_single_paragraph
├── MarkdownSplitter # 已有,从 ingest.py 移入
├── TextSplitter # 纯文本:按 \n\n 切段落,超长按标点硬切
├── PDFSplitter # 读取 PDF → extract_text() → 委托 TextSplitter
└── HTMLSplitter # 读取 HTML → get_text() → 委托 TextSplitter
```
`PDFSplitter``HTMLSplitter` **不继承** `BaseTextSplitter`,而是组合一个 `TextSplitter` 实例。它们实现 `Splitter` Protocol,在 `split()` 中:提取纯文本 → 委托 `TextSplitter.split()`
### 注册表
`registry.py` 维护默认扩展名映射:
```python
_DEFAULT_MAP = {
".md": "markdown",
".markdown": "markdown",
".txt": "text",
".pdf": "pdf",
".html": "html",
".htm": "html",
}
def get_splitter(file_path: str, **config) -> Splitter:
"""根据扩展名自动选择 Splitter,未匹配回退到 TextSplitter."""
```
## 依赖策略
- `pymupdf``beautifulsoup4` 作为**可选依赖**
- 首次使用 PDF/HTML 格式时才 `import`,库缺失时抛 `ImportError` 带安装提示
- 安装方式:
```bash
uv sync --extra pdf # PDF 支持
uv sync --extra html # HTML 支持
uv sync --extra all # 全部可选依赖
```
## ingest.py 改动
### DocumentIngestor.__init__
已有 `splitter` 可选参数,**保持不变**。显式传入的 splitter 覆盖自动选择。
### ingest_file()
改为使用 `get_splitter(file_path, ...)` 自动选择 splitter
```python
def ingest_file(self, file_path: str) -> int:
splitter = self.splitter or get_splitter(file_path,
max_size=..., overlap=...)
...
```
### ingest_directory()
`rglob("*.md")` 改为遍历所有支持格式:
```python
_SUPPORTED_SUFFIXES = {".md", ".markdown", ".txt", ".pdf", ".html", ".htm"}
def ingest_directory(self, dir_path: str) -> dict[str, int]:
for f in Path(dir_path).rglob("*"):
if f.suffix.lower() in _SUPPORTED_SUFFIXES:
...
```
## 数据流
```
文件路径 → get_splitter(path)
├── .md → MarkdownSplitter.split(text)
├── .txt → TextSplitter.split(text)
├── .pdf → PDFSplitter.split(binary)
│ └── pymupdf 提取文字 → TextSplitter.split(text)
└── .html → HTMLSplitter.split(html)
└── bs4 去标签 → TextSplitter.split(text)
batch_embed(chunks)
ChromaDB.add()
```
## 测试策略
- `tests/test_splitters_text.py` — TextSplitter 段落切分、硬切、overlap
- `tests/test_splitters_registry.py` — 扩展名映射、回退逻辑、自定义注册
- `tests/test_splitters_pdf.py` — PDF 提取文字 + 分块(需 pymupdf)
- `tests/test_splitters_html.py` — HTML 去标签 + 分块(需 bs4
- `tests/test_ingest.py` — 补 `ingest_directory` 多格式遍历测试
## CLI/API 影响
- **CLI**:零改动。`ingest``ingest-dir` 自动获得多格式能力
- **API**:零改动。`POST /api/v1/ingest``file_path` 自动支持
- **`ingest_content()`**:仍默认使用 `MarkdownSplitter`(处理 Markdown 字符串),显式传入 content 时不变
## 风险
| 风险 | 缓解 |
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| PDF 提取失败(扫描件/图片 PDF) | 抛明确异常,跳过该文件 |
| HTML 标签复杂导致去标签不干净 | 用`soup.get_text(separator="\n")` 保留段落结构 |
| `ingest.py` 迁移 MarkdownSplitter 后旧 import 路径失效 | 在`ingest.py` 保留兼容 import`from src.core.splitters.markdown import MarkdownSplitter` |