私人Wiki知识库方案设计

Posted by Forgus on 2026-04-06

私人 Markdown 信息索引系统,提供全文搜索、重复检测能力,支持 MCP Server 供 AI 工具调用。

功能特性

  • 全文搜索:基于 SQLite FTS5 实现高效全文检索,支持中文搜索
  • 文件管理:列出文件、获取文件内容和元数据
  • 重复检测:自动识别精确重复和近似重复文档
  • RAG 查询:为 AI 工具提供格式化的上下文检索
  • MCP Server:通过 SSE 与 AI 工具(如 opencode)集成
  • 定时扫描:自动检测文件新增/修改/删除/移动
  • NFS 共享:索引数据存储在 NFS 上,支持多机共享

架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
┌─────────────────────────────────────────────────────────────┐
│ 用户交互层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ opencode │ │ 浏览器 │ │ 其他AI │ │ CLI工具 │ │
│ │ (MCP) │ │ (Web UI) │ │ (RAG) │ │ (REST) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└───────┼──────────────┼──────────────┼──────────────┼─────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌───────────────────────────────────────────────────────────────┐
│ API 服务层 (FastAPI) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 全文搜索API │ 语义搜索API │ RAG查询API │ MCP Server │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────┬────────────────────────────────┘

┌───────────────────────────────────────────────────────────────┐
│ 索引引擎层 (Indexer) │
│ ┌──────────────┐ ┌────────────┐ ┌──────────────────────┐ │
│ │ Scanner │ │ Parser │ │ SQLite FTS5 + Dedup │ │
│ │ (文件扫描) │ │ (内容解析) │ │ (索引+重复检测) │ │
│ └──────────────┘ └────────────┘ └──────────────────────┘ │
└───────────────────────────────────────────────────────────────┘

┌───────────────────────────────────────────────────────────────┐
│ 存储层 (NFS) │
│ /mnt/nfs/md-files/ /mnt/nfs/md-index/ │
│ markdown源文件 index.db + metadata.json + .lock
└───────────────────────────────────────────────────────────────┘

快速开始

安装依赖

1
2
cd /root/projects/wiki-server
pip3 install --break-system-packages -r requirements.txt

启动服务

1
2
3
4
5
6
7
8
# 前台启动
./start.sh

# 后台启动
nohup python3 api_server.py > /tmp/wiki-server.log 2>&1 &

# 验证启动
curl http://localhost:8900/api/health

运行测试

1
./test.sh

停止服务

1
kill $(lsof -ti:8900)

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
/root/projects/wiki-server/
├── config.py # 配置加载模块
├── scanner.py # 文件扫描器(检测文件变更)
├── parser.py # 内容解析器(提取 frontmatter 和文本)
├── indexer.py # 索引引擎(Scanner + Parser + SQLite FTS5 + Dedup)
├── dedup.py # 重复检测模块
├── api_server.py # FastAPI 服务 + MCP Server
├── config.yaml # 配置文件
├── requirements.txt # Python 依赖
├── start.sh # 启动脚本
├── test.sh # 测试脚本
└── k3s-deployment.yaml # k3s 部署配置

API 接口

搜索接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 全文搜索(默认不排除重复,不返回总数)
curl -G "http://localhost:8900/api/search" \
--data-urlencode "q=关键词" \
--data-urlencode "tags=标签" \
--data-urlencode "limit=10"

# 搜索时排除重复文档
curl -G "http://localhost:8900/api/search" \
--data-urlencode "q=关键词" \
--data-urlencode "exclude_duplicates=true"

# 搜索时返回总数(性能开销较大)
curl -G "http://localhost:8900/api/search" \
--data-urlencode "q=关键词" \
--data-urlencode "include_total=true"

# 语义搜索(当前降级为全文搜索)
curl -X POST http://localhost:8900/api/semantic-search \
-H "Content-Type: application/json" \
-d '{"query": "关键词", "top_k": 5, "exclude_duplicates": false, "include_total": false}'

文件接口

1
2
3
4
5
6
7
8
# 列出文件(默认不排除重复,不返回总数)
curl "http://localhost:8900/api/files?tags=标签&sort=date&order=desc&limit=20"

# 列出文件时返回总数
curl "http://localhost:8900/api/files?include_total=true&exclude_duplicates=true"

# 获取文件内容和元数据
curl "http://localhost:8900/api/files/文档路径.md"

RAG 接口

1
2
3
4
5
6
7
8
9
10
# RAG 优化查询
curl -X POST http://localhost:8900/api/rag/query \
-H "Content-Type: application/json" \
-d '{
"query": "查询内容",
"top_k": 3,
"include_full_content": true,
"max_context_length": 4000,
"exclude_duplicates": false
}'

重复检测接口

1
2
3
4
5
# 获取重复检测报告
curl "http://localhost:8900/api/duplicates"

# 手动触发重复检测
curl -X POST "http://localhost:8900/api/duplicates/scan"

管理接口

1
2
3
4
5
6
7
8
# 手动刷新索引
curl -X POST "http://localhost:8900/api/index/refresh"

# 获取索引状态
curl "http://localhost:8900/api/index/status"

# 健康检查
curl "http://localhost:8900/api/health"

API 文档

启动服务后访问:

  • Swagger UI: http://localhost:8900/docs
  • ReDoc: http://localhost:8900/redoc

添加文档

将 Markdown 文件放入 /mnt/nfs/md-files/ 目录:

1
2
3
4
5
# 复制文档
cp your-doc.md /mnt/nfs/md-files/

