Skip to content

资源包热更(Resource Packs)

资源包是 WordForge 的不发版内容热更机制:把一份 payload.json(≤ 4 MiB 的不透明 JSON)经签名分发给客户端,让运营无需发版即可更新「已预埋消费点」的内容与配置。

一句话定位:资源包是**「数据热更」不是「界面热更」。它只能给客户端预先写好消费代码**的位置喂新数据,不能下发新页面、新布局、新交互逻辑(不是 CodePush,也不是 server-driven UI)。能改到什么程度,取决于客户端提前埋了多少消费点。

一、链路总览

admin 上传 payload → 服务端算 SHA-256 + Ed25519 签名 → 落盘 static/packs/<pack>/<ver>/payload.json
        ↓ admin 切某通道「激活」
服务端广播 SSE resource_pack_available(同 pack×通道 5 分钟内去重)
        ↓ 客户端收到(或冷启动/手动检查)
GET /api/resource-packs/<packId>/manifest?appVersion=&locale=&channel=
        ↓ 下载 downloadURL → SHA-256 校验 → Ed25519 验签(硬编码生产公钥)→ minAppVersion 门控
注册的 consumer 消费 payload → 渲染 UI → POST /api/telemetry/resource-pack-install 上报结果
  • 三通道stable(生产用户)/ beta(早期访问)/ internal(仅内部)。客户端冷启动默认拉 stable;切某通道激活只广播该通道。
  • 验签信任锚:三端硬编码生产 Ed25519 公钥 fr+eALsS/N3gz4AZmpSm/wDbtDCh596WjapwVPtHn6s= 作为唯一信任锚;GET /api/resource-packs/public-key 仅供运行时自检对比(不一致仅告警,仍以硬编码为准),杜绝端点被替换架空验签。
  • 缓存:manifest Cache-Control: max-age=60 + ETag(304);payload 文件 immutable, max-age=31536000(版本号路径,永不变)。

二、两个通用容器

不要为每个用途新建 packId——投递链路对任意 packId 已通用,成本全在客户端消费侧,且每个新 packId 三端都要各发一次版。因此内容/配置类用途收敛到两个通用容器

容器 A:content-slots(结构化内容位)

一次预埋覆盖 公告条 / 首页内容卡 / 激励语 / 空状态 / 推荐入口 / 更新提示等多种运营位。

jsonc
{
  "schema": 1,
  "slots": {
    "global-announcement": [ /* ContentItem[],全局公告条 */ ],
    "home-top":            [ /* 首页横幅下方内容位 */ ],
    "home-empty":          [ /* 首页空状态文案位 */ ],
    "update-notice":       [ /* 更新提示软文案(非强升) */ ]
  }
}

ContentItem 字段(三端逐字节一致):

字段类型说明
idstring唯一 ID(关闭态、去重按此)
kindstringbanner / notice / tip / entry / empty-state;决定呈现样式
titlestring?标题
bodystring?副文案
imageUrlstring?远程图(不占 4 MiB;无则走渐变兜底)
deeplinkstring?wordforge://...,见 §四
gradientStart / gradientEndstring?#RRGGBB 渐变兜底色
levelstring?info / warning(notice 配色)
dismissiblebool?可关闭(关闭态记本地)
prioritynumber降序在前
startAt / endAtstring?RFC3339 展示时间窗

容器 B:app-config(远程配置 / 开关)

jsonc
{
  "schema": 1,
  "flags": {
    "announcements_enabled": true,      // 公告位总开关(kill-switch)
    "telemetry_sampling_rate": 1.0,     // 可丢弃遥测的采样率 [0,1]
    "llm_advisor_enabled": true         // 功能 kill-switch 示例
  }
}

flags 为 key → 标量(bool / number / string)。语义是 kill-switch / 数值配置,不做百分比灰度(客户端自决可被绕过,灰度靠 channel + minAppVersion)。

三、三端消费现状

