Files
server/laf-cloud/docs/client-version-update.md
T

13 KiB
Raw Blame History

微信小游戏强制更新

当前前端接口全部豁免

按当前配置要求,前端调用的 49 个后端接口名已全部加入 versionPolicy.ts 的 nonPlayerFunctions,包括 login、存档、活动和前端支付请求。该名单优先级最高:即使接口出现在 apiBlacklist、玩家命中 openidWhitelist、版本缺失或过低、当前策略配置无效,也会跳过版本检查,继续执行原接口鉴权和业务逻辑。登录函数内的延后检查也复用同一豁免规则。

清单覆盖主包和分包,以及动态选择的支付/活动入口;按真实后端函数名维护,getAddressRankSelf 实际请求 getAddressRank。getRank、getSRank 和五彩地址排行实际请求其他服务,在本文件添加对应名字不会改变那些服务的部署配置。第三方 SDK、CDN 资源请求不属于本后端云函数清单。

发布 versionPolicy 后生效。后续若要恢复某个接口的强更检查,先从 nonPlayerFunctions 删除该接口,再按下述版本策略配置;以下强更流程仅适用于未被豁免的接口。

当前交付与默认状态

  • functions/versionPolicy.ts:人工维护版本策略,代码发布后生效,不使用数据库集合。
  • functions/__interceptor__.ts:Laf 全局 HTTP 云函数拦截器;白名单非空时,登录校验延后到 login.ts 取得微信身份后、任何账号写入之前。
  • 两个模块的 YAML 均为 methods: [],不要开放直接 HTTP 访问。
  • 客户端 MatchMaster/assets/Script/module/Pay/Utils.ts 的 POST、POSTJSON 均注入 GameConfig.GM_INFO.version,并统一识别强更错误。
  • 客户端 ClientUpdate.ts 在启动时注册微信更新管理器,在服务端要求强更时阻断游戏;普通更新不主动打断玩家。
  • 初始配置为 currentVersion = "1.0.82"、force: false、minVersion: null、apiBlacklist: [],默认不拦截任何版本。首次正式发布时,根据实际发布版本维护 GameConfig 与服务端配置。
  • 原登录回包的 data.version = "9.9.99" 及存档覆盖字段 forcedUpdate 保持原有含义,与本功能无关。

人工配置

// 填写当前小游戏 AppID 下的 openid。[] 表示对所有玩家执行版本检查。
export const openidWhitelist: string[] = ["openid_of_test_player"];

export const currentVersion = "1.0.84";
export const versions: Record<string, VersionPolicy> = {
  "1.0.83": {
    force: true,
    minVersion: "1.0.83",
    apiBlacklist: ["userData", "userCoin", "cloudRise/index"],
  },
  "1.0.84": {
    force: false,
    minVersion: "1.0.83",
    apiBlacklist: ["userData", "userCoin", "cloudRise/index"],
  },
};

这里的接口仅为示例,实际名单由维护者填写,不自动加入、推断或合并历史名单。

openidWhitelist 是同一模块中独立于 versions 的全局名单,适用于当前策略中的登录及黑名单接口:非空时仅名单内玩家受版本门槛限制;清空后立即恢复全量检查。未加入名单的玩家跳过全部版本门槛(包括历史最低兼容版本),后端必须仍兼容这些旧客户端。openid 精确匹配、区分大小写,不接受空字符串、空白或非字符串元素。修改后发布 versionPolicy 才生效,客户端无需新增 openid 请求字段。

