架构总览
插件市场在客户端里分三层,单向依赖:
- UI 层(
lib/features/market/):MarketPage/PluginDetailPage/PackDetailPage。ConsumerWidgetwatch providers,.when(data/loading/error)渲染。 - Provider 层(
lib/core/market/market_providers.dart):RiverpodFutureProvider/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 providerslib/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— 共享预览图组件
数据流:浏览市场
Section titled “数据流:浏览市场”用户打开市场首页,切到“伙伴”Tab:
MarketPage通过ref.watch(marketFriendListProvider)拿数据marketFriendListProvider调MarketRepository.listDirectory(MarketDir.friend)listDirectory先查MarketCache.getDirectory(friend)——命中(5 分钟内)直接返回 id 列表;未命中走 GitHub Contents API- Contents API 返回目录文件列表,过滤出
.meta.json文件名,得到插件 id 列表 - 缓存 id 列表(5 分钟 TTL)
- 对每个 id 调
fetchMeta(friend, id)——先查 meta 缓存(1 小时 TTL),未命中走 raw URL 拉.meta.json - 解析成
MarketPlugin对象,返回List<MarketPlugin> - UI
.when(data:)渲染 GridView 卡片
一次目录加载 = 1 次 API 调用 + N 次 raw 请求。缓存命中后零网络。
数据流:安装插件
Section titled “数据流:安装插件”用户点“安装”:
- UI 调
ref.read(pluginInstallerProvider.future)拿PluginInstaller 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()展开子插件 + 整合包目录
- 返回
PluginInstallResult.ok(ids)或.fail(error) - UI 成功后
SnackBar+ref.invalidate(pluginManagerProvider)(触发PluginManager.refresh()重新扫描)
数据流:更新检测
Section titled “数据流:更新检测”marketUpdatesProvider调UpdateChecker.checkAll()- 遍历
manager.allPlugins(已安装列表) - 每个插件按 type 映射到
MarketDir,调fetchMeta拿市场版本(走 1 小时缓存) - semver 比较:市场版本 > 已安装版本 → 加入
PluginUpdate列表 - UI 据此在 AppBar refresh 图标叠
Badge红点,卡片/详情页显示“更新”按钮
| 数据 | TTL | 存储 |
|---|---|---|
| 目录列表 | 5 分钟 | 内存 |
.meta.json |
1 小时 | 内存 |
| 预览图 | 7 天 | 磁盘 |
| 插件包 | 永久 | 磁盘 |
磁盘缓存在 <appSupportDir>/moodpet/market-cache/ 下,跨重启保留。内存缓存在进程生命周期内。
和插件系统的边界
Section titled “和插件系统的边界”市场层只通过两个点和 PluginManager 交互:
pluginsRootDir()——从plugin_paths.dart拿插件根目录路径,安装时复制文件进去PluginManager.refresh()——安装/更新后触发重新扫描
不碰 enable/disable、active Friend、uninstall——那些是 PluginManager 和插件管理页的职责。