消费实现状态
websrc/resourcePack/src/stores/appConfig.tssrc/components/content/{ContentSlot,AnnouncementBar}两容器齐全
Androidcore/di/AppContainer.ktpackAppliers 注册 + Compose 消费 UI两容器齐全
iOSResourcePackStore(@Published 响应式刷新)+ RemoteAsset 接入 HomeScreen两容器齐全

未注册的 packId 会被下载验签但「不应用」(Android ResourcePackSync / iOS RemoteAsset 同理)。新增一个可热更的 UI 位 = 必须在客户端写消费代码 + 发版homepage-banners(首页横幅)是早期独立 pack,仍受支持。

ContentItem.deeplinkwordforge:// scheme,三端映射一致:

pathwebAndroidiOS
wordforge://review/flashcard复习review tab
wordforge://practice/quick-practice练习study tab
wordforge://study/learning学习study tab
wordforge://word/{id}/vocabulary词详情忽略(无词详情路由)
wordforge://tab/{section}一级路由一级导航tab/<section>

sectionhome / review / practice / study / wordbooks / stats / profile。无法映射的 deeplink 一律忽略(按钮禁用)。

五、admin 操作(在内嵌管理后台「资源包」页)

  1. 上传新版:填 pack_id(如 content-slots)、version(semver,单调递增)、通道、可选 minAppVersion;选 payload.json 上传。服务端自动算 SHA-256 + Ed25519 签名落盘。上传 ≠ 生效
  2. 激活:在通道选择器选版本激活 —— 这一步才触发 SSE 广播并对该通道生效。
  3. 观察:卡片底部「近 7 天安装结果」+ 统计弹窗看 installed / verify_failed / rollback 三态。
  4. 回滚 / 下线:切回旧版本(旧文件保留);或「停用」做软删除。

灰度建议:先 internal / beta 自测 → 再切 stable 放量。脚本化见 API 接口 §22「资源包管理(admin)」。

「停用」的语义(重要)

停用 = 软删除:只把该版本从 server manifest 摘除(之后 GET manifest 返回 404),磁盘 payload 文件保留。不会远程擦除已安装客户端的内容——客户端内容来自本地缓存,停用后再 check 拿到 404 即保持现状。要真正撤回客户端内容,须发新版覆盖(如 banners: [] 的新版本)并激活,靠客户端 check 替换。注意停用不发 SSE(仅激活发)。

六、安全约束(实现与运维须共同遵守)

  • 验签信任锚硬编码:三端验签默认且仅用硬编码生产公钥;动态 /public-key 仅自检告警。本地开发后端用的是开发密钥,签出的包生产客户端验签会失败——勿用本地后端签的包测生产客户端。
  • bundle 默认值 = 安全态:每个 flag 的客户端内置默认必须是安全态(iOS 崩溃环会 deactivateAllActive 回滚到 bundle,回滚后仍须安全)。flag 读取做运行时类型归一(远程误填 "false"/0/"" 不得静默架空开关)。
  • telemetry_sampling_rate 绝不可门控设备存活心跳:心跳须无条件 ≤ 10s 上报,否则服务端 watchdog 误报 data_corrupted。该 flag 只能门控可丢弃的行为遥测
  • 只装纯文本/纯数据:禁止下发 HTML / 富文本 / 可执行片段(触界面热更红线 + App Store 2.3.1 / 3.1.1 审核)。
  • 计费 / 权限类 flag:不可仅信任客户端值,server 必须二次校验。
  • 「秒级生效」仅对在线收到 SSE 的设备成立(manifest max-age=60、SSE 5 分钟去重、离线靠冷启动),不可用作需强一致的安全熔断。

七、运维要点

  • 签名密钥:服务端 data/keys/ed25519_resource_pack.{key,pub},上传时服务端用私钥签。轮换流程见 密钥轮换,发布/排障流程见 资源包运维
  • 首个生产包须冒烟:上传 + 激活首包后,用真机/浏览器确认 下载 → 验签通过 → 消费 渲染成功,再放量 stable
  • 新增消费点要发版:admin 文案里的「TTS 语音、词书」等是规划用途,消费侧未实现的 packId 传了也只会「下载验签成功但不应用」。

相关文档

WordForge — 智能英语学习平台