启动链路真实化:版本解析层对齐线上配置,复刻 Android chuliversion_1(H5 零改动)

对照原 Android 工程 + 线上实测配置,修正 4 处会破坏 H5 适配的根因:
1. 配置树层级改为真实的 agentlist→gamelist→channellist→marketlist
   (原抄了废弃 Bean1 的 agent→channel→market→game)。
2. 资源下载字段名 game_download → 真实的 game_zip(原永远取空,资源升级永不触发)。
3. marketid/版本为数字,匹配/比较做 string↔number 归一(原 ===  "3" 永不命中 market 层)。
4. 大厅 app_config.gameid 为空时回退 version.xml 的 <game id>(复刻 versd.getGameid()),
   否则大厅永远定位不到自己的 game_zip。

实现:
- RemoteConfig/VersionResolver 按真实格式重写,VersionResolver 复刻 chuliversion_1
  的 game/channel/market 三层累积 + market 层 app 升级,"0"/空不覆盖。
- ConfigManager 拆分 fetchRemote(IO) / resolveWith(纯函数 + gameid 回退),移除失效的二级 url 编排。
- VersionXml 补解析 <game id>;ResourceManager 增 localGameId()。
- StartupOrchestrator 调整时序:内置资源(version.xml 种子)就绪→取 gameid→解析远程,拉取仍并行。
- 内置 gamehall_builtin.zip 改为真实版本种子(game id=G2hw... version=42),离线首启阻断、联网下载真实大厅。
- app_config.json gameconfig 切生产端点(去 _test),不再使用测试配置。
- VersionResolver 单测重写为真实格式 + 线上大厅样例。

