# Image Pipeline & Cover Strategy > 对应代码核心:`src/markdown/local-file.ts`, `src/article-render.ts` 以及相关预处理逻辑。 > 若需总体结构参见 `architecture.md`,更广设计参见 `detaildesign.md`。 ## 1. 目标 - 统一处理所有图片来源:Wikilink、标准 Markdown、远程 URL、Base64、生成图 (Mermaid/Excalidraw/SVG)。 - 保证上传后 HTML 使用微信可访问的正式 URL。 - 自动封面选取:未指定时从正文首图推断。 - WebP → JPG 转换保障兼容性。 - 默认封面配置:若无正文/画廊候选,使用 `defaultCoverPic` (settings)。 - 手动 frontmatter 回退:metadata 缺失时行级解析 `title/author/image`。 ## 2. 图片来源与归一化 | 来源 | 输入示例 | 归一化策略 | 备注 | |------|----------|-----------|------| | Wikilink | `![[foo.png]]` / `![[foo.png|120x80]]` | Marked 扩展直接生成 `` 并登记 | 支持尺寸 / 位置扩展语法 | | Markdown | `![alt](assets/img/foo.png)` | (可选) 预处理转换成 `![[foo.png]]` | 依赖设置:`enableMarkdownImageToWikilink` | | 远程 URL | `` | 解析为远程上传任务 | 跳过已是微信域名 | | Base64 | `` | 解析成 Blob 上传 | 生成虚拟 id 构造文件名 | | Mermaid | 源码块 → SVG/DOM | 转 PNG (html-to-image) / 留 SVG | 替换占位容器 | | Excalidraw | `![[diagram.excalidraw]]` | 远程接口取 SVG 或转 PNG | 需 AuthKey | | SVG 文件 | `![[icon.svg]]` | 内联 SVG 内容 | 直接嵌入,不上传 | ## 3. 关键数据结构 ```ts interface ImageInfo { resUrl: string; // 初始占位或本地资源 URL (vault path 映射) filePath: string; // Vault 内实际文件路径 url: string | null; // 上传后微信返回 URL media_id: string | null; // 上传后素材 ID(type=image 时) } ``` 管理容器:`Map`,键值为 `resUrl`(初始唯一标识)。 ## 4. 收集阶段 1. Marked 扩展 (`LocalFile.markedExtension`) 在 token 遍历时识别 LocalImage。 2. 根据 wikilink / 尺寸参数解析出真实 vault 文件路径:`assetsManager.getResourcePath()`。 3. 调用 `LocalImageManager.setImage(res.resUrl, info)` 建档。 4. Markdown 图片(标准语法)若转换启用,提前转成 wikilink 进入同一逻辑;否则需未来扩展 tokenizer 直接登记。 5. Gallery: - 短代码:`{{}}{{}}` → 目录枚举(受 `galleryPrePath` + `galleryNumPic` 影响)→ 多行 `![[...]]` 注入。 - 块级:`{{}} ... {{}}` 内 `figure src="..." link="..." caption="..." >` 解析 src basename 加入候选;`link` 预留后续包装;`caption` 未来映射题注。 ## 5. 上传阶段 顺序: ```mermaid graph TD A[准备 token] --> B[cachedElementsToImages] B --> C[uploadLocalImage] C --> D[uploadRemoteImage] D --> E[replaceImages] E --> F[输出 HTML / 复制 / 发布] ``` ### 5.1 本地图片 `uploadLocalImage` 流程: - 遍历登记表,读取 vault 文件二进制。 - 若扩展名 `.webp` → wasm (`PrepareImageLib`) 转 JPG。 - 调用 `wxUploadImage(blob, filename, token, type)`。 - 更新 `ImageInfo.url` / `media_id`。 - 失败:`Notice + console.error`,继续后续项。 ### 5.2 远程图片 `uploadRemoteImage` 匹配 ``: - 跳过已在微信域名(`mmbiz.qpic.cn`)。 - 下载 → Blob → (webp 转换) → 上传。 - 更新 Map,键为原始 src。 Base64: - 解析 data URI → 生成 Blob/扩展名 → 上传。 ### 5.3 替换阶段 `replaceImages` - 遍历 DOM `` → 查询 Map → 使用上传后 `url` 覆盖 `src`。 - 未登记:输出警告(潜在渲染管线遗漏)。 ## 6. 自动封面策略(更新) 触发点:`getMetadata()`;若存在 `thumb_media_id` 则跳过。决策链: 1. frontmatter cover/image(非空);若 metadataCache 缺失,则手动行级解析首个 `---` 块。 2. 正文本地图候选(markdown + wikilink)。 3. gallery 展开产生的首图(短代码或块级 figure 列表)。 4. `defaultCoverPic`(settings 配置,允许相对路径 / URL / wikilink)。 5. 否则封面为空。 Edge Cases: | 情况 | 处理 | |------|------| | frontmatter cover: "" | 视为未配置,继续回退 | | 所有正文图片都是 http(s) | 跳过正文阶段,转 gallery -> defaultCoverPic | | gallery 块无有效 figure | 不贡献候选 | | defaultCoverPic 不存在 | 仍返回该引用;上传阶段可能失败(待校验增强) | | markdown 图片后缀非图片 | 跳过 | | 重复图片 | 以最先出现的为准 | ## 7. 正则索引 | 目的 | 正则 | 描述 | |------|------|------| | Frontmatter 删除 | `/^(---)$.+?^(---)$.+?/ims` | 首段 YAML | | Markdown 图片 | `/!\[[^\]]*\]\(([^)\s]+)\)/g` | 捕获路径 group1 | | Wikilink 图片 | `/!\[\[(.+?)\]\]/g` | 捕获内部资源 | | Gallery 块 | `{{}}([\s\S]*?){{<\/gallery>}}` | 包裹内容 | | Gallery figure | `{{]*>}}` | 提取 src | | figure link | `link=\"([^\"]+)\"` | 可选外链属性 | | WebP 检测 | `/\.webp$/i` | 扩展判断 | | Base64 前缀 | `^data:image/` | 判断内嵌图 | ## 8. 失败与恢复策略 | 环节 | 失败示例 | 策略 | |------|----------|------| | wasm 初始化 | 网络慢 / 未加载 | 继续使用原 webp 上传(失败概率增加) | | 本地文件找不到 | 路径错误 / 移动 | 在控制台警告,图片跳过 | | 上传 403/401 | token 失效 | 抛异常终止流程(需重新获取 token) | | 单图 errcode !=0 | 大小/格式不合规 | Notice 提示 + 继续其他 | | 替换未命中 | DOM 没登记 | 记录警告,建议加入补偿扫描 | ## 9. 性能优化方向 | 问题 | 现状 | 改进 | |------|------|------| | 上传串行 | 顺序 await | 并发池 (N=3~5) + 重试退避 | | WebP 转换阻塞 | 单线程 | 预热 wasm + 批量并行 | | 远程下载串行 | 每图 await | 统一收集 Promise.all 控制并发 | | 首图扫描重复 | 重新正则两次 | 合并一次统一匹配 pipeline | ## 10. 与发布流程的接口点 | 函数 | 上下游 | 说明 | |------|--------|------| | `cachedElementsToImages()` | 延迟图形 → 图片 | 在上传前确保所有图形成为 可被收集 | | `uploadLocalImage()` | 发布前 | 更新本地图片 URL/media_id | | `uploadRemoteImage()` | 发布前 | 远程/内嵌资源统一化 | | `replaceImages()` | 发布前 | DOM HTML 成为最终状态 | | `getArticleContent()` | 发布 / 复制 / 导出 | 输出含最终图片 URL 的 HTML | ## 11. 未来扩展 | 需求 | 设想 | |------|------| | CDN 直传 | 微信前置 → 自建或 OSS 中转 | | 图片压缩 | 上传前可选压缩 (canvas/wasm) | | 失效重传 | 保存上传映射 + 校验缺失再补传 | | 分块上传 | 大图分片(若接口支持) | | 图片校验 | MD5 去重避免重复上传 | | defaultCoverPic 校验 | 渲染时验证存在性 + Notice | | 多默认封面池 | 数组随机选择减少视觉重复 | | caption alias | figure caption -> wikilink alias/figcaption | | link 包裹 | link 属性生成 `` 包裹 `` | ## 12. 快速检查清单 (Debug Checklist) - 图片是否在 `LocalImageManager.images` Map 中? - 是否执行了 `cachedElementsToImages()`(Mermaid/Excalidraw)? - 上传后 `media_id` 是否为空?(格式/大小不合规) - DOM 替换后 `` 是否为微信域? - 自动封面未生效?检查:frontmatter cover / 正文本地图是否存在 / gallery 是否展开 / defaultCoverPic 是否配置。 - 重复封面日志?确认日志节流是否失效(3 秒窗口内只应出现一次)。 ## 13. 示例 输入 Markdown: ``` --- title: 示例 --- ![首图](assets/img/a.png) 正文... ![[b-second.jpg]] ``` 处理: 1. frontmatter 去除后扫描:首个匹配为 Markdown 图片 a.png → 自动封面 `![[a.png]]`。 2. a.png 转 wikilink (开启转换时) → 登记;b-second.jpg 登记。 3. 上传顺序:a.png → b-second.jpg。 4. 最终 HTML ``。 --- 如需补充“并发上传样例代码”或“封面过滤策略扩展”,请提出。