2026-09-23·by Sijie Wang#lucerna#software#architecture

upload-ingestion

上传与摄取(多格式解析 → contentHash)

上级:key-designs

core/src/upload/。摄取过程完全在客户端完成——服务器只存储解析结果,从不自己解析。parseFile 按扩展名分派给 parseEpub / parsePdf / parseText(外加 Gutenberg 导入、epubMetadataepubToc)。

  • EPUBparseEpub.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)所依赖的身份标识——一旦哈希算错,下游的每一处锚点都会脱锚。

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →