ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Folo 桌面端与移动端 OTA 服务统一设计:从 mainHash 到 runtimeVersion 的更新架构演进

Folo 桌面端与移动端 OTA 服务统一设计:从 mainHash 到 runtimeVersion 的更新架构演进 Folo 桌面端与移动端 OTA 服务统一设计从 mainHash 到 runtimeVersion 的更新架构演进FoloAI RSS Reader在apps/ota中维护着一个基于 Cloudflare Workers KV R2 的移动端 OTA 更新服务。本文以仓库设计文档 docs/superpowers/specs/2026-04-11-desktop-ota-unification-design.md 为骨架讲解如何把该服务扩展为移动端与新桌面端共用的单一更新事实源引入文件驱动的release-plan.json/release.json发布流程、以显式runtimeVersion取代mainHash的兼容性判定、面向direct/mas/mss三种分发的二进制策略/policy以及同时承载 renderer OTA 与直连安装包的/manifest协议。读完本文你将掌握这套跨端更新系统在路由契约、元数据模型、KV 存储键设计与发布编排上的完整设计思路并可在仓库源码中找到对应的落地实现。背景从移动端专属 OTA 到多产品统一更新源apps/ota最初是一个移动端专用的 OTA 服务。仓库中的早期设计文档 docs/superpowers/specs/2026-04-10-ota-design.md 明确了其定位基于 Expo Updates 的自定义后端运行在 Cloudflare Workers 之上以 GitHub Releases 作为发布事实源、Cloudflare R2 作为交付层仅通过x.y.z纯版本号驱动并依赖runtimeVersion表达原生兼容边界。桌面端 OTA 统一设计文档即本文主题正是在这一基础之上的第二阶段演进。它要解决的核心问题是把apps/ota从一个仅服务移动端的服务扩展为移动端与新版桌面端的唯一更新事实源同时做到桌面端 OTA 与直接二进制更新策略都由apps/ota承载follow-server中既有的桌面更新路由保持不动继续服务旧版桌面客户端把兼容性判定从mainHash迁移到显式runtimeVersion桌面端发布编排对齐移动端已经建立的release-plan.jsonrelease.json文件驱动工作流。从仓库当前文件状态看这套设计并非停留在纸面apps/desktop/下已存在 release-plan.json当前默认mode: build与 release.jsonapps/ota/src/lib/下已出现 request.ts、desktop.ts 等桌面端解析模块apps/ota/src/__tests__/也配套了 manifest.test.ts、policy.test.ts、sync.test.ts 等测试。配套的实施计划文档见 docs/superpowers/plans/2026-04-11-desktop-ota-unification.md。设计目标与非目标Goals让apps/ota成为移动端与新版桌面客户端唯一的更新事实源follow-server保持不变旧版桌面客户端完全兼容桌面端 OTA 兼容性判定中移除mainHash桌面发布编排与移动端新的release-plan.json/release.json工作流对齐支持direct、Mac App Storemas、Microsoft Storemss三类分发且三者的二进制策略生效时机可以不同商店审核完成时间不同步一次桌面 OTA 发布可以同时发布 renderer OTA 数据与直连安装包数据保留一种简单、可审计、仓库原生的发布流程由入库 JSON 配置驱动。Non-Goals不迁移旧版桌面客户端离开follow-server不替换follow-server既有的桌面 YAML 路由不通过外部 API 自动探测 App Store / Microsoft Store 审核完成状态不通过 OTA payload 下发原生代码首个版本不做分阶段灰度百分比staged rollout。Constraintsfollow-server不允许被修改新版桌面客户端访问apps/ota时只使用X-App-*请求头桌面分发渠道只能从既有的X-App-Platform取值推断这些值定义在 packages/internal/utils/src/headers.ts 的DesktopPlatform枚举中桌面 OTA 兼容性必须使用显式runtimeVersion若桌面客户端省略X-App-Runtime-Version服务端须把X-App-Version当作 runtimeVersion 处理。六个关键设计决策1. 服务归属apps/ota拥有更新事实源apps/ota拥有移动端与新版桌面客户端的更新事实源follow-server退化为仅服务旧桌面客户端的兼容层。这意味着新功能的迭代不再牵动旧的follow-server部署面。2. 桌面兼容模型用 runtimeVersion 取代 mainHash桌面端不再以mainHash作为 OTA 兼容性键而改用三个明确语义的版本维度installedBinaryVersion当前已安装的桌面应用版本runtimeVersionrenderer OTA 的兼容线rendererVersion当前已安装的 renderer 版本。默认规则是runtimeVersion installedBinaryVersion这一模型与移动端的心智完全一致消除了mainHash隐藏的兼容语义。3. 文件驱动的发布意图桌面端采用与移动端完全相同的工作流形态apps/desktop/release-plan.json 表达下一次的发布意图apps/desktop/release.json 记录实际打 tag 发布时被锁定的解析配置。发布自动化在决定发布内容时必须读取release.json而不是读取临时手工输入的工作流参数。当前仓库中桌面 release.json 的实态为version: 1.12.0、mode: build。4. 桌面发布三种模式桌面端支持build、ota、binary-policy三种模式模式含义build只发布直连安装包资源ota同时发布 renderer OTA 资源与直连安装包资源binary-policy只发布二进制升级策略元数据不重新构建或上传安装包5. 分发策略粒度per-distribution 时机由于 MAS 与 MSS 的商店审核存在延迟且完成时间不同桌面二进制策略必须支持按分发渠道独立设置生效时机。受支持的分发渠道为direct mas mss策略查找须优先匹配分发特定策略找不到再回退到产品级策略product-level policy。6. 发布类型命名store → binaryOTA 元数据模型中的发布类型从ota/store迁移为ota/binary。兼容规则是Worker 继续把遗留的store元数据当作binary的别名接受。这样既能保证旧移动端元数据继续工作又给桌面端一个比store更准确的命名。发布配置设计release-plan.json 与 release.json桌面release-plan.json建议的默认形态{ mode: build, runtimeVersion: null, channel: null, distributions: [], required: false, message: null }规则约束mode只能是build、ota、binary-policy三者之一ota模式必须提供runtimeVersionbuild与binary-policy模式下runtimeVersion必须为nullota与binary-policy必须提供channelbinary-policy必须提供distributionsrequired与message只影响二进制策略的发布行为。允许的桌面渠道为stable、beta、alphadevelopment不是发布渠道。桌面release.json建议形态{ version: 1.6.1, mode: ota, runtimeVersion: 1.6.0, channel: stable, distributions: [direct], required: false, message: null }release.json是 CI 打 tag 发布时消费的最终事实源。工作流解析桌面端镜像移动端的工作流模式解析脚本读取 apps/desktop/release.json解析器决定要触发的 workflow 动作tag.yml依据解析输出分派构建与 OTA 发布。据此日常发布执行不再需要手工输入发布参数。实施计划中对应新增的解析器为.github/scripts/resolve-desktop-release-config.mjs其职责可概括为校验release.json中的version与 tag 版本一致并按模式输出triggerDirectBuild/triggerStoreBuilds/triggerMetadataPublish/releaseKind/runtimeVersion/channel等输出项build与ota模式触发构建、binary-policy只触发元数据发布。OTA 元数据模型schemaVersion 2 的 ota-release.json桌面发布必须产出单一、机器可读的元数据文件供apps/ota使用。文件继续命名为ota-release.json使 Worker 的同步路径在所有产品上保持一致。一份桌面ota-release.json需要能够描述三种内容renderer OTA payloaddirect 直连安装包 payload仅 binary-policy 的发布。设计文档给出的建议完整形态{ schemaVersion: 2, product: desktop, channel: stable, releaseVersion: 1.6.1, releaseKind: ota, runtimeVersion: 1.6.0, publishedAt: 2026-04-11T10:00:00Z, git: { tag: desktop/v1.6.1, commit: abcdef123456 }, policy: { required: false, minSupportedBinaryVersion: 1.6.0, message: null, distributions: { direct: { downloadUrl: https://example.com/Folo-1.6.1.dmg } } }, desktop: { renderer: { version: 1.6.1, commit: abcdef123456, launchAsset: { path: renderer/render-asset.tar.gz, sha256: 0123..., contentType: application/gzip }, assets: [] }, app: { platforms: { macos: { platform: macos-x64, releaseDate: 2026-04-11T10:00:00Z, manifest: { name: latest-mac.yml, downloadUrl: https://example.com/latest-mac.yml }, files: [ { filename: Folo-1.6.1-macos-x64.zip, sha512: base64sha512, size: 123456789, downloadUrl: https://example.com/Folo-1.6.1-macos-x64.zip } ] } } } } }语义要点schemaVersion: 2用于与既有移动端专属形态区隔避免歧义移动端可以继续沿用现有 schema也可以在未来选择迁移releaseKind: ota表示该发布可从/manifest下发 renderer OTA 数据releaseKind: binary表示/manifest不服务任何 OTA payload但元数据仍可更新/policyruntimeVersion仅在releaseKind: ota时必填binary时应为null或省略direct二进制 payload 数据在build与ota两种模式下都可以存在。仓库中的落地实现位于 apps/ota/src/lib/schema.tsdesktopReleaseInputSchema将schemaVersion约束为字面量2、product约束为字面量desktopreleaseKind使用z.enum([ota, binary, store])接受store遗留值policy.distributions通过z.partialRecord(desktopDistributionSchema, ...)表达direct/mas/mss的分发级策略runtimeVersion为可空 semver。请求契约X-App-* 请求头体系新版桌面客户端请求apps/ota时只使用X-App-*请求头必填头X-App-PlatformX-App-VersionX-App-Channel可选头X-App-Runtime-VersionX-App-Renderer-VersionX-App-Platform的实际取值可在 packages/internal/utils/src/headers.ts 的DesktopPlatform枚举中看到包含desktop、desktop/web、desktop/macos、desktop/macos/dmg、desktop/macos/mas、desktop/windows/exe、desktop/windows/ms、desktop/linux。其中分发相关的建构建函数createDesktopAPIHeaders会根据运行平台darwin/win32/linux以及是否为process.mas、Microsoft Store 构建自动选择对应的 platform 头值。X-App-Platform到路由维度的映射头值platformdistributiondesktop/macos/dmgmacosdirectdesktop/macos/masmacosmasdesktop/windows/exewindowsdirectdesktop/windows/mswindowsmssdesktop/linuxlinuxdirectdesktop/web不参与桌面 OTA 与桌面二进制策略—仓库中的实际解析实现位于 apps/ota/src/lib/request.ts 的parseDesktopRequest它用DESKTOP_PLATFORM_MAP把上述头值映射为platform distribution并实现X-App-Runtime-Version缺省时回退到X-App-Version的逻辑与设计约束完全一致。桌面版本语义X-App-Version永远是已安装的二进制版本installed binary versionX-App-Runtime-Version是 OTA 兼容性键若X-App-Runtime-Version缺失使用X-App-VersionX-App-Renderer-Version仅用于判断某个 renderer payload 是否比已安装 renderer 更新。GET /manifest下发兼容 payload/manifest只回答一个问题当前客户端可用的兼容更新 payload 有哪些。桌面端manifest可以包含 renderer OTA payload 或 direct 渠道的完整应用 payload但不能代替/policy去做商店升级拦截的最终 UX 决策。建议响应形态{ id: uuid, createdAt: 2026-04-11T10:00:00.000Z, product: desktop, channel: stable, runtimeVersion: 1.6.0, renderer: { releaseVersion: 1.6.1, version: 1.6.1, commit: abcdef1234, launchAsset: { key: render-asset, hash: sha256-base64url, fileExtension: .tar.gz, contentType: application/gzip, url: https://ota.folo.is/assets/desktop/stable/1.6.0/1.6.1/windows/render-asset.tar.gz }, assets: [] }, app: { releaseVersion: 1.6.1, version: 1.6.1, platform: windows-x64, releaseDate: 2026-04-11T10:00:00.000Z, manifest: { name: latest.yml, downloadUrl: https://example.com/latest.yml }, files: [ { filename: Folo-1.6.1-windows-x64.exe, sha512: base64sha512, size: 123456789, downloadUrl: https://example.com/Folo-1.6.1-windows-x64.exe } ] } }桌面 Manifest 规则renderer仅在同时满足以下条件时返回存在product channel runtimeVersion platform匹配的兼容桌面 OTA 发布renderer payload 版本比X-App-Renderer-Version更新app仅在满足以下条件时返回客户端分发渠道为direct存在请求平台下更新的兼容直连安装包mas与mss渠道绝不能从/manifest拿到 direct 二进制 payload若renderer与app都为空返回204。客户端决策优先级有renderer就优先应用 renderer否则有app就提供appdirect 全量更新二进制升级的引导与强制则单独走/policy。实施计划给出的对应测试期望包括direct 客户端两者皆有时返回renderer app、只有 renderer 时只返回 renderer、store 渠道请求永远拿不到app、无兼容 payload 时返回204。GET /policy显式发布的二进制升级策略/policy只回答三个问题当前已安装二进制是否应继续可用是否应提示用户升级用户应去哪里拿到正确的二进制。建议响应形态无动作{ action: none, targetVersion: null, message: null, distribution: direct, downloadUrl: null, storeUrl: null, publishedAt: null }有直连升级可用时{ action: prompt, targetVersion: 1.6.1, message: A newer desktop version is available., distribution: direct, downloadUrl: https://example.com/Folo-1.6.1.dmg, storeUrl: null, publishedAt: 2026-04-11T10:00:00.000Z }策略选择规则对桌面端从X-App-Platform推断distribution查询product channel distribution的策略若不存在回退到product channel若仍无策略返回none。动作语义none不提示也不阻塞prompt建议二进制升级但允许继续使用block继续使用前必须先完成二进制升级。URL 语义direct返回downloadUrlmas与mss返回storeUrl。为什么策略必须显式发布服务端不能从 GitHub Release 的发布时间推断策略生效时机。原因在于 MAS / MSS 的审核时机是异步且不可知的GitHub Release 可能早于商店二进制真正可安装就发布。因此二进制策略只有在针对相关分发渠道执行了一次显式binary-policy发布后才生效例如 App Store 审核通过后发布 MAS 策略Microsoft Store 审核完成后另行发布 MSS 策略。仓库的 KV 键实现apps/ota/src/lib/constants.ts已把 policy 键设计为可选分发维度policy: ( product, channel, distribution?, // direct | mas | mss ) distribution ? policy:${product}:${channel}:${distribution} : policy:${product}:${channel},存储模型KV 键设计Release 记录按 release-version 键存储解析后的桌面与移动发布元数据release:desktop:1.6.1 release:mobile:0.4.3最新 OTA 指针桌面 OTA 最新指针继续以 product、channel、runtimeVersion、platform 为维度latest:desktop:stable:1.6.0:windows二进制策略键新增分发感知键policy:desktop:stable:direct policy:desktop:stable:mas policy:desktop:stable:mss移动端初期可继续使用 product 级键policy:mobile:production等未来再平滑迁移到分发感知键。发布流程三种模式的差异Desktopbuild构建并上传直连安装包资源发布桌面二进制元数据不发布 renderer OTA payload。Desktopota构建并上传 renderer OTA payload构建并上传直连安装包资源发布一份同时包含 renderer OTA 与 direct 二进制 payload 数据的元数据文件。这样做保证了既有用户有资格接收 renderer OTA新用户也能立即下载最新直连安装包——一次 OTA 发布仍要发布最新完整安装包这一桌面端硬性需求由此得到满足。Desktopbinary-policy只发布策略元数据不重建安装包不上传 renderer OTA payload显式指定一个或多个目标分发渠道。典型场景App Store 审核通过后发布 MAS 策略Microsoft Store 审核完成后再发布 MSS 策略。迁移与验证方案Migration Plan引入桌面发布配置文件与解析逻辑让桌面发布流程产出新的桌面ota-release.json扩展apps/ota的 sync、存储与选择逻辑以理解桌面元数据为apps/ota增加桌面感知的manifest与policy路由让新版桌面客户端改用apps/ota的manifest policy旧版桌面客户端继续使用follow-server不做改动。测试矩阵单元测试覆盖桌面 release config 校验与解析器行为、桌面元数据解析、X-App-Version到 runtimeVersion 的回退、X-App-Platform到 platform/distribution 的映射、策略选择与回退、renderer payload 选择、direct 二进制 payload 选择。仓库中的对应测试文件包括 schema.test.ts、sync.test.ts 等。Worker 路由测试覆盖direct 请求在两者可用时返回renderer app只有 renderer 时返回 rendererMAS / MSS 请求永不从/manifest拿到apppolicy 优先分发特定记录direct返回downloadUrl、mas/mss返回storeUrl无兼容 payload 时manifest返回204。对应文件为 manifest.test.ts 与 policy.test.ts。客户端验证新版桌面客户端优先应用 renderer OTArenderer 不可用时回退到 direct 全量更新MAS / MSS 客户端尊重prompt并执行block。开放风险桌面元数据文件要同时为未来的移动端收敛预留向后兼容需要谨慎处理 schema 版本演进桌面客户端不得假定 store 分发渠道一定存在apppayload发布自动化必须避免对商店渠道过早发布binary-policy。最终建议一套可理解、可落地的更新模型采纳apps/ota作为移动端与新版桌面客户端的统一更新事实源follow-server保持原样为旧桌面客户端服务从兼容模型中移除mainHash并让桌面发布编排与移动端文件驱动发布流对齐。整套体系收敛为四层清晰的概念release-plan.json定义意图release.json锁定打 tag 发布的配置/manifest暴露兼容 payload 事实/policy暴露二进制升级策略。同时保留桌面端直接分发的要求一次 OTA 发布依然会发布最新完整安装包。对于正在阅读源码的开发者建议按 request.ts → schema.ts → constants.ts →desktop.ts/sync.ts→ 路由与测试的顺序阅读即可把本文的设计契约与代码逐一对上。创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表