diff --git a/domain_resource/src/main/ets/config/RemoteConfig.ets b/domain_resource/src/main/ets/config/RemoteConfig.ets index 5d6d78c..86d009a 100644 --- a/domain_resource/src/main/ets/config/RemoteConfig.ets +++ b/domain_resource/src/main/ets/config/RemoteConfig.ets @@ -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、版本号等)。 */ diff --git a/domain_resource/src/main/ets/version/VersionResolver.ets b/domain_resource/src/main/ets/version/VersionResolver.ets index e56e139..090cf0c 100644 --- a/domain_resource/src/main/ets/version/VersionResolver.ets +++ b/domain_resource/src/main/ets/version/VersionResolver.ets @@ -1,15 +1,14 @@ /** - * 分层版本决策 —— **复刻 Android `chuliversion_1` 真实逻辑**(纯函数,无 IO,便于单测)。 + * 分层版本决策 —— **4 层逐级回退模型**(纯函数,无 IO,便于单测)。 * - * 遍历真实层级 agentlist → gamelist → channellist → marketlist,逐层下钻匹配 - * agentid / gameid / channelid / marketid(marketid 数字与本地字符串归一比较)。 + * 层级(由浅入深 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 } } } diff --git a/entry/src/test/VersionResolver.test.ets b/entry/src/test/VersionResolver.test.ets index 7ba7be9..6f8bf86 100644 --- a/entry/src/test/VersionResolver.test.ets +++ b/entry/src/test/VersionResolver.test.ets @@ -143,5 +143,47 @@ export default function versionResolverTest() { expect(d.gameDownload).assertEqual('http://tsgames.daoqi88.cn/zip2/gamehall.zip'); expect(d.appVersion).assertEqual(49); }); + + it('agent_level_resource_fallback', 0, () => { + // 4 层回退:game_zip/game_version 仅在 agent(1) 层;game/channel/market 命中但不携带 → 回退取 agent 层 + const remote: RemoteConfig = { + agentlist: [{ + agentid: 'A1', game_version: 200, game_zip: 'agent.zip', game_size: '9.9M', + gamelist: [{ gameid: 'G1', + channellist: [{ channelid: 'C1', + marketlist: [{ marketid: 3 }] }] }], + }], + }; + const d: VersionDecision = VersionResolver.resolve(remote, keys); + expect(d.gameVersion).assertEqual(200); + expect(d.gameDownload).assertEqual('agent.zip'); + expect(d.gameSize).assertEqual('9.9M'); + }); + + it('app_fields_fall_back_above_market', 0, () => { + // 4 层回退:app 字段在 channel(3) 层;market(4) 命中但无 app → 回退取 channel 层 + const remote: RemoteConfig = { + agentlist: [{ agentid: 'A1', + gamelist: [{ gameid: 'G1', + channellist: [{ channelid: 'C1', app_version: 70, app_download: 'ch.apk', + marketlist: [{ marketid: 3 }] }] }] }], + }; + const d: VersionDecision = VersionResolver.resolve(remote, keys); + expect(d.appVersion).assertEqual(70); + expect(d.appDownload).assertEqual('ch.apk'); + }); + + it('deepest_market_overrides_agent_resource', 0, () => { + // 4 层全部携带 game_zip:market(4) 最深,应覆盖 agent/game/channel + const remote: RemoteConfig = { + agentlist: [{ agentid: 'A1', game_zip: 'a.zip', game_version: 10, + gamelist: [{ gameid: 'G1', game_zip: 'g.zip', game_version: 20, + channellist: [{ channelid: 'C1', game_zip: 'c.zip', game_version: 30, + marketlist: [{ marketid: 3, game_zip: 'm.zip', game_version: 40 }] }] }] }], + }; + const d: VersionDecision = VersionResolver.resolve(remote, keys); + expect(d.gameVersion).assertEqual(40); + expect(d.gameDownload).assertEqual('m.zip'); + }); }); }