fix(config): VersionResolver 改为 4 层逐级回退(agent/game/channel/market 每层 merge 全字段)

原实现资源字段 game_zip/game_version 只在 game/channel/market 三层覆盖、app 字段只在
market 层,漏了 agent 层。按 4 层模型:每个字段取最深命中层非空值、深层缺省逐级回退到根。
Acc 合并为单个 merge() 每层调用。新增 agent层回退/app上浮回退/四层全覆盖 3 个单测。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lanterngamescn
2026-06-27 18:49:05 +08:00
co-authored by Claude Opus 4.8
parent 763ee42345
commit a6ef891507
3 changed files with 73 additions and 39 deletions
@@ -8,8 +8,9 @@
* 真实字段要点(与旧实现的差异,均为"破坏 H5 适配"的根因):
* - 资源下载字段名是 **game_zip**(非 game_download)。
* - marketid / app_version / game_version 在 JSON 里是**数字**,匹配/比较需做 string↔number 归一。
* - app 升级字段 app_download/app_version 落在 market 层;资源字段 game_zip/game_version
* 可出现在 game / channel / market 各层,越深越后、覆盖前层("0"/空视为未设置不覆盖)。
* - **4 层逐级回退**game_zip/game_version 与 app_download/app_version 等所有升级字段,
* 可出现在 agent / game / channel / market 任一层(节点类型均 extends VersionFields),
* 取最深命中层的非空值、深层缺省则逐级回退到浅层直至根("0"/空视为未设置不覆盖)。
*/
/** JSON 里既可能是字符串也可能是数字的标量(id、版本号等)。 */
@@ -1,15 +1,14 @@
/**
* 分层版本决策 —— **复刻 Android `chuliversion_1` 真实逻辑**(纯函数,无 IO,便于单测)。
* 分层版本决策 —— **4 层逐级回退模型**(纯函数,无 IO,便于单测)。
*
* 遍历真实层级 agentlist → gamelist → channellist → marketlist逐层下钻匹配
* agentid / gameid / channelid / marketidmarketid 数字与本地字符串归一比较)。
* 层级(由浅入深 1→4):agentlist(1) → gamelist(2) → channellist(3) → marketlist(4)
* 逐层用 agentid / gameid / channelid / marketid 匹配(marketid 数字与本地字符串归一比较)。
*
* 累积规则(与 Android 一致):
* - 资源升级 game_zip/game_version:在 game、channel、market 三层累积,越深越后、覆盖前层;
* "0" 或空的 game_version 不覆盖(视为未设置)。
* - App 升级 app_download/app_version:取 market 层值
* - showmessage:任一命中层非空即生效(阻断)
* - 最终资源版本与本地 version.xml 比较由调用方决定是否下载 game_zip。
* 取值规则(**每个字段独立**):取**最深命中层**的非空值;该层缺失则逐级回退到上一层,
* 直到根节点(showmessage 根层可携带;game/app 升级字段根层无、回退至多到 agent 层)。
* 实现等价:由浅入深遍历,每命中一层就对全字段做"非空(zip/download/size/msg)或非零(version)即覆盖前层"
* 故越深的非空值覆盖越浅、深层缺省则保留浅层值。"0"/空视为未设置、不覆盖
* 不再区分"资源字段只在 game/channel/market、app 字段只在 market"——四层对所有字段一视同仁
*/
import { AgentNode, ChannelNode, GameNode, MarketNode,
MatchKeys, RemoteConfig, Scalar, VersionDecision, VersionFields, asStr } from '../config/RemoteConfig';
@@ -24,12 +23,12 @@ function parseVer(v: Scalar | undefined): number {
return isNaN(n) ? 0 : n;
}
/** game_version 是否"有效非零"(用于决定是否覆盖累积值)。 */
/** 版本是否"有效非零"(用于决定是否覆盖累积值)。 */
function hasVer(v: Scalar | undefined): boolean {
return parseVer(v) > 0;
}
/** 累积器:复刻 chuliversion_1 的 newgame_zip / newgame_version + app 字段 + showmessage。 */
/** 累积器:由浅入深逐层 merge,每字段非空/非零覆盖前层(= 取最深命中层值、深层缺省回退浅层)。 */
class Acc {
gameZip: string = '';
gameVersion: number = 0;
@@ -39,8 +38,8 @@ class Acc {
appSize: string = '';
showmessage: string = '';
/** 资源字段覆盖(game/channel/market 各层调用):非空 zip 覆盖;非零 version 覆盖。 */
mergeGame(n: VersionFields): void {
/** 单层全字段覆盖(agent/game/channel/market 每命中一层都调用)。 */
merge(n: VersionFields): void {
const zip: string = n.game_zip ?? '';
if (zip !== '') {
this.gameZip = zip;
@@ -48,25 +47,21 @@ class Acc {
if (hasVer(n.game_version)) {
this.gameVersion = parseVer(n.game_version);
}
const size: string = n.game_size ?? '';
if (size !== '') {
this.gameSize = size;
const gsize: string = n.game_size ?? '';
if (gsize !== '') {
this.gameSize = gsize;
}
}
/** App 升级字段(market 层):下载地址与版本均非空才采纳。 */
mergeApp(n: VersionFields): void {
const dl: string = n.app_download ?? '';
const ver: string = asStr(n.app_version);
if (dl !== '' && ver !== '') {
if (dl !== '') {
this.appDownload = dl;
this.appVersion = parseVer(n.app_version);
this.appSize = n.app_size ?? '';
}
}
/** 命中层公告非空即记录(最后非空者生效)。 */
mergeMsg(n: VersionFields): void {
if (hasVer(n.app_version)) {
this.appVersion = parseVer(n.app_version);
}
const asize: string = n.app_size ?? '';
if (asize !== '') {
this.appSize = asize;
}
const msg: string = n.showmessage ?? '';
if (msg !== '') {
this.showmessage = msg;
@@ -75,7 +70,7 @@ class Acc {
}
export class VersionResolver {
/** 真实层级遍历agent → game → channel → market。 */
/** 4 层逐级回退agent → game → channel → market,逐层 merge 全字段(深层覆盖浅层、缺省回退)。 */
static resolve(remote: RemoteConfig, keys: MatchKeys): VersionDecision {
const acc: Acc = new Acc();
const rootMsg: string = remote.showmessage ?? '';
@@ -86,23 +81,19 @@ export class VersionResolver {
const agent: AgentNode | undefined =
(remote.agentlist ?? []).find((a: AgentNode) => (a.agentid ?? '') === keys.agentid);
if (agent !== undefined) {
acc.mergeMsg(agent);
acc.merge(agent); // 层1 agent
const game: GameNode | undefined =
(agent.gamelist ?? []).find((g: GameNode) => (g.gameid ?? '') === keys.gameid);
if (game !== undefined) {
acc.mergeMsg(game);
acc.mergeGame(game);
acc.merge(game); // 层2 game
const channel: ChannelNode | undefined =
(game.channellist ?? []).find((c: ChannelNode) => (c.channelid ?? '') === keys.channelid);
if (channel !== undefined) {
acc.mergeMsg(channel);
acc.mergeGame(channel);
acc.merge(channel); // 层3 channel
const market: MarketNode | undefined =
(channel.marketlist ?? []).find((m: MarketNode) => asStr(m.marketid) === keys.marketid);
if (market !== undefined) {
acc.mergeMsg(market);
acc.mergeGame(market);
acc.mergeApp(market);
acc.merge(market); // 层4 market
}
}
}