16 KiB
存储运行时架构与边界说明
📋 文档目的
本文档用于说明当前仓库在 Web / Extension / Desktop 三种运行环境下的真实存储结构,重点回答以下问题:
- 现在有哪些存储方式
- 各存储里有哪些逻辑区域
- 不同类型的内容会落到哪里
- 每个区域的大小限制、配额和清理策略是什么
- 当前已经建立了哪些防线
- 还存在哪些灰区和后续必须补齐的边界
本文档描述的是 当前运行态事实,不是理想设计图。
🎯 核心原则
当前系统必须严格区分两类数据:
-
结构化业务数据
- 例如设置、会话快照、模型配置、收藏文本、上下文文档。
- 应存入结构化主存储。
-
图片二进制资产
- 例如上传图、生成图、收藏封面、收藏示例图。
- 必须存入独立图片资产库。
- 结构化主存储中只允许保存图片引用和轻量元数据,不允许保存大体积 inline base64。
这条边界是本轮存储事故复盘后的第一原则。
🧱 一、物理存储介质
1. Web / Extension 结构化主存储
- 介质:IndexedDB
- 实现:
DexieStorageProvider - 数据库名:
PromptOptimizerDB - 表结构:单表
storage - 本质:一个共享的 KV 桶
相关文件:
packages/core/src/services/storage/dexieStorageProvider.ts
2. Desktop 结构化主存储
- 介质:主进程 JSON 文件
- 实现:
FileStorageProvider - 主文件:
<userData>/prompt-optimizer-data.json - 备份文件:
<userData>/prompt-optimizer-data.json.backup - 写入方式:内存镜像 + 延迟写盘 + 原子替换 + 备份恢复
相关文件:
packages/core/src/services/storage/fileStorageProvider.tspackages/desktop/main.js
3. Session 图片资产库
- 介质:IndexedDB
- 实现:
ImageStorageService - 数据库名:
PromptOptimizerImageDB - 目标:保存会话使用的上传图、生成图和相关图片资产
表结构分为两张表:
imageMetadataimageData
相关文件:
packages/core/src/services/image/storage.tspackages/ui/src/composables/system/useAppInitializer.ts
4. Favorite 图片资产库
- 介质:IndexedDB
- 实现:
ImageStorageService - 数据库名:
PromptOptimizerFavoriteImageDB - 目标:保存收藏项相关的封面图、示例图、引用图资产
表结构同样分为两张表:
imageMetadataimageData
相关文件:
packages/core/src/services/image/storage.tspackages/ui/src/composables/system/useAppInitializer.ts
5. 非主线路径 provider
代码里还存在两个 provider,但不是当前线上主路径:
LocalStorageProvider- 浏览器本地存储实现
- 代码声明能力上限约
5MB
MemoryStorageProvider- 主要用于测试或临时环境
相关文件:
packages/core/src/services/storage/localStorageProvider.tspackages/core/src/services/storage/factory.ts
🗂️ 二、结构化主存储里的逻辑分区
虽然 Web 物理上只有 PromptOptimizerDB.storage 一张表,Desktop 物理上只有一个 JSON 文件,但逻辑上可以拆成以下几块。
1. pref:* 命名空间
由 PreferenceService 统一管理。逻辑 key 会被自动加上 pref: 前缀后再落盘。
示例:
global-settings/v1->pref:global-settings/v1session/v1/basic-system->pref:session/v1/basic-systemvariableManager.storage->pref:variableManager.storage
相关文件:
packages/core/src/services/preference/service.ts
2. Core 直接 key
这些 key 不经过 PreferenceService,直接写入主存储:
modelsimage-modelsuser-templatesprompt_history
相关文件:
packages/core/src/constants/storage-keys.ts
3. Context 单文档区
上下文不是按多个 key 分散存,而是集中在一个文档里:
ctx:store
这个文档同时保存:
- 所有 context 数据
- 当前激活的 context id
- 文档版本号
相关文件:
packages/core/src/services/context/constants.tspackages/core/src/services/context/repo.ts
4. Favorites 直接 key 区
收藏系统也是独立 key,不经过 PreferenceService:
favoritesfavorite_categoriesfavorite_statsfavorite_tagsfavorite_categories_initialized
相关文件:
packages/core/src/services/favorite/manager.ts
5. 旧 UI key / 混合态 key
当前仓库仍存在一批历史 key 和新快照并存的情况,包括但不限于:
global-settings/v1app:settings:ui:function-modeapp:settings:ui:basic-sub-modeapp:settings:ui:pro-sub-modeapp:settings:ui:builtin-template-language
需要特别注意:
imageSubMode现在以路由为真源,不再直接依赖 preference 持久化- 但应用外壳仍会把当前路由状态镜像回
global-settings/v1
相关文件:
packages/ui/src/stores/settings/useGlobalSettings.tspackages/ui/src/composables/mode/useFunctionMode.tspackages/ui/src/composables/mode/useBasicSubMode.tspackages/ui/src/composables/mode/useProSubMode.tspackages/ui/src/composables/mode/useImageSubMode.tspackages/ui/src/components/app-layout/PromptOptimizerApp.vue
📦 三、不同内容现在实际存到哪里
1. 会话快照
各功能区会话存储在 pref:session/v1/* 下,包括:
- basic-system
- basic-user
- pro-multi
- pro-variable
- image-text2image
- image-image2image
- image-multiimage
这些快照保存的是结构化 JSON,例如:
- 原始 prompt
- 优化后 prompt
- 模型选择
- 模板选择
- 评测结果
- 变量值
- 图片引用 id
相关文件:
packages/ui/src/stores/session/useSessionManager.tspackages/ui/src/stores/session/useBasicSystemSession.tspackages/ui/src/stores/session/useBasicUserSession.tspackages/ui/src/stores/session/useProMultiMessageSession.tspackages/ui/src/stores/session/useProVariableSession.tspackages/ui/src/stores/session/useImageText2ImageSession.tspackages/ui/src/stores/session/useImageImage2ImageSession.tspackages/ui/src/stores/session/useImageMultiImageSession.ts
2. Session 图片
会话中真正的图片字节不应该放进 session 快照,而是写入 PromptOptimizerImageDB。
session 快照里应该只保留:
assetIdimage-ref- 轻量 metadata
图片写入和引用转换相关文件:
packages/ui/src/utils/image-asset-storage.tspackages/ui/src/stores/session/imageStorageMaintenance.ts
3. 收藏文本和收藏元数据
收藏项本体仍保存在结构化主存储中的 favorites key 下。
其中包含:
- 标题
- 正文内容
- 标签
- 分类
- functionMode
- optimizationMode
- imageSubMode
- metadata
注意:
favorites不是pref:*命名空间的一部分- 因此它不受
PreferenceService针对 session 的大小防线保护
相关文件:
packages/core/src/services/favorite/manager.ts
4. 收藏图片资产
收藏相关图片应该写入 PromptOptimizerFavoriteImageDB,然后在收藏 metadata 中保存引用信息:
coverAssetIdassetIdsimageAssetIdsinputImageAssetIds
相关文件:
packages/ui/src/utils/favorite-media.tspackages/ui/src/components/SaveFavoriteDialog.vuepackages/ui/src/composables/app/useAppPromptGardenImport.ts
5. 收藏图片 fallback
当前收藏元数据 schema 仍允许 URL 型 fallback:
coverUrlurls
这意味着当前系统允许:
- 远程 URL fallback
但现在已经明确禁止:
data:image/...;base64,...这种 inline 数据 URL 被写入 favorite metadata
相关文件:
packages/ui/src/utils/favorite-media.tspackages/core/src/services/favorite/manager.ts
6. Prompt Garden 导入收藏
Prompt Garden 导入链路已经改成严格模式:
- 优先把图片落到图片资产库
- 不再允许把 inline 图片内容回退进收藏 metadata
- 自动收藏路径在图片落盘失败时会跳过收藏保存
- 确认弹窗路径在图片落盘失败时不再附带
mediafallback
相关文件:
packages/ui/src/composables/app/useAppPromptGardenImport.ts
7. 模型、模板、历史、上下文
这几类数据都属于结构化主存储:
- 模型配置 ->
models - 图像模型配置 ->
image-models - 用户模板 ->
user-templates - 历史记录 ->
prompt_history - 上下文文档 ->
ctx:store
相关文件:
packages/core/src/services/model/manager.tspackages/core/src/services/template/manager.tspackages/core/src/services/history/manager.tspackages/core/src/services/context/repo.ts
📏 四、大小限制、配额和清理策略
1. Session 快照大小限制
PreferenceService 现在对 session/* key 建立了硬限制:
- 单条 session 快照最大
1 MiB
限制覆盖:
- 写入时校验
- 读取时校验
超限行为:
- 写入失败,抛出结构化存储错误
- 读取失败时,
SessionManager会清理超限的单个 session key,而不是清空整库
相关文件:
packages/core/src/services/preference/service.tspackages/ui/src/stores/session/useSessionManager.ts
2. Session 图片库配额
PromptOptimizerImageDB 默认配额:
maxCacheSize = 50 MBmaxAge = 7 天maxCount = 100autoCleanupThreshold = 0.8
清理顺序:
- 清理过期图片
- 超过数量上限时按最旧访问时间删除
- 超过总容量时继续按最旧访问时间删除,直到降到目标阈值
相关文件:
packages/core/src/services/image/storage.tspackages/ui/src/composables/system/useAppInitializer.ts
3. Favorite 图片库配额
PromptOptimizerFavoriteImageDB 默认配额:
maxCacheSize = 200 MBmaxAge = 365 天maxCount = 1000autoCleanupThreshold = 0.9
这套配置比 session 图片库更宽松,因为收藏被视为长期保留资产。
相关文件:
packages/core/src/services/image/storage.tspackages/ui/src/composables/system/useAppInitializer.ts
4. 历史记录限制
prompt_history 最多保留:
50条记录
相关文件:
packages/core/src/services/history/manager.ts
5. Desktop 文件存储运行特性
Desktop 主存储没有应用层固定大小上限,但有运行时写盘策略:
- 延迟写入:
500ms - 最大 flush 超时:
3s - 自动备份恢复:主文件失败时尝试 backup
这意味着 Desktop 不是“无限安全”,只是没有像 session 那样的显式 size cap。
相关文件:
packages/core/src/services/storage/fileStorageProvider.ts
6. LocalStorage provider 能力上限
LocalStorageProvider 报告的能力上限约为:
5MB
但它不是当前主线路径。
相关文件:
packages/core/src/services/storage/localStorageProvider.ts
🔄 五、导入导出边界
当前 DataManager.exportAllData() 导出的不是整套运行态存储,而是一个经过裁剪的业务数据集合。
当前导出包含:
historymodelsuserTemplatesuserSettingscontexts
当前不包含:
favoritesfavorite image assetssession snapshotssession image assets
因此必须明确:
- 导入导出边界 != 运行时存储边界
- 当前导入导出不能视为完整灾备备份
相关文件:
packages/core/src/services/data/manager.tspackages/core/src/services/preference/service.ts
🚨 六、当前已经建立的防线
1. Session 超限防线
session/*超过1 MiB直接拒绝- 恢复时只清理超限单 key,不再整库清空
2. Favorite inline 图片防线
- FavoriteManager 现在拒绝
data:image/...inline data URL 进入 metadata
3. Prompt Garden 收藏严格落盘策略
- 图片资产落盘失败时不再用 inline 图片兜底
4. Session 图片与结构化快照分层
- 图片二进制进入独立图片库
- session 快照只保留引用
⚠️ 七、当前仍存在的灰区和风险
1. 结构化主存储物理上仍是共享桶
无论在 Web 还是 Desktop,本质上都还是单一主存储容器:
- Web 是一张 Dexie 表
- Desktop 是一个 JSON 文件
这意味着只要某条链路把大对象写错位置,影响范围就不是单个业务模块,而是整个主存储。
2. Favorites 不经过 PreferenceService
favorites 是直接 key,不走 PreferenceService,因此:
- session 的
1 MiB防线不覆盖 favorites - favorites 必须在自身 manager 和调用方建立独立边界
3. Favorite 图片资产目前更像“配额库”,不是“强引用回收库”
已确认:
- session 图片库会根据 session 快照引用做 GC
- 收藏删除链路当前只删除
favorites文本记录 - 没有看到收藏删除后同步清理
PromptOptimizerFavoriteImageDB孤儿资产的强引用回收逻辑
这意味着当前 favorite 图片库仍存在孤儿资产积累风险,更多依赖配额清理,而不是引用级删除。
相关文件:
packages/ui/src/stores/session/imageStorageMaintenance.tspackages/ui/src/components/FavoriteButton.vuepackages/ui/src/components/FavoriteManager.vuepackages/core/src/services/favorite/manager.ts
4. UI 设置仍处于混合态
当前存在:
- 新的
global-settings/v1 - 旧的
app:settings:ui:* - 路由真源与持久化镜像并存
这不一定立即导致数据损坏,但会提高认知成本,也会增加后续修改时误写双真源的风险。
5. 导出数据不覆盖图片资产
当前导出文件不能还原:
- 收藏图片库
- session 图片库
这也是为什么“运行时数据完整性”和“导出文件完整性”必须分开讨论。
🛡️ 八、必须遵守的存储红线
后续所有功能开发必须遵守以下规则:
红线 1:结构化主存储禁止承载大体积二进制内容
禁止把以下内容直接写入主存储:
- 图片 base64
- data URL
- 大型二进制序列化字符串
红线 2:图片只能进入图片资产库
所有图片相关能力都必须遵循:
- 先落图片资产库
- 再把
assetId写入业务对象
红线 3:任何 fallback 都不能回退到 inline 图片
允许的 fallback:
- 外部 URL
- 跳过保存
- 显式失败
禁止的 fallback:
- 将
data:image/...写回 favorites 或 session metadata
红线 4:删除业务对象必须考虑资产回收
如果一个对象持有图片 assetId,那么删除该对象时必须明确以下策略之一:
- 立即删除资产
- 标记引用减少后异步 GC
- 周期性按引用扫描清理
不能只删文本记录,不考虑资产生命周期。
红线 5:provider 级测试必须覆盖真实请求体和真实落盘结果
仅验证“调用链路经过某个 adapter”是不够的。
必须补齐:
- provider 级 payload 断言
- 存储层实际写入结果断言
- 大对象拒绝与回退行为断言
🧭 九、建议的后续治理方向
1. Favorite 图片库补引用级 GC
目标:
- 删除收藏时同步处理收藏图片资产
- 或建立 favorites 专属的引用扫描 + 清理任务
2. 为 favorites 建立独立 size guard
建议:
- 不依赖
PreferenceService - 在
FavoriteManager或更底层 provider 边界增加单项和总量限制
3. 收口 UI 设置双真源
建议逐步统一:
- 哪些由路由主导
- 哪些由
global-settings/v1主导 - 哪些旧 key 只保留迁移读,不再写入
4. 补齐“结构化存储禁止 inline 图片”的系统性测试
建议至少覆盖:
- session
- favorites
- Prompt Garden import
- SaveFavoriteDialog
- 未来任何带图片快照的新功能
✅ 十、结论
当前系统的真实运行时存储结构可以总结为:
-
结构化主存储
- 保存设置、会话快照、模型、模板、历史、上下文、收藏文本等结构化数据。
-
Session 图片资产库
- 保存会话期间使用的图片字节。
-
Favorite 图片资产库
- 保存收藏相关的图片资产。
当前已经建立了两条关键防线:
session/*的1 MiB硬限制- favorites metadata 禁止 inline 图片 data URL
但要彻底避免同类事故再次发生,还需要继续补齐:
- favorite 图片资产回收
- favorites 的独立容量防线
- 旧 UI key 的收口
- provider / storage 边界的强测试
只有当“结构化数据”和“图片资产”在架构、代码、测试、删除回收四个层面都被强制分层后,这类问题才算真正从根上解决。