# 等待自动扫描(默认 5 分钟),或手动刷新
curl -X POST http://localhost:8900/api/index/refresh

MCP Server 配置

opencode 配置

在 opencode 配置文件中添加:

1
2
3
4
5
6
7
{
"mcpServers": {
"md-index": {
"url": "http://192.168.x.x:8900/mcp/sse"
}
}
}
  • 本地访问: http://localhost:8900/mcp/sse
  • 局域网访问: http://<服务器IP>:8900/mcp/sse

MCP 工具列表

工具 说明 参数
search_markdown 关键词搜索 query, tags, limit
semantic_search 语义搜索 query, top_k
get_file_content 获取文件内容 path
list_files 列出文件 tags, sort, limit
rag_query RAG 查询 query, top_k, max_context_length
check_duplicates 检查重复文档

配置说明

编辑 config.yaml 自定义配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
storage:
md_files_path: "/mnt/nfs/md-files" # Markdown 文件目录
index_path: "/mnt/nfs/md-index" # 索引数据目录

scanner:
interval_minutes: 5 # 扫描间隔(分钟)
file_extensions: [".md", ".markdown"] # 文件扩展名
exclude_patterns: [".git/", "node_modules/", ".trash/"] # 排除模式

vector:
enabled: auto # 向量检索(auto/true/false)
model_path: "./models/all-MiniLM-L6-v2" # 本地模型路径
embedding_api_url: "" # 云端 API URL
chunk_size: 500 # 文本分块大小
chunk_overlap: 50 # 分块重叠

dedup:
enabled: true # 启用重复检测
exact_hash: true # 精确重复检测
similarity_threshold: 0.9 # 相似度阈值
containment_threshold: 0.9 # 包含阈值

api:
host: "0.0.0.0" # 监听地址
port: 8900 # 监听端口
cors_origins: ["*"] # CORS 配置
api_token: "" # API 令牌(可选)

mcp:
enabled: true # 启用 MCP Server
transport: "stdio" # 传输方式

部署

k3s 部署

1
2
3
4
5
6
7
8
9
10
# 1. 构建镜像并推送到私有仓库
docker build -t 192.168.2.40:30500/wiki-server:latest .
docker push 192.168.2.40:30500/wiki-server:latest

# 2. 部署到 k3s 集群
kubectl apply -f k3s-deployment.yaml

# 3. 查看状态
kubectl get pods -l app=wiki-server
kubectl logs -f deployment/wiki-server

部署配置说明

k3s-deployment.yaml 关键配置:

配置项 说明
镜像 192.168.2.40:30500/wiki-server:latest Zot 私有镜像仓库
存储 NFS (nfs-pvc) 挂载到 /mnt/nfs
节点选择 amd64 仅在 k3s-master 运行
资源限制 CPU: 100m-500m, Memory: 256Mi-512Mi

环境变量(可选覆盖 config.yaml):

变量名 默认值 说明
MD_FILES_PATH /mnt/nfs/md-files Markdown 文件目录
INDEX_PATH /mnt/nfs/md-index 索引数据目录

访问方式:

  • ClusterIP: http://wiki-server-service.default.svc.cluster.local:8900
  • Ingress: http://wiki-server.local/ (需配置 hosts)
  • NodePort: 通过 Service 改为 NodePort 类型后访问

资源预估

组件 CPU 内存
API 服务 50m 100MB
扫描器 100m (周期性) 50MB
SQLite 10m 20MB
总计 ~200m ~250MB

技术栈

  • Python 3.11
  • FastAPI - Web 框架
  • SQLite FTS5 - 全文索引
  • MCP SDK - AI 工具集成
  • PyYAML - 配置解析
  • fcntl - NFS 并发锁

注意事项

  • 中文搜索使用 LIKE 查询(FTS5 unicode61 tokenizer 对中文分词支持有限)
  • 向量检索当前未启用,需要下载模型或配置云端 API
  • NFS 并发写入通过 fcntl 文件锁保护
  • 服务启动时自动全量扫描,之后定时增量扫描

故障排查

Pod 启动失败 (Read-only file system)

问题: OSError: [Errno 30] Read-only file system: '/mnt/nfs/md-files'

原因: NFS 存储卷挂载为只读

解决:

  1. 确保 Deployment 中 volumeMounts 未设置 readOnly: true
  2. NFS 服务器需允许客户端写入 (检查 NFS 导出权限)
  3. 如使用 Longhorn 存储,可创建新的 PVC 替换 nfs-pvc

查看日志

1
2
kubectl logs -l app=wiki-server
kubectl describe pod -l app=wiki-server

重新部署

1
2
kubectl rollout restart deployment/wiki-server
kubectl rollout status deployment/wiki-server

NFS 路径不一致导致索引不更新

问题: 在 /mnt/nfs/md-files 放入文件后,索引未生成

排查步骤:

1
2
3
4
5
6
7
8
9
10
11
# 1. 检查主机挂载路径
mount | grep nfs

# 2. 检查 NFS 服务器导出
showmount -e 192.168.2.20

# 3. 检查容器内实际挂载内容
kubectl exec deploy/wiki-server -- ls -la /mnt/nfs/md-files/

# 4. 检查 PV 配置
kubectl get pv -o yaml | grep -A5 nfs

原因: Kubernetes PV 的 NFS path 与主机挂载路径不一致,导致容器内看到空目录

解决: 确保 PV 的 nfs.path 与主机挂载的 NFS 路径一致