# 微信小游戏强制更新 ## 当前前端接口全部豁免 按当前配置要求,前端调用的 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 = { "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)。