7.6 KiB
7.6 KiB
DocHub 设计文档
背景与目标
小团队(10 人以下)需要一个基于 MinIO 的文档管理系统,支持:
- 浏览 MinIO 中存储的文档(主要是 Markdown / HTML,图片为次要内容)
- 全文内容检索(不只是文件名搜索)
- 在线编辑文档内容并保存
- 上传新文档
无需用户登录和权限管理(内部工具,信任团队内所有成员)。
技术栈
| 层 | 选型 | 理由 |
|---|---|---|
| 前端 | React + Vite + Monaco Editor | Monaco 提供代码级编辑体验(语法高亮、查找替换),适合 Markdown/HTML 源码编辑 |
| 后端 | Python + FastAPI | 团队熟悉度高,异步 IO 足够应对 MinIO/Meilisearch 网络调用瓶颈,生态利于后续扩展(文档解析、AI 摘要等) |
| 存储 | MinIO(独立部署) | S3 兼容对象存储,文档内容和元数据(大小/时间/路径)都存在这里,不引入额外数据库 |
| 检索 | Meilisearch | 轻量、内置中文分词、MIT 协议、Docker 一条命令部署,检索延迟低 |
明确不引入的组件:
- 关系型数据库(内容和元数据用 MinIO 承载,索引用 Meilisearch 承载,当前需求下够用)
- 用户认证/权限系统(团队内部工具,暂不需要)
架构
┌─────────────┐
HTTPS │ nginx │
───────────▶ │ (反向代理) │
└──────┬──────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
app.domain api.domain minio.domain(可选)
│ │
┌───────▼───────┐ ┌──▼──────────────┐
│ React 前端 │ │ FastAPI 后端 │
│ Vite + Monaco│ │ (Python) │
└───────────────┘ └───┬──────────┬───┘
│ │
S3 API │ │ REST API
▼ ▼
┌─────────────┐ ┌──────────────┐
│ MinIO │ │ Meilisearch │
│ (独立部署) │ │ (与后端同机) │
└─────────────┘ └──────────────┘
部署形态:两套独立的 docker-compose,各自独立生命周期:
minio/docker-compose.yml— 仅 MinIO,独立服务器或独立栈,长期稳定运行,可被其他项目复用app/docker-compose.yml— frontend + backend + meilisearch,可整体重启/升级而不影响已存储的数据
两套 compose 之间通过共享 Docker 网络或宿主机 IP 通信(后端配置 MinIO endpoint 即可,代码不感知部署形态差异,因为走的是标准 S3 API)。
域名与端口:通过 nginx 反向代理 + 子域名区分服务,无需在域名上绑定端口:
app.domain → React 前端
api.domain → FastAPI 后端
minio.domain → MinIO 控制台(可选对外)
Meilisearch 仅供后端内网访问,不对外暴露子域名。
数据流
上传文档
前端上传文件 → 后端 PUT 到 MinIO
→ 后端解析内容(Markdown/HTML 转纯文本)
→ 推送索引到 Meilisearch
→ 返回成功
浏览文件列表
前端请求文件树 → 后端调用 MinIO ListObjects → 返回文件夹/文件结构
查看/编辑文档
点击文件 → 后端从 MinIO GetObject 取内容 → 前端 Monaco Editor 加载展示
保存编辑 → 后端覆盖写入 MinIO → 同步更新 Meilisearch 索引
全局搜索
输入关键词 → 后端转发查询到 Meilisearch
→ 返回匹配文档(含高亮片段)
→ 前端展示结果列表,点击跳转到对应文档
删除文档
后端从 MinIO 删除对象 → 同步从 Meilisearch 删除对应索引
关键决策:索引更新采用后端同步处理,即写完 MinIO 后立即在同一请求中更新 Meilisearch 索引,不引入 MinIO webhook 异步机制。理由:团队规模小、写入量低,同步更新逻辑更简单、更容易保证一致性,避免维护额外的 webhook 消费者服务。
模块设计
前端(React + Vite + Monaco Editor)
| 组件 | 职责 |
|---|---|
FileExplorer |
左侧文件树,展示文件夹/文件,支持点击导航 |
DocumentViewer/Editor |
主区域,用 Monaco 加载文档内容,支持编辑与保存 |
SearchBar |
顶部全局搜索框,输入即查(debounce) |
SearchResults |
搜索结果列表,展示高亮匹配片段,点击跳转对应文档 |
UploadButton |
拖拽或选择文件上传 |
apiClient |
封装对后端的 fetch 调用(documents CRUD + search),是前端与后端之间的唯一边界 |
后端(FastAPI)
API 层:
| 接口 | 方法 | 职责 |
|---|---|---|
/api/documents |
GET | 列出文件树(基于 MinIO ListObjects) |
/api/documents/{key} |
GET | 读取单个文档内容 |
/api/documents/{key} |
PUT | 创建/覆盖文档(写 MinIO + 更新索引) |
/api/documents/{key} |
DELETE | 删除文档(删 MinIO + 删索引) |
/api/upload |
POST | 处理文件上传(multipart) |
/api/search |
GET | 转发查询到 Meilisearch,返回结果 |
内部模块划分(各模块单一职责,可独立测试):
minio_client.py— 封装 MinIO 操作(list/get/put/delete),只负责与 MinIO 交互,不含业务逻辑search_client.py— 封装 Meilisearch 操作(index/update/delete/search),只负责与 Meilisearch 交互document_service.py— 业务逻辑层,协调minio_client和search_client(保存文档先写 MinIO 再更新索引;删除时顺序相反),并负责 Markdown/HTML 转纯文本供索引使用routes/documents.py、routes/search.py— API 路由层,只做参数校验和调用 service,不直接触碰 MinIO/Meilisearch 客户端
依赖方向:routes → document_service → minio_client / search_client。上层不了解下层实现细节,替换 MinIO 为其他 S3 兼容存储或替换 Meilisearch 为其他搜索引擎时,只需改动对应的 client 模块。
错误处理
- MinIO 写入成功但索引更新失败:记录日志,不阻塞用户操作(文档已保存成功),后续可手动触发全量重建索引
- 请求的文档不存在:返回 404
- 上传文件类型/大小校验:在后端
document_service层做校验(限制非文档类型或超大文件),拒绝时返回明确错误信息
测试策略
- 后端:pytest 测试
document_service业务逻辑,mockminio_client和search_client,覆盖保存/删除时序、索引失败降级等场景 - 前端:暂不强制要求自动化测试覆盖(小型内部工具,手动验证优先级更高)
部署资源参考
学习/小团队场景下,DocHub 各服务的大致资源占用(供部署规划参考,非强制要求):
- MinIO:约 256-512MB 内存
- Meilisearch:约 512MB-1GB 内存(随索引文档量增长)
- FastAPI 后端:约 256-512MB 内存
- React/nginx 前端:约 128-256MB 内存
4 核 / 4GB 内存足以运行 DocHub 全部服务;若与其他自建服务(如 Gitea、数据库)共享同一台机器,建议预留合计 8GB 以上内存。