登录使用微信 code2session 返回的真实 openid,首次注册的玩家也能命中;只交换一次登录 code。其他受限请求按现有 token 查询 users,未找到时查询 usersAd,使用数据库中的 openid;不使用客户端自报的 openid、uid 或 gameName 判断白名单。token 缺失、非法或失效时继续交给原接口鉴权,不强行提示版本更新;数据库查询失败则返回暂不可用,不绕过检查。兼容版本、非黑名单接口和空白名单模式均不额外查询身份。

  • currentVersion 指向服务端正在生效的完整策略,不能按请求版本选择历史配置,也不能按对象键顺序选最新版。
  • force 标识该次发布是否为破坏性更新,不是取消所有历史门槛的开关。
  • minVersion: null 明确关闭版本门槛。普通更新应保留最低兼容版本;降低门槛需要人工修改,并确认后端仍能兼容旧数据。
  • force: true 时必须同时设置 minVersion === currentVersion;force: false 时可以保留较低门槛,且 minVersion 不能大于 currentVersion。
  • 版本必须为三个非负整数组成的字符串,不接受空白、前导零、预发布后缀或超出安全整数范围的分段;按数值比较,例如 1.0.10 > 1.0.9。
  • 接口名使用 Laf 的完整函数名,精确且区分大小写,例如 cloudRise/index;不要写域名、开头的 /、通配符或 action。
  • 登录固定检查,无须加入黑名单。名单中一个接口的所有 action(含读取)都会受限制。
  • 微信支付、客服等服务端回调、后台任务和管理接口不得加入玩家名单;实现对现有回调和已识别的后台入口另有排除,防止误伤。新增非玩家入口时也应检查该排除规则。
  • 配置条目不存在、版本/字段格式错误、缺少 minVersion 等属于错误配置,不等同于关闭。返回 CLIENT_UPDATE_CONFIG_UNAVAILABLE,不执行玩家业务。非玩家排除入口不受影响。

请求和响应

客户端 POST JSON 或表单中统一携带:

{ "version": "1.0.82", "token": "...", "action": "read" }

即使调用方自行传入 version,请求入口也会覆盖为 GameConfig 中的实际版本;不会覆盖调用方原对象。登录和不带 token 的公开玩家请求同样上报。旧排行榜/五彩服务的请求也上报,但那些服务的服务端拦截不在本次范围内。资源下载、微信原生支付和第三方埋点不套用玩家业务协议。

服务端流程:

  1. OPTIONS、内部 call 及已识别的非玩家入口放行。
  2. 获取当前服务端策略并检查配置有效性。
  3. 非 login 且不在当前黑名单的请求放行。
  4. minVersion === null 放行。
  5. 相等或更高版本放行;缺失、非法或低于最低兼容版本时,空白名单直接返回强更错误,非空白名单仅对服务端确认命中的玩家返回强更错误。登录在取得微信身份后执行这一步。
  6. 放行仅表示继续执行目标函数,目标函数原有鉴权和业务检查不变。

POST 从 ctx.body.version 读取;GET/HEAD 从 ctx.query.version 读取,不相互兜底。目标函数名取运行时 ctx.__function_name,兼容回退为 ctx.request.path 去掉首个斜杠,不接受客户端自报的函数名。

强更错误使用 HTTP 200 的业务 JSON(现有客户端只解析 2xx):

{
  "code": 0,
  "errorCode": "CLIENT_UPDATE_REQUIRED",
  "msg": "当前游戏版本过低,请更新后重新进入",
  "data": { "version": "1.0.84", "minVersion": "1.0.83" }
}

配置异常使用 code: 0、errorCode: "CLIENT_UPDATE_CONFIG_UNAVAILABLE"、data: null、msg: "服务暂不可用,请稍后重试",不触发更新界面。策略本身不从数据库读取;白名单身份查询失败也返回暂不可用。非法配置对非玩家入口之外的请求保守返回暂不可用,与是否命中白名单无关。

客户端行为

  • 首次收到强更错误后,暂停场景输入、游戏与音频,阻止所有后续玩家业务请求。
  • 同一进程内不解除阻断;后续并发成功回包也转换为强更错误,不再进入成功业务逻辑。已经在服务器执行完的操作不会回滚,客户端不自动重放。
  • 已收到 onUpdateReady 时,只显示一个不可取消的“重启更新”弹窗,用户点击后调用 applyUpdate()。重启沿用既有自动登录流程。
  • 检查或下载中展示“正在准备更新”。等待超过 30 秒、检查无新包、下载失败或不支持更新管理器时,维持阻断,提示退出后重新进入;后续若收到 ready 仍可切换为重启提示。
  • 微信更新管理器没有主动重新下载 API,不能把重新调用 getUpdateManager() 当成下载重试。
  • 返回前台不会恢复游戏或绕过更新状态;原登录轮询、登录失败重试与进入 HomeScene 的异步回调都检查阻断状态。
  • 更新交互仅支持微信小游戏,使用微信原生提示和更新管理器,不提供编辑器/浏览器预览、DOM 面板或模拟开关。

