6.9 KiB
6.9 KiB
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 转换保障兼容性。
2. 图片来源与归一化
| 来源 | 输入示例 | 归一化策略 | 备注 |
|---|---|---|---|
| Wikilink | ![[foo.png]] / `![[foo.png |
120x80]]` | Marked 扩展直接生成 <img src="vault://..."> 并登记 |
| Markdown |  |
(可选) 预处理转换成 ![[foo.png]] |
依赖设置:enableMarkdownImageToWikilink |
| 远程 URL | <img src="https://..."> |
解析为远程上传任务 | 跳过已是微信域名 |
| Base64 | <img src="data:image/png;base64,..."> |
解析成 Blob 上传 | 生成虚拟 id 构造文件名 |
| Mermaid | 源码块 → SVG/DOM | 转 PNG (html-to-image) / 留 SVG | 替换占位容器 |
| Excalidraw | ![[diagram.excalidraw]] |
远程接口取 SVG 或转 PNG | 需 AuthKey |
| SVG 文件 | ![[icon.svg]] |
内联 SVG 内容 | 直接嵌入,不上传 |
3. 关键数据结构
interface ImageInfo {
resUrl: string; // 初始占位或本地资源 URL (vault path 映射)
filePath: string; // Vault 内实际文件路径
url: string | null; // 上传后微信返回 URL
media_id: string | null; // 上传后素材 ID(type=image 时)
}
管理容器:Map<string, ImageInfo>,键值为 resUrl(初始唯一标识)。
4. 收集阶段
- Marked 扩展 (
LocalFile.markedExtension) 在 token 遍历时识别 LocalImage。 - 根据 wikilink / 尺寸参数解析出真实 vault 文件路径:
assetsManager.getResourcePath()。 - 调用
LocalImageManager.setImage(res.resUrl, info)建档。 - Markdown 图片(标准语法)若转换启用,提前转成 wikilink 进入同一逻辑;否则需未来扩展 tokenizer 直接登记。
5. 上传阶段
顺序:
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
匹配 <img src="http...">:
- 跳过已在微信域名(
mmbiz.qpic.cn)。 - 下载 → Blob → (webp 转换) → 上传。
- 更新 Map,键为原始 src。 Base64:
- 解析 data URI → 生成 Blob/扩展名 → 上传。
5.3 替换阶段 replaceImages
- 遍历 DOM
<img>→ 查询 Map → 使用上传后url覆盖src。 - 未登记:输出警告(潜在渲染管线遗漏)。
6. 自动封面策略
触发点:getMetadata() 中,若 frontmatter 未给出封面且无 thumb_media_id。步骤:
- 使用
originalMarkdown(含 frontmatter 原始文本拷贝)。 - 去除 frontmatter 块。
- 分别用正则匹配:
- Markdown:
/!\[[^\]]*\]\(([^)\s]+)\)/g - Wikilink:
/!\[\[(.+?)\]\]/g
- Markdown:
- 过滤远程 URL(仅保留本地引用或无协议路径)。
- 生成候选
{ idx, basename }列表,按出现位置排序。 - 第一项 →
![[basename]]写入res.cover。
Edge Cases:
| 情况 | 处理 |
|---|---|
| 所有图片都是 http(s) | 不作为封面候选 |
| 文件名带 query/hash | 使用前切分去除 ? # |
| markdown 图片后缀非图片 | 跳过 |
| 重复图片 | 以最先出现的为准 |
7. 正则索引
| 目的 | 正则 | 描述 |
|---|---|---|
| Frontmatter 删除 | /^(---)$.+?^(---)$.+?/ims |
首段 YAML |
| Markdown 图片 | /!\[[^\]]*\]\(([^)\s]+)\)/g |
捕获路径 group1 |
| Wikilink 图片 | /!\[\[(.+?)\]\]/g |
捕获内部资源 |
| 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 去重避免重复上传 |
12. 快速检查清单 (Debug Checklist)
- 图片是否在
LocalImageManager.imagesMap 中? - 是否执行了
cachedElementsToImages()(Mermaid/Excalidraw)? - 上传后
media_id是否为空?(格式/大小不合规) - DOM 替换后
<img src>是否为微信域? - 自动封面未生效?检查:frontmatter cover / 有无本地首图 / 正则是否截获远程 URL。
13. 示例
输入 Markdown:
---
title: 示例
---

正文...
![[b-second.jpg]]
处理:
- frontmatter 去除后扫描:首个匹配为 Markdown 图片 a.png → 自动封面
![[a.png]]。 - a.png 转 wikilink (开启转换时) → 登记;b-second.jpg 登记。
- 上传顺序:a.png → b-second.jpg。
- 最终 HTML
<img src="https://mmbiz.qpic.cn/...">。
如需补充“并发上传样例代码”或“封面过滤策略扩展”,请提出。