Files
blog/docs/research/04-照片上传与管理管线论证.md
wait4xandGLM-5.3 0fbb023375 docs: 落库博客重设计调研报告 v1.8 与视觉样板 v2
调研收官产物入库:主报告(架构 C''=Astro SSG+gitea 双写作流、
Artalk 评论、分片 webfont、双语预译管线、多端适配规格、
Phase 0-5 实施规划)+ 五份侦察原稿 + 可交互单文件样板
(三路线×明暗×五页型,命令面板/终端/热力图/masonry)。

Co-authored-by: GLM-5.3
2026-10-24 21:48:11 +08:00

147 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 照片管线与管理界面 · 四路线实测论证报告
> 侦察员 @agent-4f37d7 | 2026-10-24 | 博客架构 = Astro SSG + 自建 gitea;照片库存 21 张;需求 = **上传管理界面 + 图库按宽高比自适应**
> 所有结论基于 2026-10-24 实抓的官方源码 / OpenAPI spec / GitHub API / README,标注「推断」处为合理推理而非实抓。
---
## TL;DR
| | P1 gitea 原生 + 构建时处理 | P2 自建/现成管理端 | P3 Immich | P4 自托管图床 |
|---|---|---|---|---|
| 上传体验(手机) | ⭐⭐⭐ 拖拽+多选,手机走系统相册(推断) | ⭐⭐⭐⭐⭐ Sveltia 移动支持(自述)/自写可 PWA | ⭐⭐⭐⭐⭐ App 自动备份(但不是进博客仓) | ⭐⭐⭐ 网页上传 |
| EXIF/缩略图 | 构建时 exifr + sharp,宽高比天然可得 | 入库前处理完(自写)/ Sveltia 无 EXIF 提取 | ⭐ API 直出全套 EXIF | 部分自带,非结构化 |
| 数据归属/版本控制 | ✅ git 仓 | ✅ git 仓 | ❌ Immich DB | ❌ 图床自有存储 |
| 新增运维组件 | **零** | 零(Sveltia 纯前端)或 1 个小服务 | 重型全家桶 | 1 个 PHP 应用 |
| 工作量 | 半天(一个 loader 脚本) | 0~3 天 | 1 天(仅当已有 Immich) | 1 天部署 + 长期割裂 |
**推荐**:P1 起步(当天可用)→ 管理面先用 Sveltia CMS 试水(Gitea 后端已是一等公民)→ 不合手再自写照片专用小服务(可开源)。P3 仅当本来就部署 Immich;P4 不推荐。
---
## P1 · gitea 原生上传 + 构建时处理
### 上传能力(实抓 gitea main 分支源码)
- **Web UI 上传页存在且走 dropzone**:`templates/repo/editor/upload.tmpl` 定义 `repo-file-upload` 表单,内嵌 `templates/repo/upload.tmpl` 的 dropzone 容器(`data-accepts` / `data-max-file` / `data-max-size`);`web_src/js/features/repo-editor.ts:64-65` 对 `.repository.editor.upload .dropzone` 调 `initDropzone`。
→ **拖拽 ✅、多文件 ✅**(dropzone 标准能力);入口 = 仓库页「Add File → Upload Files」,需选目标目录+写 commit message。
- **默认限制(`modules/setting/repository.go` 约 170-177 行,实抓)**:
```go
Upload: struct { Enabled bool; AllowedTypes string; FileMaxSize int64; MaxFiles int }{
Enabled: true, AllowedTypes: "", FileMaxSize: 50, MaxFiles: 5,
}
```
→ 默认**单次 5 个文件、单文件 50MB、不限类型**;`[repository.upload]` 可调大(照片场景建议 `MAX_FILES` 调到 20+)。
- **手机浏览器**:dropzone 底层是标准 `<input type=file>`(可多选),手机 Safari/Chrome 弹系统相册、支持多选——**推断**(未实机测)。
- **API 上传(实抓 swagger.v1.json,953KB)**:
- 单文件:`PUT/POST /repos/{owner}/{repo}/contents/{filepath}`,内容 base64;
- **多文件批提交:`POST /repos/{owner}/{repo}/contents`**(summary: *Modify multiple files in a repository*),body = `ChangeFilesOptions{ files: [{operation: create|update|upload|rename|delete, path, content(base64), from_path}], branch, author/committer, dates }`——脚本化上传/搬移/删除全覆盖,且一次 commit 完成多文件。
- 仓库 webhook:官方文档站有 `/usage/repository/webhooks/` 页(目录实抓),可触发构建。
### 构建时 EXIF + 缩略图(实抓 Astro 官方文档与源码)
- **astro:assets 默认 image service = sharp**(官方 images 指南 L670-672),`<Image />` 构建时转换尺寸/格式/质量,**自动推断宽高写进 `<img>` 防 CLS**(L211/214)——缩略图多尺寸(`srcset`/responsive layout)**官方原生,零自写**。
- **宽高比能力(关键,正中需求)**:导入图片的 `ImageMetadata = { src, width, height, format, orientation }`(images.mdx L497-503);源码 `packages/astro/src/assets/utils/metadata.ts` 实证:用内置 image-size 探测,**orientation ≥ 5 时自动交换 width/height** → 拍竖片时宽高比正确。masonry 组件构建时直接读 `width/height` 算 `aspect-ratio`,零运行时 JS。
- **能力边界(如实)**:astro:assets **只给尺寸/格式/方向,不吐相机/镜头/光圈等 EXIF**。这一层要 `exifr`(npm v7.1.3,**零依赖**,npm registry 实查)在 content collection loader 或 prebuild 脚本里提,写入数据层(20 行级别代码)。
- **public/ 与未授权远程图不处理**(images.mdx L16/36/49):照片必须放 `src/` 下(或经 `image.domains` 授权)才享受缩略图管线。
- **Astro 生态现成「照片上传→EXIF」集成**:npm 搜 `astro exif` 无官方/主流插件;GitHub 上仅有小模板(如 hant121/memori ★9 静态相册模板)。**没有一等公民现成集成,但缺的部分(exifr 提取)是 20-50 行的事。**
### 评估
- 上传体验:能用但纯 gitea——无缩略图预览、无 caption/alt 编辑、无分组管理;commit message 手写。
- 数据归属:✅ 21 张原图进 git 仓(21 张对仓库体积无压力;数百张后再谈 LFS)。
- 新增运维组件:**零**;构建时间 += sharp 处理 21 张(秒级,可忽略)。
- 工作量:**半天到一天**(photo collection loader + exifr + masonry 组件)。
---
## P2 · 自建上传/管理端点
### 现成开源「git-based 媒体管理器」搜索结果(GitHub API 实查)
**没有专精「git 照片管线」的主流项目**(搜 `git media manager upload`、`gitea cms` 等只出 teabag ★6——Static CMS 的 gitea 认证 shim,2023 停更;nbg ★1 等)。**但存在重要降维解**:
- **Sveltia CMS(sveltia/sveltia-cms,★2990,2026-10-24 仍在推提交)**:自称「Netlify/Decap CMS 后继,feature complete」(README 实抓 + discussions #957)。badge 自述:Fast / Git-based / Headless / Modern UX / **Mobile Support** / i18n。
- **Gitea 后端是一等公民(源码级实锤)**:GitHub contents API 列出 `src/lib/services/backends/git/providers/` 下 **`gitea` `github` `gitlab` 三目录并列**。
- 活跃度佐证:v0.251.0(2026-10-23)release notes 原文 "…approves its pull/merge request on GitHub, GitLab, **Gitea or Forgejo**"。
- 风险如实记录:Gitea 后端相对新——#1031(2026-10-01 才关闭)曾因 "Gitea 28 被误识别为 Forgejo 导致拉文件 404";#721(已关)涉 token 认证。**self-host gitea + Sveltia 的组合需实测后才能定案**(本次未能实机验证;sveltia-cms.app 文档站 SSL 握手失败,抓取未遂,已如实标注)。
- 能覆盖:图片上传(media folder 入 git 同 commit)+ markdown/frontmatter 编辑(caption/分组/alt 可做成字段)→ **「管理界面」需求的开箱即用候选**。不覆盖:EXIF 自动提取、缩略图预览策略定制。
### 自写小服务成本评估(基于 P1 实抓的 API 面)
管线 = 拖拽上传 → 服务端 exifr 提 EXIF + sharp 出缩略图预览 → 元数据表单(caption/分组)→ 组装 frontmatter + 原图 → gitea `POST /contents` 多文件一次 commit → webhook 触发构建。
- 技术面全部有实抓依据(contents 多文件 API、webhook、exifr、sharp),无未知阻塞;
- 工作量:**Node/Go 约 300-600 行,2-3 天到「自用够用」**;真正成本不在上传而在管理面(列表/编辑/删除/分组/精选标记);
- 手机:标准 `<input capture>` + PWA manifest 可达「拍照→直接进仓」;
- **自写最大红利:这个工具本身就是开源作品**(用户热爱开源 + 博客本就开源在 gitea 的定调),且可按自己的 21 张照片的真实工作流打磨。
---
## P3 · Immich 集成
### API 能力(实抓 immich-openapi-specs.json,973KB,199 路径)
- 认证:`x-api-key` header(securitySchemes 实查);
- `GET /albums`、`GET /albums/{id}`、`GET /assets/{id}` → `AssetResponseDto` **内含 `exifInfo`**;
- `ExifResponseDto` 字段全:make / model / **lensModel** / iso / fNumber / exposureTime / focalLength / dateTimeOriginal / GPS 经纬度 / orientation / city/state/country —— **EXIF 直出,结构化,比自提还全**;
- `GET /assets/{id}/thumbnail`(preview 尺寸)+ `/assets/{id}/original` 原图下载;
- `POST /search/smart`、`/search/metadata`(按相册/标签/日期筛);`/shared-links` CRUD(公开分享面)。
- 生态:eleventy-immich(★6,2026-10-21 仍活跃)、若干 astro+immich 小项目(★0-1)——**模式真实存在但都小众,无大一统方案**。
### 代价评估(如实)
- **数据不在 git**:精选集 = Immich 里的相册状态,改精选不改博客仓;缓解办法 = 构建时把拉到的元数据 JSON 提交进 content collection(照片本体仍可 gitignore),得到「元数据版本化 + 原图在 Immich」的折中;
- 构建时强依赖 Immich 在线(静态站本身不受影响,发布链路受影响);
- **新增运维组件最重**:Immich 全家桶(server + ML + Postgres + Redis),为 21 张照片引入纯属杀鸡用牛刀——**除非本来就部署 Immich 管理手机相册**(若已有,P3 成本骤降为 1 天集成);
- 公开图库还需管理 API key / shared link 暴露面。
---
## P4 · 自托管图床(Lychee / Chevereto / EasyImages)
### 实抓现状
| 项目 | 状态(2026-10-24 实查) | 要点 |
|---|---|---|
| LycheeOrg/Lychee | 活跃,**v7.0**(7.0 docker 镜像有 breaking change,README 升级公告),PHP 8.4/8.5,MIT | 功能完整的相册管理;**与静态博客集成的真实案例:未找到**(如实标注) |
| chevereto/chevereto | 活跃(pushed 2026-10-21,★1002,默认分支 4.5,**AGPL-3.0**,README 自述 "pro edition" 商业功能分层) | 老 chevereto-free 已退役(README 挂 Legacy 徽章指向 rodber/chevereto-free);为 21 张照片过重 |
| icret/EasyImages2.0 | README 实抓:PHP 5.6-8.0、**无数据库**、GPL-2.0,支持 API/多文件上传/在线管理/水印/压缩/格式转换,定位「游客上传和个人使用」 | 无版本控制;无结构化 EXIF 输出;PHP 上传类工具历史上有上传绕过类漏洞记录(**评估性提醒,未逐一核 CVE**),若用必须隔离部署 |
### 共同问题
- **版本控制缺失**:图片在图床自有存储(文件系统/DB),文章与图的关系无 git 级完整性——图床端误删/误改,博客构建照样成功、线上静默坏图;备份责任独立于博客仓;
- 唯一优点 = 构建不依赖图床在线(引用 URL 即可);
- 与「数据可控、全自托管、最少外部依赖」的选型轴相悖度最高(多一个有状态服务,且该服务只服务照片这一条内容线)。
---
## 推荐分阶段路径
**阶段 0(当天可用)— P1 全家桶**
1. 照片进仓:`src/assets/photos/`(或 `content/photos/` collection),gitea web 上传页拖拽(先把 `[repository.upload] MAX_FILES` 调到 20+);API 通路 `POST /contents` 多文件批提交留给脚本;
2. 构建时:content collection loader 用 **exifr** 提 EXIF(相机/镜头/参数/拍摄时间)进数据层;**sharp**(astro:assets 默认)出多尺寸缩略图 + `srcset`;
3. 宽高比自适应图库:masonry 组件从 `ImageMetadata.width/height`(已处理 orientation)算 `aspect-ratio` + grid row-span,零运行时 JS;lightbox 用原生 `<dialog>`。
→ 交付物 = 能用的图库页;新增代码约一个 loader + 一个组件。
**阶段 1(要管理界面时)— 先 Sveltia 后自写**
1. 零成本试 **Sveltia CMS**(纯前端 SPA,无新运维组件,Gitea 后端一等支持):把照片目录配成 media folder、caption/分组配成 frontmatter 字段;**验收点:self-host gitea 登录认证 + 媒体库上传 21 张照片实测**(Gitea 后端 2026-10 初还有兼容 bug 修复记录,需过一遍真机);
2. Sveltia 不合手(比如要「上传即预览缩略图+EXIF 自动回填」的照片专用体验)→ 自写 **photo-pipe** 小服务(上传→EXIF→缩略图预览→元数据表单→`POST /contents` 一次 commit→webhook 构建,PWA 手机可用),**作为独立开源项目发布**——这是「热爱开源」的最自然表达,且有 P1 打底保证任何时刻可退回纯手工流程。
**不推荐**:P4(数据归属最弱、与 git 哲学相悖);P3 仅在「本来就部署/计划部署 Immich 管手机相册」时启用,届时构建时拉精选相册 + EXIF 直出是省力方案。
---
## 未验证项与残余风险(如实标注)
1. gitea 上传页**手机浏览器实机**未测(理论上标准 file input 可用);
2. **Sveltia × self-hosted gitea** 未实机验证(认证流、媒体库体验、#1031 类兼容问题是否复发);sveltia-cms.app 文档站本次抓取失败(SSL),功能细节以 README/issues/release notes 为据;
3. homelab gitea **当前运行版本**未知——contents API 与 upload 页均为老牌功能(1.x 早期即有),风险低,但 `[repository.upload]` 可调项以实际版本 config cheat sheet 为准(main 分支源码与文档站页面结构本次均核过,cheat sheet 内容段未单独核);
4. Immich 版本迭代快、API breaking change 风险未逐一考证(API 面本身已实抓确认);
5. EasyImages 安全记录未逐一核 CVE,仅有泛化提醒;
6. astro-imagetools 仓库 API 查询未返回(疑改名/迁移),生态备选项未深究——不影响主方案(astro:assets 原生已够)。
## 抓取物存档
`/tmp/scout-photos/`:gitea(upload.tmpl / repo-editor.ts / dropzone.ts / setting-repository.go / swagger.v1.json)、astro(images.mdx / assets metadata.ts)、immich(openapi specs)、p4(lychee / easyimages / chevereto README)、sveltia(README / releases)。