发布顺序

  1. 核实线上 Laf/Sealaf 支持双参数 (ctx, next) 全局拦截器。不能给 next 设置默认值,平台按函数形参数量选择执行方式。
  2. 发布 versionPolicy 的默认关闭配置,再发布 __interceptor__。确认现有线上拦截器没有其他逻辑需要合并,切勿直接覆盖线上独有逻辑。
  3. 发布包含上报与更新处理的新客户端。GameConfig 中版本应与实际游戏包版本一致,正式版和体验版使用同一套判断;当前项目测试/正式 URL 相同,不会按 isDebug 自动豁免。
  4. 真机确认新包可获取后,再发布提高门槛的 versionPolicy;部署完成后请求受保护接口验证新配置已生效。
  5. 后续普通更新复制有效策略,保留最低门槛和仍需要限制的接口。不要因 force 改为 false 而清空门槛。

旧包没有更新处理代码,只能被服务端拒绝,无法凭新错误码为旧包补上更新交互。启用门槛前应完成过渡版本发布。设置最低版本高于平台实际可获取的版本会让玩家停在更新提示,因此必须遵循先发布包、验证可获取、再启用门槛的顺序。

发布白名单功能

首次上线白名单功能需要一并发布 versionPolicy、__interceptor__ 和 login;后续只调整名单或版本策略时发布 versionPolicy 即可。

与微信灰度发布的关系

服务端名单与微信后台灰度比例独立,不会自动同步。按本实现推导:若新版本仅向 10% 玩家发布,却把服务端最低兼容版本提高到该版本且白名单为空,另外 90% 中低于门槛的玩家在登录或访问黑名单接口时同样会被要求更新;但微信未向他们提供新包时,更新管理器无法完成更新,客户端会停在更新阻断流程中。

白名单只能限制“谁需要强更”,不能让名单内玩家自动取得微信灰度包。灰度期间应保持原有全量兼容门槛;要测试新门槛,使用非空白名单,并先确认这些账号实际可取得目标包。全量发布且确认目标包可获取后,再启用全量新门槛。不要用“清空白名单”表示暂停测试,因为清空意味着全量检查。

微信官方 UpdateManager.applyUpdate 要求收到 onUpdateReady 后调用;官方 API 类型定义 的 UpdateManager 注释也明确新包下载由微信客户端触发,下载成功才触发 ready。这里的 90% 受影响结论来自本项目服务端规则与平台分发范围独立的推导;没有据此宣称微信会将灰度包提供给未命中玩家。

自动化验证

在服务端仓库运行:

node --test laf-cloud/tests/client-version-policy.test.mjs laf-cloud/tests/login-wucai-state.test.mjs

在 MatchMaster 仓库运行:

node --test tools/test-client-update.cjs tools/test-wucai-request-barrier.cjs tools/test-activity-schedule.cjs

自动测试覆盖数值版本比较、非法/缺失版本、持续门槛、黑名单精确匹配、拦截前无业务执行、回调豁免、GET/POST 参数、全入口上报、并发回包、更新就绪/失败/无更新/超时、回前台和登录重试。客户端更新测试使用不含 DOM 的微信 API 模拟环境。

上线前真机验证:

  • 兼容包正常自动登录;旧包的 login、黑名单接口返回强更;非黑名单接口仍按原逻辑执行。
  • 先开旧包再发布新包:新包 ready 后点击重启,重新上报新版本并自动登录。
  • 在线游戏收到强更后不能操作、不能继续发送玩家请求;切后台再回来仍阻断。
  • 断网、无新包、下载失败时不放行;恢复并重新进入后可取得新版。
  • 微信支付/客服回调和任务继续正常运行。
  • 正式版和体验版分别验证。体验版需实际上传两个包测试,模拟 API 的单元测试不能证明微信分发行为。

强更机制测试使用未豁免的模拟接口,覆盖版本比较、名单内外玩家、伪造 openid、GET/POST token 来源、身份查询失败和业务异常传播。登录回归验证旧版/缺失/非法版本在名单为空或非空、配置有效或无效时,仍按接口豁免规则正常登录。login-wucai-state.test.mjs 的既有用例 login opens the personal CloudRise period after resolving the account 在改动前后均失败(null !== 1);此外本地人工配置 force: true, minVersion: null 会使当前策略有效性测试失败。两项均不能表述为回归通过;当前接口豁免不会修正配置,只是跳过该校验。

参考:Laf 拦截器、Laf 模块导入、Laf 执行器、微信更新管理器。