> For the complete documentation index, see [llms.txt](https://summerain-1.gitbook.io/summerain/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://summerain-1.gitbook.io/summerain/zh-cn/fa-bu-shuo-ming/v2.0.0.md).

# V2.0.0

> \[!IMPORTANT] **v2.0.0 是一个早期大版本。** 上传协议、浏览器图片管线、数据库结构、兼容行为与运维默认值仍可能频繁变化。请预期会有频繁的补丁版本，每次升级前都应阅读变更日志，并在生产环境固定精确版本或 OCI 索引摘要。首次启动 V2 前，请备份 MySQL 与完整图片卷。

此版本以资源感知的 V2 管线替换 V1 上传热点路径，面向与其他服务共享的 3 核、4 GB 主机上突发上传大图片的场景。现有 V1 图片仍可读取；新的 Web 上传则改用清单声明的客户端预处理、固定持久化变体、持久后台发布与有界清理。

本变更日志与 V1 对比的基线是标签 `v0.1.0`。

## 亮点

* 对新的 V2 Web 上传，将高开销的解码、缩放、格式转换与压缩从服务端移到浏览器。
* 持久化固定配方的 WebP 变体，而不是在首次读取时生成缩略图，从而为新图片消除 V1 的两次读取/两次写入热点路径。
* 新增可恢复、幂等的上传会话，以及由 MySQL 支持的持久发布任务、清理状态、存储沿袭信息与事务 outbox 投递。
* 保留 V1 图片服务，并为禁用 V2 的部署提供由服务端明确公布的 V1 上传回退。
* 为资源受限的共享主机增加有界部署配置、磁盘压力准入、有限的数据库和 Redis 连接池，以及增量清理。
* 通过可恢复且版本标签不可变的工作流，将可复现的多平台镜像发布到 Docker Hub 与 GHCR。

## 相对 V1 的完整变更日志

### 浏览器端图片处理

* 为静态 JPEG、PNG、BMP、WebP 与 AVIF 输入新增 V2 预处理。
* 解码前新增基于文件签名的格式检查，包括扩展名、MIME、尺寸、空文件与动画校验。
* 明确拒绝动画 PNG、WebP 与 AVIF。GIF 不是 V2 上传格式。
* 强制执行 15 MiB 源文件上限与 50 MP 源图片上限。
* 裁剪和缩放前新增识别 EXIF 的方向处理。
* 新增专用 `wasm-vips` worker 快速路径，支持 HEIF/AVIF 解码、单线程执行、禁用操作缓存，并限制被跟踪缓存。
* 新增基于 Pica、`ImageBitmap`、`OffscreenCanvas` 与 Canvas 2D 的原生回退。
* 上传前对每个生成部件执行运行时校验，确保它是完整的 WebP 容器。
* 新增 8 分钟客户端处理超时、取消，以及对 worker、bitmap、canvas、blob URL 与 object URL 的显式清理。

V2 生成以下固定配方资源：

| 资源               | 几何尺寸                  |    质量 | 生命周期与用途                        |
| ---------------- | --------------------- | ----: | ------------------------------ |
| `master`         | 方向校正后的原始尺寸            |    80 | 持久化全分辨率 WebP；owner/admin 可访问   |
| `gallery`        | 400x400 cover 裁剪      |    60 | 持久化的“我的图片”和控制台预览               |
| `admin`          | 120x160 cover 裁剪      |    60 | 持久化的图片管理预览，以 60x80 显示并提供 2x 密度 |
| `publish_source` | 保持宽高比，最长边不超过 2048     |    80 | 用于创建 `publish` 的临时上传部件         |
| `publish`        | 从 `publish_source` 派生 | 服务端输出 | 持久化的公开/私密发布资源，可带水印             |

* 上传清单新增 SHA-256、字节数、尺寸、质量、处理器版本与配方版本元数据。
* V2 不保留原始编码源字节；`master` 是全分辨率、质量 80 的 WebP 替代品。
* 所有持久化 V2 变体均为 WebP；AVIF 仅作为输入格式支持。

### 可恢复上传协议与浏览器恢复

* 新增基于会话的 `/api/v1/uploads` API，涵盖配方发现、初始化、部件上传、完成、单个和批量状态查询与取消。
* 新增必需的 `Idempotency-Key` 处理。同一 key 与清单返回现有会话，不同清单会被拒绝。
* 为初始化、传输、处理、完成、失败、取消与待清理新增持久会话状态。
* 对最多 100 个 ID 新增全有或全无的批量状态查询，不泄露缺失 ID 是否属于其他用户。
* 新增四部件可恢复清单，并严格匹配客户端与服务端清单。
* 新增精确到字节的 XHR 进度、2 分钟部件超时、带抖动的指数退避，以及最多 5 次部件尝试。
* 仅对明确标记为幂等的请求执行 CSRF 刷新与重放。
* 浏览器取消或部件最终失败后，尽力取消服务端会话。
* 完成恢复会先检查服务端持久状态，再重试结果不明确的完成响应。
* 新增重试分类，可继续轮询、复用当前尝试，或在终态/过期后创建新尝试。
* 新增自适应发布轮询：前 30 秒每 2 秒一次，2 分钟内每 5 秒一次，之后每 10 秒一次，总截止时间为 10 分钟。
* 浏览器一次只处理 1 张图片，上传管线并发为 2，每用户活动上传会话为 4。
* 部件传输以并发 2 开始，在能力充足、速度快且非按流量计费的连接上可增至 3。
* 一次尝试期间上传可见性固定，批次活动时不能更改。
* 客户端在预处理前读取 `/api/v1/uploads/recipe`，仅当服务端明确公布 `v2_enabled=false` 时使用 V1 multipart 路径。
* V2 启用时，不提供单文件原始源回退。无法使用 WASM 或有界原生路径的浏览器会收到明确错误。

### 后端上传校验与不可变存储

* 将每个部件直接流式写入暂存区，同时在单次遍历中计算 SHA-256 并校验字节长度、RIFF/WebP 结构、尺寸与动画。
* 拒绝截断容器、伪造的 RIFF 大小、尾随字节、意外几何尺寸、哈希不匹配、无效 MIME/配方字段及动画 WebP 部件。
* 不将完整 V2 部件加载到 Go 内存，也不会仅为格式校验而再次读取暂存文件。
* 将 `master`、`gallery` 与 `admin` 固化到不可变的内容寻址存储，复用已存在目标前验证其身份。
* 将高开销的目标预校验放在进程级容量闸门之外，同时保持事务与固化控制串行化。
* 完成操作幂等，并在一次持久转换中提交图片、固定变体、配额记账、发布任务与处理事件。
* 发布成功后删除 `publish_source` 和上传暂存目录；上传中间产物不会作为用户可见资源保留。

### 持久发布与水印

* 新增由 MySQL 支持的异步发布任务，具有优先级、重试上限、租约、续租、fencing token 与过期 worker 恢复。
* 防止丢失租约的 worker 提交或重新创建过期结果。
* 对耗尽重试的终态任务执行补偿清理，避免图片无限期停留在半发布状态。
* 完成操作创建发布任务时快照水印配置，使之后的配置变更不会影响已排队图片。
* 服务端水印仅应用于最终 `publish` 资源；客户端生成的 `master`、`gallery`、`admin` 不带水印。
* 使用原子水印替换、进程内锁、文件系统 `flock` 与恢复日志。
* 使用相同水印快照的任务可并发执行；历史快照在安全切换并恢复活动 SVG 时串行执行。
* V2 发布与 imgproxy 默认各使用 2 个 worker，并记录了资源更受限或争用更严重时降为 1 个 worker 的方案。

### 固定服务路由与 V1 兼容

* 新增固定 V2 路由：
  * `/i/<asset_link>.webp`
  * `/i/<asset_link>/master.webp`
  * `/i/<asset_link>/gallery.webp`
  * `/i/<asset_link>/admin.webp`
  * `/i/<asset_link>/publish.webp`
* 查询参数不会生成额外 V2 尺寸或格式。
* `master` 与 `admin` 需要 owner 或管理员授权；私密 `gallery` 与 `publish` 保留普通私密图片授权。
* 现有 V1 图片仍可通过原短链读取。
* 保留 V1 动态格式、宽度、高度与质量参数，尺寸上限为 4096。
* 现有无尺寸 WebP 文件与后台 AVIF 输出可以继续持久化。任意 V1 转换现在使用有界临时文件，并在最后一个等待响应结束后删除。
* 相同 V1 转换的并发请求共享一个 imgproxy 任务。
* V1 动态生成最多同时处理 2 个转换并排队 4 个转换 key，超出容量的工作返回可重试繁忙响应。
* 后台 V1 AVIF 生成限制为 1 个操作，执行响应大小上限，并使用临时文件加原子 rename 持久化。
* V2 启用时，旧版 `POST /api/v1/images/` 返回 `4262`；设置 `V2_UPLOAD_ENABLED=false` 会重新启用 V1 multipart 上传端点。

### 图片 UI 与后台管理

* 为本地处理、传输与服务端发布新增独立队列状态。
* 使用处理后的 `gallery` blob 预览替换原始源预览。
* 新增单文件重试、批量重试、离开页面时取消，以及确定性的 object URL 清理。
* 上传完成操作改为复制包含 V2 WebP 发布链接的固定 Markdown。
* 移除上传页中任意 URL/Markdown/BBCode/HTML 与原图/WebP/AVIF 输出的组合。
* “我的图片”和控制台预览现在对 V2 图片使用 `gallery`。
* 图片管理以 60x80 CSS 像素使用 `admin`，缩放查看使用 `master`。
* V2 详情提供固定 Publish、Master、Gallery 与 Admin 链接；V1 详情保留动态转换控件。
* 新增感知管线的 URL 解析，以及安全 token 查询参数/fragment 处理。
* 新增签发令牌后立即显示过期时间，并在吊销后本地清理。
* 为 active、suspended、pending-deletion 与内部 deleting 用户新增明确管理员行为。
* 明确 R2 设置变更不会迁移历史文件，必须使用独立迁移工具。

### 存储一致性、R2 沿袭与清理

* 为 CDN purge 与本地/R2 物理删除新增事务 outbox。
* 在删除权威业务行的同一 MySQL 事务中记录删除意图。
* 物理删除前，在共享存储锁下重新检查 `image_variants` 与 `image_files` 引用。
* 将 R2 404 响应视为成功的幂等删除。
* 新 V1 R2 上传首次远程 `PUT` 前创建持久清理意图。
* 为新 V1 对象记录精确的存储后端、endpoint 与 bucket。
* 按已存储的沿袭信息读取、下载与删除，而不是使用当前启用的 R2 target。
* 当记录引用其他 target、历史记录仍未分类或远程删除事件待处理时，阻止更改 endpoint 或 bucket。
* 校验 R2 endpoint 与公共 URL，拒绝嵌入凭据、query string、fragment 及非 HTTP(S) URL。
* 移除同步 `/api/v1/admin/r2/migrate` 路径；历史迁移保留给支持 checkpoint、审计与回滚的独立工具。
* 启动时不猜测或重写历史 V1 存储 target。
* 回滚后新增可重试的暂存区恢复，并增量恢复无引用的持久 V2 文件。
* 保留目录 cursor 并应用条目、时间与批次预算，避免将大型暂存区和孤立目录树无界扫描到内存。
* 保留失败的存储删除事件直到成功，而不是通过通用留存清理删除。
* 流式生成批量下载 ZIP，并对已压缩图片使用 store 模式，而不是在内存中组装归档。

### CDN、隐私与可见性

* 新增持久 Cloudflare purge 投递，以及通用带认证 webhook 回退，并限制批次、租约、速率与超时。
* 未配置投递目标时让 purge 事件保持待处理，而不是错误报告成功。
* 将公开原站缓存限制为 10 分钟，使主动 purge 不可用时可见性变更仍能收敛。
* V2 公开转私密时，将活动原站别名从 `<unique_link>` 切换为 `<unique_link>S`，并为旧公共 URL 排队 purge 工作。
* 私密转公开会移除私密别名、撤销有效访问令牌并 purge 受影响 URL。
* 私密响应在浏览器、代理与 surrogate 缓存头上始终保持 `no-store`。
* 在图片行上串行替换访问令牌，在并发请求期间保持每张图片只有一个有效令牌。

### MySQL 数据结构、恢复与账户注销

* 在数据库范围 advisory lock 与保留连接下新增有序、追加式 MySQL 迁移。
* 在 `schema_migrations` 中记录不可变 SHA-256 校验和；遇到未知、缺失、重排、修改或未应用的迁移历史时启动失败。
* 为 `images` 新增 V2 管线元数据，并增加 `image_variants`、`upload_sessions`、`upload_parts`、`processing_jobs`、`outbox_events`、全局容量状态、存储引用索引、留存索引与 R2 沿袭字段。
* 现有图片行保持 V1 且 completed；启动不会生成 V2 变体或移动历史文件。
* 将 `pending_deletion` 与内部 fail-closed 的 `deleting` 阶段分离。
* 在有界、可恢复阶段中执行账户删除，并等待活动上传与发布进入安全终态。
* 在删除前重新计算共享文件引用，并持久清理变体、去重文件、本地/R2 对象、会话、令牌、通知、审计数据与上传记录。

### 共享主机的资源管理

默认容器预算如下：

| 服务       |  CPU |       内存 | PID 上限 |
| -------- | ---: | -------: | -----: |
| 后端       | 0.75 |  640 MiB |    128 |
| MySQL    | 0.75 | 1024 MiB |    256 |
| Redis    | 0.15 |  192 MiB |    128 |
| imgproxy | 0.70 |  512 MiB |    128 |

* 在后端容器内设置 `GOMEMLIMIT=512MiB`。
* MySQL 限制为 8 个打开、4 个空闲应用连接，生命周期 30 分钟；Redis 限制为 8 个连接。
* Redis 使用 AOF、128 MB 数据集上限与 `noeviction`，让容量失败明确暴露，而不是静默删除控制状态。
* 新增 80% 和 90% 磁盘压力软/硬准入阈值。
* 上传预留包含所有声明部件，以及有界的 32 MiB 发布额度。
* 配额检查包含活动会话预留，避免并行会话分别花费相同剩余配额。
* 通过 MySQL 在后端实例间串行化全局容量，并用进程内准入闸门保护较小的数据库连接池。
* 默认全局允许 8 个并发部件上传、每用户 4 个，并允许每用户 8 个活动后端会话。
* 普通 JSON 请求体上限为 1 MiB，同时保留图片上传端点专用的流式上限。
* 为 header、read、write、idle 与大文件上传定义明确超时。
* 新增有界队列、健康检查启动期、优雅关停、PID 上限、日志轮转与 `no-new-privileges` 默认值。

### 安全与浏览器兼容性

* 大图片 WASM 处理路径默认启用 COOP/COEP 跨源隔离。
* 隔离启用时，在启动与运行期管理中拒绝 GeeTest v4；仍支持 `none`、reCAPTCHA 与 Turnstile。
* 新增类型化、带保护的 reCAPTCHA 与 Turnstile 集成，并提供明确的库不可用错误。
* 新增带认证、精确同源、感知 Fetch Metadata 的 CSRF 刷新，并允许有效令牌有界重叠，以处理乱序浏览器响应。
* Web 与设备认证对未知和破坏性账户状态 fail-closed。
* 拒绝绝对存储路径、路径穿越、逃逸 symlink 与非普通文件。
* 要求暂存区是持久存储的子目录，并应用受限本地权限。
* 根据观测到的 Chrome 93+ Android 兼容下限，将浏览器生产输出固定为 ES2020。
* 在运行时检测能力，而不是使用 UA allowlist。
* 50 MP WASM 路径需要跨源隔离、`SharedArrayBuffer`、Worker 支持，以及设备报告约 4 GB 或更多内存。
* 原生回退有意限制为：低内存设备 8 MP、典型或未报告内存设备 13 MP、报告至少 8 GB 的设备 16 MP，同时最大尺寸为 8192 像素。

### 部署、开发、依赖与 CI

* 从 Compose 中移除应用镜像构建；GitHub Actions 是唯一应用镜像构建器。
* 生产环境要求精确 `DOCKER_IMAGE` 标签或摘要，并记录 `pull` 加 `up -d --no-build` 部署方式。
* 将暂存区移到持久图片卷的 `/data/images/.staging`，以实现同文件系统原子固化。
* 新增 `storage-init` 服务，以受限权限为 UID/GID 10001 创建 V2 目录。
* 生产环境中的 MySQL、Redis 与 imgproxy 保持私有，仅后端绑定 loopback。
* 新增 `scripts/dev-wsl.sh`，支持只用 Compose 启动依赖、直接进行 Go/Vite 开发、本地 HTTPS 与同源 API/图片代理，不在本地构建应用镜像。
* 新增 `requirements.lock`，并与 Dockerfile、Compose、Go、npm、运行时设置和发布策略交叉校验。
* 锁定 Go 1.26.5、Node.js 24.18.0 LTS、Alpine 3.24.1、MySQL 8.4.10 LTS、Redis 8.8.0、imgproxy 4.0.11、libvips 8.18.3、Pica 10.0.2 与 wasm-vips 0.0.18。
* 将第三方 GitHub Actions 固定到完整 commit SHA。
* 在 GitHub Actions 中构建带 provenance、SBOM 与共享构建缓存的 `linux/amd64`、`linux/arm64` 镜像，并推送到 Docker Hub 与 GHCR。
* 普通提交发布 `edge` 与 `sha-<12-character-commit>`。
* 稳定 v2.0.0 发布 `v2.0.0`、`2.0.0`、`2.0`、`2`、`latest` 与 commit 标签。
* 保护精确 SemVer 标签不可变，同时让 `latest`、major、minor、`edge` 与 commit 别名可移动。
* 新增跨 registry 感知摘要的发布恢复。部分发布可从现有 descriptor digest 修复；精确摘要冲突会停止发布。
* 对 Docker Hub policy API 失败新增有界重试，并让普通 edge 发布不依赖该管理 API。
* 发布成功后将根 README 同步到 Docker Hub。

## 破坏性变更

* V2 启用时，新的 V2 上传必须在浏览器中预处理。
* V2 不保留原始编码源字节；`master` 是质量 80 的 WebP。
* V2 Web 客户端不支持动画图片与 GIF 上传。
* V2 只公开固定 WebP 变体；任意尺寸、质量与格式转换仅 V1 支持。
* V2 使用 `/i/<asset_link>.webp` 与固定变体路径，而不是通过查询参数创建新资源。
* AVIF 在 V2 中仅作为输入格式。
* 默认跨源隔离要求第三方脚本、字体与图片提供兼容的 CORS 或 CORP 头。
* GeeTest v4 要求禁用隔离，这会移除大图片 WASM 路径，不建议用于 50 MP 部署。
* 历史 R2 迁移不再是进程内管理员操作。
* MySQL、Redis、imgproxy、容器路径、非 root 身份与必需环境设置均有变化；未审查新示例前不要复用生产配置。
* 数据库迁移是追加式的，不提供自动 down migration。

## 从 V1（`v0.1.0`）升级

1. 从同一时间点备份 MySQL 与完整 `image_storage` 卷。
2. 将 `backend/.env.example` 中的新值合并到现有私有配置，不要替换 secret。
3. 设置 `DOCKER_IMAGE=jaykserks/summerain:2.0.0`，或使用已发布 OCI 多平台索引摘要。生产升级不要使用 `latest`。
4. 确保 `V2_STAGING_PATH` 是 `STORAGE_PATH` 的子目录；默认值为 `/data/images/.staging`。
5. 确认磁盘使用率低于 80% 软阈值，且 UID/GID 10001 可读取文件。
6. 复用现有服务卷前，检查 MySQL 8.4、Redis 8.8 与 imgproxy 4 的兼容性，并保留服务级备份。
7. 使用以下命令部署：

   ```bash
   docker compose --env-file backend/.env -f backend/docker-compose.deploy.yml pull
   docker compose --env-file backend/.env -f backend/docker-compose.deploy.yml up -d --no-build
   ```
8. 观察启动过程中的迁移校验和与配置错误，然后验证 `/health`、`/ready`、V1 图片访问、一次 V2 上传、固定变体、可见性切换与 CDN 行为。

现有 V1 记录与动态路由仍受支持。主服务不会批量转换历史图片。请保持当前历史存储 target 可用，直到独立迁移工具发布并完成自身的 checkpoint、审计与回滚流程。

## 回滚

* 将 `DOCKER_IMAGE` 改为之前的不可变标签或 OCI 索引摘要，并用 `--no-build` 重新部署。
* 优先只回滚应用。在较新服务版本打开数据卷后，不要原地降级 MySQL 或 Redis；需要降级依赖时应恢复兼容备份。
* 应用回滚期间保留追加式 V2 迁移及其 ledger。
* 完整状态回滚需要来自同一备份点的 MySQL 与图片存储。
* V1 无法理解所有由 V2 创建的变体、任务或沿袭记录。将 V1 回滚视为完成前，应验证升级后上传的图片。

## 已知限制

* 动画图片上传推迟到后续版本。
* 主服务和本版本不包含历史图片转换与存储迁移。
* V2 当前在本地存储中持久化固定 WebP 资源；本版本的 R2 沿袭改进主要保护 V1 兼容路径。
* 后端校验容器完整性、哈希、几何尺寸、MIME 与配方声明，但不会重新编码客户端输出，也不会独立校验视觉裁剪和实际编码质量。
* 大图片支持取决于浏览器与设备能力。浏览器可能支持输入格式，但仍无法在有界原生回退内处理。
* 缺少合适 CORS 或 CORP 头时，跨源隔离可能阻止第三方资源。
* CDN purge 投递需要配置 Cloudflare 或 webhook。未配置时事件保持待处理，10 分钟缓存上限是最坏情况下的收敛边界。
* 3 核/4 GB 配置是保守基线，并非通用吞吐保证。磁盘速度、水印复杂度、数据库延迟、CDN 行为与同机工作负载都会影响 10 分钟目标。
* 发布是异步的。客户端必须轮询持久上传状态，不能假定第四个部件上传完成时 `publish` 已存在。
* 这是早期大版本，补丁发布可能频繁。请固定精确标签、阅读每份变更日志，并避免在生产环境使用移动别名。

## 校验

* 后端 build、vet、module verification、完整测试与完整 race test 均通过。
* 前端 lint、生产构建与 94 个自动化测试均通过。
* Docker/Compose 配置检查、依赖锁校验、发布恢复测试、Docker Hub policy payload 测试、工作流 YAML 解析与 shell 语法检查均通过。
* WSL 开发栈已验证 MySQL、Redis、imgproxy 健康，后端 `/health` 与 `/ready`、前端 HTTPS、同源 API 代理及 COOP/COEP 头均正常。

## 比较

完整源码比较：`v0.1.0...v2.0.0`


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://summerain-1.gitbook.io/summerain/zh-cn/fa-bu-shuo-ming/v2.0.0.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
