存档图片默认云端保存:产品与工程建议
状态:用户已批准云存档方案及资源分区预览 A。发布代码已实现,生产数据库安装和真实存储验证已完成,按试点方案发布。本文保留设计时的调查与建议;实际实现、限制和上线步骤见 实施说明。 用户要求先给方案,不预设预算。下列额度和试点指标均为建议,不是已上线限制或费用承诺。
建议
建设平台统一的、默认私有的存档图片能力。新上传的游戏图片自动进入该能力,跨设备读取、检查点、分支和分享使用同一份受控引用。
复用现有对象存储、直传流程、账户容量和分享权限,补齐会话归属、图片版本、配额预留、私有读取、迁移及清理。 现有通用 api.storage 保留兼容语义,不通过猜测任意字符串内容来自动公开或迁移所有本地数据。 新卡片通过统一 SDK 默认获得云端图片能力;旧卡片逐类适配,不能承诺任意 FileReader/base64/blob 实现零改动兼容。
已查明的基础
代码已有:
- S3 兼容对象存储;配置注释与默认端点对应 Railway Storage Buckets。
- 预签名直传与用户素材库;已有账户级配额:Free 100 MiB、Gold 500 MiB、Platinum 2 GiB、Diamond 5 GiB、Ascendant 20 GiB。
- Sharp 图片处理,但当前处理会在繁忙/错误/部分格式/小图片时跳过,不适合作为新功能的强制安全校验。
- 会话状态、检查点、分支及不可变分享快照。
- 工作区内
pickChatImage、patchVariables包含未提交改动,不能视为线上部署事实。
代码缺口:
- 当前
/cdn/:assetId和允许前缀的/cdn/key/:encodedKey无需登录;仅增加私有图片接口而保留旁路仍会泄露。 - 当前配额入口检查已有用量,登记字节数来自客户端;缺少上传预留与最终对象核验。
- 当前用户素材删除可直接删除 S3 文件,没有存档/分享保留语义。
- 当前浏览器内写队列不解决不同设备之间的并发写入。
- 当前图库若放进 AI 可读的普通变量,可能进入每轮提示词。
- 当前图片读取由应用服务代理返回,缓存未命中的流量会经过应用服务。
实际规模:只读聚合结果
来源:用户提供数据库的 user_assets 和 play_sessions,仅聚合计数与大小,未读取私人对话或玩家图片。
| 指标 | 查询结果 |
|---|---|
| 登记图片 | 20,938 张 |
| 有图片的用户 | 4,105 人 |
| 登记图片大小 | 16,294,697,140 字节,约 15.18 GiB |
| 近 30 天登记图片 | 11,650 张,约 7.47 GiB |
| 单图中位数 | 约 197 KiB |
| 单图 P95 | 约 3.04 MiB |
| 有图片用户的图片总量 P95 | 约 8.43 MiB |
| 近 30 天有存档更新的用户 | 14,851 人 |
限制:这些是数据库登记量,不是完整存储桶清单或已核验账单。它们包括现有用户素材,不能直接当成未来游戏图库的使用分布。未包含所有世界素材、孤儿上传、可能保留的对象版本等;客户端上报大小也可能不准确。
成本模型
按 Railway Storage Buckets 官方公开价作条件估算:存储 $0.015/GB-month,桶 API 操作和桶出站免费;Railway 应用服务出站另计 $0.05/GB。实际使用的供应商、计量单位、缓存命中率和账单需在上线前核对。
假设每个使用者平均保留 50 MiB,包含主图和缩略图,保持整月:
| 使用者数量 | 大约存量 | 纯对象存储月费估算 |
|---|---|---|
| 1,000 | 49 GiB | $0.73 |
| 10,000 | 488 GiB | $7.32 |
| 100,000 | 4,883 GiB | $73.24 |
这些不含图像处理、服务流量、边缘鉴权、数据库、备份、监控和内容处置。50 MiB 是累计平均存量假设,不是每月新增量。若不断新增,费用会累积直到用户配额限制增长。
若有约 1,000 个计费 GB 的图片字节经过 Railway 应用服务发出,仅服务出站约 $50。浏览器直传桶、鉴权后短期直读桶,可以避免主要图片字节经过聊天服务;图片校验/转码写回仍有处理和流量成本。
不建议为这一功能先换存储供应商。现有方案的容量单价足够低,优先解决字节传输路径、处理次数、异常上传和数据重复。所有提价/新增套餐空间均留到试点得到实际成本后评估。
第一版产品规则
- 登录用户主动选取并保存到游戏的图片默认云存档;不是后台扫描文件,也不自动搬运所有浏览器缓存。
- 文件归上传者所有;关联到具体会话。作者不能因为拥有世界而读取玩家私人图片。
- 账户现有容量统一计费;同账号同一文件被多个存档、检查点、分享引用只计算一份物理存储。跨账号另行保存必须有各自授权和逻辑配额,不能通过分支无限规避额度。
- 普通上传不消耗 AI Mushies。生成图片仍遵循独立生成计费,不与存储混为一谈。
- 满额后保留已有图片可读,暂停新增并清晰提示。套餐降级不自动删除,不允许旧账号继续无限增加字节。
- 第一版只支持静态 JPEG/PNG/WebP。动画、视频、大文件和云盘同步不纳入首发;已有素材能力保持原状。
- 建议初始原始文件上限 16 MiB,并另设解码像素上限;主图最长边目标 2048px,生成一份约 320px 缩略图。目标主图数百 KiB,建议最终单图硬上限 2 MiB;具体压缩质量须通过文字地图、透明图片、肖像和像素图样本验证。
- 默认仅保留适合游戏显示的处理后主图及缩略图,不永久保留手机原始大图。试点文案明确这一点;不批量压缩/覆盖历史素材。不能满足质量要求的文件明确失败,不能偷偷返回未校验原图。
- 去除定位等附带元数据、保留方向和透明度;真正解码核验格式和尺寸,不能只信扩展名、客户端 MIME 或文件大小。
最小完整架构
复用现有存储组件;新增的是权限、配额和引用关系,不新建第二套素材云盘。
玩家选图
-> 平台确认会话权限、预留容量、签发短期上传权限
-> 浏览器直传私有临时对象
-> 服务端核验、有限并发处理、生成不可变主图/缩略图
-> 事务内登记实际大小、绑定当前会话、释放预留
-> 返回稳定的媒体 ID,标记已保存
打开存档 / 分享
-> 校验当前访问者对会话 / 分享的权限
-> 根据已登记媒体引用换取短期读取地址
-> 图片字节由对象存储返回,不由聊天服务持续转发持久保存媒体 ID,不持久保存会过期的读取签名。图片不存进 PostgreSQL 的大段 base64,也不默认进入 AI 提示词。图库标题/人物绑定等属于有大小限制、独立于模型状态的媒体元数据。
最少需要的逻辑记录:媒体所有者和对象版本、实际大小与状态;会话/检查点/分享引用;上传配额预留。优先扩展现有素材记录和引用表,实际表结构在实现前根据现有约束确定。
上传对象不可成为永久可覆盖的正式文件:临时上传授权不能重写已校验内容;完成后使用没有暴露 PUT 权限的正式不可变对象。失败重试使用幂等标识,不能生成重复记录或重复扣配额。
容量必须校验 已用 + 已预留 + 本次上限,并在事务内更新;完成后使用服务端实际字节结算。过期/失败预留要释放。平台还要限制上传频率、待处理任务数和账户新增字节速率,避免反复上传删除绕过存量限制。
私有素材必须同时堵住 legacy CDN 的按 ID 和原始 key 两条旁路;使用隔离前缀或桶及服务端访问检查。不能只把 URL 变长或称为不可猜测。
第一版建议短期签名读取,URL 在有效期内属于持有者可访问的凭据,不可当成完全不可转发的权限体系。撤销分享会阻止新签名,已有签名需在明确的短期窗口内失效(例如不超过 5 分钟)。已下载的图片无法远程收回。如果需要即时撤销,应使用受控的边缘鉴权读取并计入成本,而不能承诺短期签名做不到的保证。
同步、快照和删除
资源管理入口(用户补充要求)
- 在现有「资源」内新增「存档图片」分区,与创作素材分开展示;两者共用账户总容量,不创建第二份额度。
- 支持按存档、使用状态筛选,按文件大小排序,多选管理;显示实际大小、关联存档、检查点及分享。
- 删除前查询最新引用及版本。仅移除当前存档关联时,清楚说明仍占用的容量;彻底删除前显示受影响的引用范围。
- 删除完成且对象清理得到确认后,更新容量。异步清理中显示待释放,不提前声称已经释放。
- 无引用图片显示回收期限,仍计入已用空间;支持提前彻底删除。已有引用的图片不可误列入「未使用」。
- 列表分页,缩略图按需签名与加载;不得因打开资源页而读取整个图库或请求所有原图。
- 「存档图片」默认私有,不继承创作素材的公开分享或复制永久 CDN 链接操作。
- UI 预览方案 A:资源内双标签 + 图片网格 + 引用详情;方案 B:资源分类与存档侧栏 + 文件列表。两者均预览删除确认及占用变化;视觉批准后再接入实际界面。
生命周期规则
- 图片新增/移除使用逐项操作、幂等 ID 和服务器版本检查,不采用整库最后写入覆盖。
- 图片二进制一旦完成不可变;替换图片生成新版本。设备可以缓存文件,但云端决定当前引用关系。
- 分享冻结当时的引用集合,后续在私人存档新增图片不会自动进入旧分享。
- 分享只授权这份快照相关图片,不授权整个账户素材库;已有年龄/访问范围限制必须同时作用到媒体读取。
- 检查点恢复/分支复制引用集合,保持原图版本;图像字节不因每次存档重复复制。历史引用也必须受容量与保留规则约束。
- 从单个图库移除仅解除该处关联。仍被用户其他存档/检查点/分享引用的图片继续占账户容量。
- 完全无引用的文件进入延迟回收,例如 7 天可恢复期;临时/未完成上传更短,例如 24 小时。恢复期内物理字节仍有成本,不得在服务端完全忽略。
- 明确“彻底删除”与普通移除不同:撤销所有受影响引用、停止新授权、执行清理;其他分享显示图片已删除。账户注销及内容下架不得被分享引用无限阻止。
- 不对跨账号复制做首发全局去重,减少授权、删除、配额与存在性泄漏问题。同账号内容哈希去重足够。
旧卡片和迁移
新卡片的生成指引、SDK 和平台上传控件统一走新机制。旧 api.storage 用途包括缓存与跨会话偏好,首发不能直接改语义。
先适配 Oncin 这一已知图库格式:识别当前会话的图库条目,从原设备读取图片;按稳定项目 ID/内容哈希逐项上传并合并;服务器确认绑定后记录迁移状态。PC/手机分别执行时不能整库覆盖。
需要防范旧存储只有 worldId、缺少账号归属的问题:共享浏览器或切换账号时,不能默认把所有旧数据归给当前账号。只迁移经确认属于当前会话的数据,无法判定时要求用户选择。
迁移失败保留本地原件,重试不重复创建;离线只能显示本地待同步,不能显示云端已保存。关闭页面时可提示未完成,但不能把阻止离开作为唯一可靠性机制。
新机制不承诺支持任意网页自己放在临时内存/Blob URL 的图片,也不自动抓取任意远程图片 URL。后者额外引入服务端请求、版权和访问权限问题,不属于首发。
分阶段实施与放量
阶段 A:上线前基线与基础约束
- 核对真实供应商、桶容量/孤儿对象、实际账单、图片请求数、源站服务流量、缓存命中率。
- 实现私有访问和 CDN 旁路拦截、配额预留/完成核验、图片限额与不可变版本。
- 复用现有上传流程与存储 SDK;不改套餐,不购买/迁移供应商。
阶段 B:完整链路的受控试点
- Oncin 加少量不同图库实现的创作者;平台能力先按账号/世界开关开放。
- 同时完成跨设备、只读分享、删除、迁移、满额及失败重试,不以“只上传成功”视为试点完成。
- 建议观察至少 7 天并覆盖足够成功与失败样本;不是仅凭时间到就自动放量。
阶段 C:默认推广
- 新建卡片和已适配卡片从 10% 放量到全部;未知旧格式继续保留原行为,并提供适配路径。
- 功能开关可暂停新上传和新迁移,已保存图片仍可读取。不能回滚成只读本地导致云端数据消失。
- UI/提示文案变化依用户规则先做具体预览;当前建议本身不代表视觉批准。
放量前必须验证
| 场景 | 验收要求 |
|---|---|
| PC 新增,手机重开 | 读取同一媒体与元数据 |
| PC/手机同时新增、删除、改标题 | 无静默覆盖,冲突可识别 |
| 上传完成但确认请求丢失 | 重试同一记录,不重复扣费 |
| 断网/满额/转码失败 | 保留原件,明确未保存 |
| 两个并发上传抢最后容量 | 配额不能被并发突破 |
| 更换账号或猜测媒体 ID/key | 无权访问;CDN 旧路由不可旁路 |
| 作者访问玩家存档媒体 | 不因世界所有权而获得权限 |
| 分享后私人会话新增/替换图片 | 原分享内容不变 |
| 撤销分享/彻底删除 | 无新授权,已签 URL 在约定窗口失效 |
| 检查点、分支、恢复 | 引用版本正确,无复制倍增 |
| 清理临时对象与无引用文件 | 不删除活跃会话或有效分享的图片 |
| PC/手机分批迁移 | 无丢图/重复,旧账号数据不串入 |
| 恶意大尺寸/伪造 MIME/动画 | 有界资源使用,拒绝不支持内容 |
必须补自动化并发、权限、快照与故障注入测试;UI 操作与低网速/跨设备验收在用户授权浏览器验证后执行。
观测:成功率、确认延迟、转码失败、配额预留泄漏、每用户实际字节、每图处理成本、临时对象积压、桶与服务的流量、分享读取量、被拒绝请求。日志只存标识/大小/错误码,不存图片字节、签名 URL 或私人标题。
成本保护以分层限速、处理队列和暂停新增为主;不能因为预算预警删除玩家已有数据。按照实际单用户存量和读取分布再制定费用目标,当前不预设月预算。
首发不做
- 全站历史 localStorage 自动同步:无法可靠确定内容类型、归属与分享范围。
- 任意文件、视频、动画及无限原图备份:成本/处理边界不同,另行评估。
- 跨账号全局文件去重:首发收益不足以抵消权限和删除复杂度。
- 本地离线优先的通用数据库同步:超出当前缺图问题。
- 自动让模型分析所有上传图片:不是保存所需步骤,增加成本和隐私传播。
- 先更换供应商或提高付费套餐额度:先测量现有路径,不用迁移掩盖设计问题。
仍待核实的事实
真实对象清单和账单、生产桶配置、媒体转码质量/速度、CDN 命中率、所有旧卡片格式分布、用户对原图保真的需求。所提数值是可测试的初始建议,不能冒充已有测量结论。
来源
- Railway Storage Buckets Billing:容量、桶请求/流量与服务出站的区别。
- Railway Pricing Plans:应用服务出站、CPU 和 RAM 计价。
- Railway CLI bucket:官方示例默认端点与项目配置对应。
- 仓库:
packages/server/src/routes/user-assets.ts、routes/cdn.ts、routes/shared-playthroughs.ts、lib/image-resize.ts、lib/plan-config.ts、lib/account-deletion.ts、packages/app/src/lib/asset-upload.ts。 - 前序调查:
docs/oncin-gallery-storage-investigation-2026-09-28.md。
