Skip to content

架构总览

插件市场在客户端里分三层,单向依赖:

  • UI 层(lib/features/market/):MarketPage / PluginDetailPage / PackDetailPage。ConsumerWidget watch providers,.when(data/loading/error) 渲染。
  • Provider 层(lib/core/market/market_providers.dart):Riverpod FutureProvider / Provider,把数据层对象包成响应式可 watch 的东西。
  • 数据层(lib/core/market/ + lib/core/plugin/plugin_installer.dart):MarketConfig / MarketCache / MarketRepository / PluginInstaller / UpdateChecker。和 GitHub 说话、管缓存、解压 ZIP、校验 manifest、比对版本。

依赖方向:UI → Provider → 数据层 → PluginManager.refresh()。反过来不行——PluginManager 不知道市场存在。

  • lib/core/market/market_config.dart — 仓库坐标 + URL 构建 + MarketDir 枚举
  • lib/core/market/market_cache.dart — TTL 缓存(内存 + 磁盘)
  • lib/core/market/market_repository.dart — GitHub 网络层 + MarketPlugin 模型
  • lib/core/market/market_providers.dart — Riverpod providers
  • lib/core/market/update_checker.dart — 版本比对
  • lib/core/plugin/plugin_installer.dart — 下载 + 解压 + 注册
  • lib/features/market/market_page.dart — 市场首页
  • lib/features/market/plugin_detail_page.dart — 插件详情页
  • lib/features/market/pack_detail_page.dart — 整合包详情页
  • lib/features/market/preview_image.dart — 共享预览图组件

用户打开市场首页,切到“伙伴”Tab:

  1. MarketPage 通过 ref.watch(marketFriendListProvider) 拿数据
  2. marketFriendListProvider 调 MarketRepository.listDirectory(MarketDir.friend)
  3. listDirectory 先查 MarketCache.getDirectory(friend)——命中(5 分钟内)直接返回 id 列表;未命中走 GitHub Contents API
  4. Contents API 返回目录文件列表,过滤出 .meta.json 文件名,得到插件 id 列表
  5. 缓存 id 列表(5 分钟 TTL)
  6. 对每个 id 调 fetchMeta(friend, id)——先查 meta 缓存(1 小时 TTL),未命中走 raw URL 拉 .meta.json
  7. 解析成 MarketPlugin 对象,返回 List<MarketPlugin>
  8. UI .when(data:) 渲染 GridView 卡片

一次目录加载 = 1 次 API 调用 + N 次 raw 请求。缓存命中后零网络。

用户点“安装”:

  1. UI 调 ref.read(pluginInstallerProvider.future) 拿 PluginInstaller
  2. PluginInstaller.install(plugin, onProgress):
    • MarketRepository.downloadPackage(dir, id, onProgress)——先查包缓存(永久),未命中走流式下载(http.Client.send 按 chunk 累积,据 Content-Length 调 onProgress)
    • ZipDecoder().decodeBytes() 解压到临时目录,拒绝绝对路径和 ..(防 zip-slip)
    • 读临时目录 manifest.json,校验 id/type 和 MarketPlugin 一致
    • Friend/Application:copyDirectory 复制到 <pluginsRoot>/<safeId>.mood*
    • Pack:installPack() 展开子插件 + 整合包目录
  3. 返回 PluginInstallResult.ok(ids) 或 .fail(error)
  4. UI 成功后 SnackBar + ref.invalidate(pluginManagerProvider)(触发 PluginManager.refresh() 重新扫描)
  1. marketUpdatesProvider 调 UpdateChecker.checkAll()
  2. 遍历 manager.allPlugins(已安装列表)
  3. 每个插件按 type 映射到 MarketDir,调 fetchMeta 拿市场版本(走 1 小时缓存)
  4. semver 比较:市场版本 > 已安装版本 → 加入 PluginUpdate 列表
  5. UI 据此在 AppBar refresh 图标叠 Badge 红点,卡片/详情页显示“更新”按钮
数据 TTL 存储
目录列表 5 分钟 内存
.meta.json 1 小时 内存
预览图 7 天 磁盘
插件包 永久 磁盘

磁盘缓存在 <appSupportDir>/moodpet/market-cache/ 下,跨重启保留。内存缓存在进程生命周期内。

市场层只通过两个点和 PluginManager 交互:

  1. pluginsRootDir()——从 plugin_paths.dart 拿插件根目录路径,安装时复制文件进去
  2. PluginManager.refresh()——安装/更新后触发重新扫描

不碰 enable/disable、active Friend、uninstall——那些是 PluginManager 和插件管理页的职责。