注:远程配置格式属原生内部消费(H5 不可见),按铁律以线上服务器为准对齐。devecocli build 通过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lanterngamescn
2026-06-26 17:43:20 +08:00
co-authored by Claude Opus 4.8
parent 4b671c7437
commit 799e78d8af
10 changed files with 282 additions and 356 deletions
@@ -7,7 +7,7 @@
"market": "3",
"gameid": "",
"weburl": "",
"gameconfig": "tsgames.daoqi88.cn-config_test-update_jsonv2_test",
"gameconfig": "tsgames.daoqi88.cn-config-update_jsonv2",
"other": "",
"tuiguang": ""
}
+89 -85
View File
@@ -2,11 +2,13 @@ import { describe, it, expect } from '@ohos/hypium';
import { VersionResolver, RemoteConfig, MatchKeys, VersionDecision } from 'domain_resource';
/**
* 分层版本决策单测(T-M2-06,契约 §4.4)。纯函数,构造典型多层样例验证后层覆盖、
* 两棵树取更高 version、showmessage 阻断。
* 分层版本决策单测(对齐真实线上 `.txt` 配置 + Android chuliversion_1)。
* 真实层级:agentlist → gamelist → channellist → marketlist;资源字段 game_zip
* marketid/版本为数字;大厅 gameid 由 version.xml 回退提供。
*/
export default function versionResolverTest() {
const keys: MatchKeys = { agentid: 'A1', channelid: 'C1', marketid: 'M1', gameid: 'G1' };
// 大厅典型 keygameid 来自 version.xml 回退(G1
const keys: MatchKeys = { agentid: 'A1', channelid: 'C1', marketid: '3', gameid: 'G1' };
describe('VersionResolver', () => {
it('empty_config_zero_decision', 0, () => {
@@ -17,74 +19,83 @@ export default function versionResolverTest() {
expect(d.gameDownload).assertEqual('');
});
it('agent_tree_deeper_overrides', 0, () => {
// 代理树:agent 设 game_version=10market 覆盖为 20(深层覆盖浅层)
const remote: RemoteConfig = {
agentlist: [{
agentid: 'A1', game_version: '10', game_download: 'a_url', game_size: '100',
channellist: [{
channelid: 'C1',
marketlist: [{ marketid: 'M1', game_version: '20', game_download: 'm_url' }],
}],
}],
};
const d: VersionDecision = VersionResolver.resolve(remote, keys);
expect(d.gameVersion).assertEqual(20);
expect(d.gameDownload).assertEqual('m_url');
});
it('market_game_level_overrides', 0, () => {
// market.gamelist 中 gameid 命中,覆盖到最深
it('game_level_resource_picked', 0, () => {
// 资源 game_zip/game_version 落在 game 层,channel/market 覆盖 → 取 game 层
const remote: RemoteConfig = {
agentlist: [{
agentid: 'A1',
channellist: [{
channelid: 'C1',
marketlist: [{
marketid: 'M1', game_version: '20',
gamelist: [{ gameid: 'G1', game_version: '30', game_download: 'g_url' }],
}],
gamelist: [{
gameid: 'G1', game_version: 261, game_zip: 'hall.zip', game_size: '4.6M',
channellist: [{ channelid: 'C1', game_version: 0,
marketlist: [{ marketid: 3, app_version: 49, app_download: 'apk' }] }],
}],
}],
};
const d: VersionDecision = VersionResolver.resolve(remote, keys);
expect(d.gameVersion).assertEqual(30);
expect(d.gameDownload).assertEqual('g_url');
expect(d.gameVersion).assertEqual(261);
expect(d.gameDownload).assertEqual('hall.zip');
expect(d.appVersion).assertEqual(49);
expect(d.appDownload).assertEqual('apk');
});
it('two_trees_take_higher_version', 0, () => {
// 代理树 game_version=20,游戏树 game_version=25 → 取 25 及其下载地址
it('deeper_layer_overrides_resource', 0, () => {
// game 层 game_zip=hall.zipmarket 层覆盖为 m.zip / 更高版本
const remote: RemoteConfig = {
agentlist: [{ agentid: 'A1', game_version: '20', game_download: 'agent_url' }],
gamelist: [{
gameid: 'G1',
agentlist: [{ agentid: 'A1', game_version: '25', game_download: 'game_url' }],
agentlist: [{
agentid: 'A1',
gamelist: [{
gameid: 'G1', game_version: 100, game_zip: 'hall.zip',
channellist: [{ channelid: 'C1',
marketlist: [{ marketid: 3, game_version: 120, game_zip: 'm.zip' }] }],
}],
}],
};
const d: VersionDecision = VersionResolver.resolve(remote, keys);
expect(d.gameVersion).assertEqual(25);
expect(d.gameDownload).assertEqual('game_url');
expect(d.gameVersion).assertEqual(120);
expect(d.gameDownload).assertEqual('m.zip');
});
it('app_and_game_independent_winners', 0, () => {
// app 升级取代理树(app_version=49)game 升级取游戏树(game_version=30)
it('numeric_marketid_matches_string_key', 0, () => {
// JSON marketid 为数字 3,本地 key 为字符串 '3',应归一命中
const remote: RemoteConfig = {
agentlist: [{ agentid: 'A1', app_version: '49', app_download: 'apk_url', game_version: '10' }],
gamelist: [{
gameid: 'G1',
agentlist: [{ agentid: 'A1', app_version: '40', game_version: '30', game_download: 'g_url' }],
}],
agentlist: [{ agentid: 'A1',
gamelist: [{ gameid: 'G1', game_version: 50, game_zip: 'z',
channellist: [{ channelid: 'C1',
marketlist: [{ marketid: 3, app_version: 49, app_download: 'apk' }] }] }] }],
};
const d: VersionDecision = VersionResolver.resolve(remote, keys);
expect(d.appVersion).assertEqual(49);
expect(d.appDownload).assertEqual('apk_url');
expect(d.gameVersion).assertEqual(30);
expect(d.gameDownload).assertEqual('g_url');
expect(d.appDownload).assertEqual('apk');
});
it('no_match_returns_empty', 0, () => {
it('zero_game_version_does_not_override', 0, () => {
// channel 层 game_version=0 不应把 game 层 261 覆盖掉
const remote: RemoteConfig = {
agentlist: [{ agentid: 'OTHER', game_version: '99', game_download: 'x' }],
agentlist: [{ agentid: 'A1',
gamelist: [{ gameid: 'G1', game_version: 261, game_zip: 'hall.zip',
channellist: [{ channelid: 'C1', game_version: 0, game_zip: '',
marketlist: [{ marketid: 3, game_version: 0 }] }] }] }],
};
const d: VersionDecision = VersionResolver.resolve(remote, keys);
expect(d.gameVersion).assertEqual(261);
expect(d.gameDownload).assertEqual('hall.zip');
});
it('no_agent_match_returns_empty', 0, () => {
const remote: RemoteConfig = {
agentlist: [{ agentid: 'OTHER',
gamelist: [{ gameid: 'G1', game_version: 99, game_zip: 'x' }] }],
};
const d: VersionDecision = VersionResolver.resolve(remote, keys);
expect(d.gameVersion).assertEqual(0);
expect(d.gameDownload).assertEqual('');
});
it('no_game_match_returns_empty', 0, () => {
// gameid 回退值不匹配任何 gamelist 节点
const remote: RemoteConfig = {
agentlist: [{ agentid: 'A1',
gamelist: [{ gameid: 'OTHER', game_version: 99, game_zip: 'x' }] }],
};
const d: VersionDecision = VersionResolver.resolve(remote, keys);
expect(d.gameVersion).assertEqual(0);
@@ -95,49 +106,42 @@ export default function versionResolverTest() {
const top: RemoteConfig = { showmessage: '停服维护公告' };
expect(VersionResolver.resolve(top, keys).showmessage).assertEqual('停服维护公告');
const layer: RemoteConfig = {
agentlist: [{ agentid: 'A1', showmessage: '代理层公告' }],
agentlist: [{ agentid: 'A1', showmessage: '代理层公告',
gamelist: [{ gameid: 'G1', showmessage: '游戏层公告' }] }],
};
expect(VersionResolver.resolve(layer, keys).showmessage).assertEqual('代理层公告');
// 最后非空命中层(游戏层)生效
expect(VersionResolver.resolve(layer, keys).showmessage).assertEqual('游戏层公告');
});
it('empty_string_does_not_override', 0, () => {
// 深层空串不应覆盖浅层非空值
it('realworld_lobby_sample', 0, () => {
// 复刻线上 tsgames.daoqi88.cn 大厅命中:game 261 + market3 app49market 无 game_zip
const liveKeys: MatchKeys = {
agentid: 'veRa0qrBf0df2K1G4de2tgfmVxB2jxpv',
channelid: 'FtJf073aa0d6rI1xD8J1Y42fINTm0ziK',
marketid: '3',
gameid: 'G2hw0ubng0zcoI0r4mx3H2yr4GejidwO',
};
const remote: RemoteConfig = {
agentlist: [{
agentid: 'A1', game_download: 'keep', game_version: '10',
channellist: [{ channelid: 'C1', game_download: '' }],
agentid: 'veRa0qrBf0df2K1G4de2tgfmVxB2jxpv',
gamelist: [{
gameid: 'G2hw0ubng0zcoI0r4mx3H2yr4GejidwO',
game_version: 261, game_zip: 'http://tsgames.daoqi88.cn/zip2/gamehall.zip', game_size: '4.61M',
channellist: [{
channelid: 'FtJf073aa0d6rI1xD8J1Y42fINTm0ziK', game_version: 0,
marketlist: [
{ marketid: 1, app_version: 1, app_download: '' },
{ marketid: 2, app_version: 43, app_download: 'itms://x' },
{ marketid: 3, app_version: 49, app_download: 'http://daoqi.daoqi88.cn/apk/gamehall.apk' },
],
}],
}],
}],
};
const d: VersionDecision = VersionResolver.resolve(remote, keys);
expect(d.gameDownload).assertEqual('keep');
});
it('empty_gameid_skips_game_tree', 0, () => {
// 大厅 gameid='' 不应误命中 gamelist 中 gameid 为空的节点
const lobbyKeys: MatchKeys = { agentid: 'A1', channelid: 'C1', marketid: 'M1', gameid: '' };
const remote: RemoteConfig = {
gamelist: [{ gameid: '', game_version: '99', game_download: 'should_not_match',
agentlist: [{ agentid: 'A1', game_version: '99', game_download: 'should_not_match' }] }],
agentlist: [{ agentid: 'A1', game_version: '10', game_download: 'agent_only',
channellist: [{ channelid: 'C1',
marketlist: [{ marketid: 'M1',
gamelist: [{ gameid: '', game_version: '88', game_download: 'should_not_match' }] }] }] }],
};
const d: VersionDecision = VersionResolver.resolve(remote, lobbyKeys);
expect(d.gameVersion).assertEqual(10);
expect(d.gameDownload).assertEqual('agent_only');
});
it('tie_prefers_nonempty_download', 0, () => {
// 两树 game_version 相等(20),代理树只有版本号无下载地址 → 应取游戏树的下载地址
const remote: RemoteConfig = {
agentlist: [{ agentid: 'A1', game_version: '20' }],
gamelist: [{ gameid: 'G1',
agentlist: [{ agentid: 'A1', game_version: '20', game_download: 'game_url' }] }],
};
const d: VersionDecision = VersionResolver.resolve(remote, keys);
expect(d.gameVersion).assertEqual(20);
expect(d.gameDownload).assertEqual('game_url');
const d: VersionDecision = VersionResolver.resolve(remote, liveKeys);
expect(d.gameVersion).assertEqual(261);
expect(d.gameDownload).assertEqual('http://tsgames.daoqi88.cn/zip2/gamehall.zip');
expect(d.appVersion).assertEqual(49);
});
});
}