上传与摄取(多格式解析 → contentHash)
上级:key-designs
core/src/upload/。摄取过程完全在客户端完成——服务器只存储解析结果,从不自己解析。parseFile 按扩展名分派给 parseEpub / parsePdf / parseText(外加 Gutenberg 导入、epubMetadata、epubToc)。
- EPUB(
parseEpub.ts):JSZip 打开压缩包 → 读取container.xml→ 解析 OPF spine → 每个 spine 条目去除 HTML 标签后用\n\n拼接;扫描版/内容过少的书(<200 字符)会被拒绝。目录优先从 NCX 读取,其次是 EPUB3 nav(epubToc.ts),每个条目再通过computeChapterOffsets映射到一个累计的字符偏移量(这样章节分界就是内容位置,供 dynamic-pagination 使用)。 - PDF / OCR:文本型 PDF 在客户端解析;扫描版 PDF 会路由到 Python 的
nlp-api,走/parse-pdf+/ocr-images(Tesseract)——这是唯一的服务端摄取路径。 - 语言检测 以及 ISBN/元数据的来源标记(
isbnSource= epub_meta / manual / fuzzy_match)。
contentHash(MD5)是整个设计的基石。 upload/contentHash.ts 对全文做 MD5——特意选用 MD5,是为了与遗留数据行保持可重新关联的兼容性。它是一切内容锚定的连接键:词汇、笔记、句子缓存、搜索索引、以及 TTS 有声书,全都以 content hash 为键,所以重新上传同一本书会把已有的词汇/笔记/高亮重新关联上去,而不是让它们变成孤儿数据。上传时提交结构化 JSON(POST /api/books {action:"create"})——正文 + 语言 + contentHash + 目录 + base64 封面——并返回一个 bookId。
为什么它是支柱:多格式解析本身就是扎实的工程量,而 contentHash 正是整个内容锚定设计(raw-display-cursor)所依赖的身份标识——一旦哈希算错,下游的每一处锚点都会脱锚。