6.9 KiB
6.9 KiB
SDD — Obsidian Vault 同步 · 软件设计文档
版本:v1.0 · 日期:2026-08-19 · 对应 PRD v1.0 事实源:真实代码。文档与代码冲突时以代码为准。
1. 总体架构
React AntD Pro (build-flow.admin)
└─ src/pages/Knowledge/index.tsx
└─ 「Obsidian 同步」按钮 → Modal(vaultDirectory + chunkingModeEnum)
│ POST /knowledge-bases/{id}/obsidian/sync (JWT, umi request)
▼
build-flow.api · BuildFlow.Host
└─ Endpoints/Rag/Obsidian/SyncObsidianVaultEndpoint [Permissions("knowledge:write")]
│ IMediator.Send(SyncObsidianVaultCommand)
▼
modules/rag/Application/Obsidian
└─ SyncObsidianVaultCommandHandler
│ 1. 校验知识库归属(Id + CreatedBy == 当前用户)→ NotFoundException
│ 2. 加载该 KB 已有 Obsidian 文档(VaultPath → 实体字典)
│ 3. 流式扫描 vault,逐文件 新增/更新/跳过
│ 4. SaveChangesAsync,返回 ObsidianSyncResult
▼
modules/rag/Infrastructure/Obsidian
└─ ObsidianVaultScanner(安全解析 + 流式枚举 + SHA256)
└─ ObsidianOptions(配置节 Rag:Obsidian)
2. 后端设计(现状)
2.1 分层与文件
| 层 | 文件 | 职责 |
|---|---|---|
| Host | Endpoints/Rag/Obsidian/SyncObsidianVaultEndpoint.cs |
路由 POST /knowledge-bases/{KnowledgeBaseId}/obsidian/sync,权限 knowledge:write,路由参数 + 请求体组装 Command |
| Application | Obsidian/Commands/SyncObsidianVaultCommand.cs |
(KnowledgeBaseId, VaultDirectory, ChunkingModeOverride?) : IRequest<ObsidianSyncResult> |
| Application | Obsidian/DTOs/ObsidianSyncRequest.cs |
(VaultDirectory, ChunkingModeEnum:int?) |
| Application | Obsidian/DTOs/ObsidianSyncResult.cs |
(KnowledgeBaseId, VaultDirectory, TotalFiles, Added, Updated, Skipped, Message) |
| Application | Obsidian/Handlers/SyncObsidianVaultCommandHandler.cs |
归属校验 + 增量对账 + 落库 |
| Infrastructure | Obsidian/ObsidianVaultScanner.cs |
白名单/符号链接/容量门禁 + 流式枚举 .md + SHA256 |
| Infrastructure | Obsidian/ObsidianOptions.cs |
Rag:Obsidian 配置节:AllowedRoots[] / MaxFiles / MaxFileBytes / MaxTotalBytes |
| Infrastructure | Obsidian/ObsidianVaultFile.cs |
(FullPath, RelativePath, FileName, FileSize, ContentHash) |
2.2 增量对账算法(Handler)
db.KnowledgeBases按Id == request.KnowledgeBaseId && CreatedBy == userId查找,未命中抛NotFoundException(ErrorCode.NotFound, "知识库不存在");- 已有
SourceType == Obsidian的文档以VaultPath ?? FileName为键建字典; - 对扫描到的每个文件:
- 已存在且
ContentHash相同 → Skipped; - 已存在但哈希不同 → 更新 FilePath/FileName/FileSize/ContentHash/VaultPath,
Status = Pending→ Updated; - 不存在 → 新增
DocumentEntity(SourceType = Obsidian、ContentType = "text/markdown"、Status = Pending、带ChunkingModeOverride)→ Added;
- 已存在且
- 一次
SaveChangesAsync提交,返回三计数 + 中文 message。
2.3 安全设计(Scanner)
- 白名单:
AllowedRoots为空直接拒绝;请求路径必须落在某个已存在的配置根目录内; - 路径净化:拒绝含
..段;Path.GetFullPath规范化;格式异常 →invalid_param; - 符号链接:
ResolveLinkTarget(returnFinalTarget: true)解析到真实路径后再次EnsureWithin( vault 根与每个条目双重校验),逃逸 →invalid_param; - 容量门禁:流式计数,
MaxFiles/MaxFileBytes/MaxTotalBytes超限 →too_large; - 竞态容错:扫描期间文件消失仅记 Warning 并跳过;枚举/读取/元数据 IO 失败 →
business; - 忽略目录:
.obsidian.trash.gitnode_modules;仅.md(大小写不敏感); - 流式:
IAsyncEnumerable+ 64KB 异步流式 SHA256,不全量加载内容; - 取消:每轮枚举与哈希均响应
CancellationToken。
2.4 配置(appsettings.json 现状)
"Rag": { "Obsidian": { "AllowedRoots": [], "MaxFiles": 10000, "MaxFileBytes": 10485760, "MaxTotalBytes": 536870912 } }
⚠️ AllowedRoots 默认为空 → 未配置时所有同步请求都会被拒绝(fail-closed,符合预期)。部署/本地调试必须显式配置,见 SOP。
3. 前端设计(本次新增)
- 位置:
build-flow.admin/src/pages/Knowledge/index.tsx(知识库列表)行内操作新增「Obsidian 同步」; - 交互:点击 →
Modal表单vaultDirectory:Input,必填,placeholder 提示为「服务器上的 vault 绝对路径」;chunkingModeEnum:Select,可清空,选项 =ChunkingModeEnum0-5(General/Heading/ParentChild/Separator/ByPage/Token),不选表示用知识库默认策略;- 提交中按钮 loading + 禁用,防重复提交;
- 结果反馈:成功后
Modal.success展示 TotalFiles/Added/Updated/Skipped 与 message;失败由requestErrorConfig统一透出后端中文 message; - 请求:统一走
@umijs/max的request(自动注入 Authorization),禁止直接 fetch/axios; - 权限:按钮渲染受
knowledge:write权限点控制(对齐 access.ts 现有模式)。
4. 数据模型(相关字段)
DocumentEntity:SourceType(Upload=0/Obsidian=1)、VaultPath(库内相对路径,如 Daily/2026-06-13.md)、ContentHash(SHA256 hex 小写)、ChunkingModeOverride、Status(同步后一律 Pending 交给处理管线)。
5. 错误处理映射
| 场景 | 异常 | 语义错误码 | HTTP |
|---|---|---|---|
| 知识库不存在/非本人 | NotFoundException | not_found |
404 |
路径为空/含 ../格式非法/不在白名单/目录不存在/链接逃逸 |
BusinessException | invalid_param |
400 |
| 文件数/单文件/总量超限 | BusinessException | too_large |
400 |
| IO 失败/无读取权限/扫描期消失(条目级) | BusinessException | business |
400 |
未登录 / 无 knowledge:write |
中间件 | — | 401 / 403 |
6. 测试设计
- 单元/集成(后端):
BuildFlow.Rag.Tests覆盖 Handler 三分支(added/updated/skipped)、归属校验;Scanner 白名单、..、符号链接逃逸、容量上限; - E2E:Playwright 驱动 React 端「登录 → 知识库 → Obsidian 同步 → 结果可见」,对接真实后端(详见 TDD.md)。
7. 已知缺陷与修复项
| # | 缺陷 | 处置 |
|---|---|---|
| D1 | 端点校验 ChunkingModeEnum is >= 0 and <= 2,但枚举已有 0-5(Separator/ByPage/Token 会被静默丢弃为 null) |
本期修复:改为 Enum.IsDefined 校验 |
| D2 | vault 中删除文件后知识库文档不联动删除(无删除对账) | 本期范围外,PRD §6 已声明 |
| D3 | AllowedRoots 默认空数组,未配置时功能不可用但无启动期告警 |
SOP 中作为部署检查项 |