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

152 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 微信小游戏强制更新
## 当前前端接口全部豁免
按当前配置要求,前端调用的 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` 保持原有含义,与本功能无关。
## 人工配置
```ts
// 填写当前小游戏 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 或表单中统一携带:
```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):
```json
{
"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](https://developers.weixin.qq.com/minigame/dev/api/base/update/UpdateManager.applyUpdate.html) 要求收到 `onUpdateReady` 后调用;[官方 API 类型定义](https://github.com/wechat-miniprogram/minigame-api-typings/blob/master/types/wx/lib.wx.api.d.ts) 的 UpdateManager 注释也明确新包下载由微信客户端触发,下载成功才触发 ready。这里的 90% 受影响结论来自本项目服务端规则与平台分发范围独立的推导;没有据此宣称微信会将灰度包提供给未命中玩家。
## 自动化验证
在服务端仓库运行:
```powershell
node --test laf-cloud/tests/client-version-policy.test.mjs laf-cloud/tests/login-wucai-state.test.mjs
```
在 MatchMaster 仓库运行:
```powershell
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 拦截器](https://doc.laf.run/zh/cloud-function/interceptor.html)、[Laf 模块导入](https://doc.laf.run/zh/cloud-function/import.html)、[Laf 执行器](https://github.com/labring/laf/blob/main/runtimes/nodejs/src/support/engine/executor.ts)、[微信更新管理器](https://developers.weixin.qq.com/minigame/dev/api/base/update/wx.getUpdateManager.html)。