From 6333ddc8e42ffa2b66dc7fb79902c04d3c115b95 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Sun, 28 Jun 2026 16:41:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(framework):=20config=20=E8=BF=9C=E7=A8=8B?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=88=86=E5=B1=82=E5=8F=96=E5=8F=82=20getPar?= =?UTF-8?q?am=20+=20=E6=9C=8D=E5=8A=A1=E5=99=A8=E8=A7=A3=E6=9E=90=20resolv?= =?UTF-8?q?eServers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../assets/framework/config/remote-config.ts | 67 +++++++++++++++++++ .../config/remote-config.test.ts | 57 ++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 cocoscreator_projects/YouleNexus/assets/framework/config/remote-config.ts create mode 100644 cocoscreator_projects/framework-tests/config/remote-config.test.ts diff --git a/cocoscreator_projects/YouleNexus/assets/framework/config/remote-config.ts b/cocoscreator_projects/YouleNexus/assets/framework/config/remote-config.ts new file mode 100644 index 0000000..9bdfa2a --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/framework/config/remote-config.ts @@ -0,0 +1,67 @@ +import type { ChannelIdentity } from './identity.ts'; + +/** 远程配置内层数据(键名逐字一致原 get_paravalue / getConfig_Succ)。 */ +export interface MarketEntry { marketid: string | number; [k: string]: unknown; } +export interface ChannelEntry { channelid: string | number; marketlist?: MarketEntry[]; [k: string]: unknown; } +export interface GameEntry { gameid: string | number; channellist?: ChannelEntry[]; [k: string]: unknown; } +export interface AgentEntry { agentid: string | number; gamelist?: GameEntry[]; [k: string]: unknown; } +export interface RemoteConfigData { agentlist?: AgentEntry[]; [k: string]: unknown; } +export interface RemoteConfig { data?: RemoteConfigData; } + +/** 远程配置抓取抽象(生产注入 HttpConfigFetcher,测试注入 fake)。 */ +export interface ConfigFetcher { fetch(url: string): Promise; } + +export class ConfigFetchError extends Error {} +export class ConfigParseError extends Error {} + +/** + * 分层取参,忠实原 get_paravalue:顶层 data[key] 为默认,逐层进入匹配项; + * 某层有该 key(truthy)则更新返回值;匹配链中断则返回当前最具体值。 + */ +export function getParam(config: RemoteConfig, key: string, id: ChannelIdentity): unknown { + const data = config.data; + if (!data) return null; + let value: unknown = data[key] ? data[key] : null; + + const descend = >( + list: T[] | undefined, idKey: string, idVal: unknown, + ): T | null => { + if (!list) return null; + for (const item of list) { + if (item[idKey] == idVal) { // 宽松比较,id 可能 string/number + if (item[key]) value = item[key]; + return item; + } + } + return null; + }; + + const agent = descend(data.agentlist, 'agentid', id.agentid); + if (!agent) return value; + const game = descend(agent.gamelist, 'gameid', id.gameid); + if (!game) return value; + const channel = descend(game.channellist, 'channelid', id.channelid); + if (!channel) return value; + descend(channel.marketlist, 'marketid', id.marketid); + return value; +} + +/** 连接相关参数键(逐字一致)。优先级即数组顺序。 */ +const SERVER_KEYS = ['player_server_tcp', 'visitor_server_tcp', 'game_server_tcp'] as const; + +function toWs(addr: string): string { + return (addr.startsWith('ws://') || addr.startsWith('wss://')) ? addr : 'ws://' + addr; +} + +/** 解析 ws 候选服务器列表(按 SERVER_KEYS 优先级,去重)。 */ +export function resolveServers(config: RemoteConfig, id: ChannelIdentity): string[] { + const out: string[] = []; + for (const key of SERVER_KEYS) { + const v = getParam(config, key, id); + if (typeof v === 'string' && v) { + const ws = toWs(v); + if (!out.includes(ws)) out.push(ws); + } + } + return out; +} diff --git a/cocoscreator_projects/framework-tests/config/remote-config.test.ts b/cocoscreator_projects/framework-tests/config/remote-config.test.ts new file mode 100644 index 0000000..99fa95a --- /dev/null +++ b/cocoscreator_projects/framework-tests/config/remote-config.test.ts @@ -0,0 +1,57 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { getParam, resolveServers, type RemoteConfig } from '../../YouleNexus/assets/framework/config/remote-config.ts'; +import type { ChannelIdentity } from '../../YouleNexus/assets/framework/config/identity.ts'; + +const ID: ChannelIdentity = { agentid: 'A', channelid: 'C', gameid: 'G', marketid: 2, version: '1', versionCode: 1 }; + +const CONFIG: RemoteConfig = { + data: { + player_server_tcp: '1.1.1.1:1000', // 顶层默认 + agentlist: [{ + agentid: 'A', + player_server_tcp: '2.2.2.2:2000', // agent 级覆盖 + gamelist: [{ + gameid: 'G', + channellist: [{ + channelid: 'C', + player_server_tcp: '3.3.3.3:3000', // channel 级覆盖(最具体) + visitor_server_tcp: '4.4.4.4:4000', + marketlist: [{ marketid: 2 }], + }], + }], + }], + }, +}; + +test('getParam:最具体层级胜出', () => { + assert.equal(getParam(CONFIG, 'player_server_tcp', ID), '3.3.3.3:3000'); +}); + +test('getParam:channel 未匹配时回退到 agent 级值', () => { + const id2 = { ...ID, channelid: 'OTHER' }; + assert.equal(getParam(CONFIG, 'player_server_tcp', id2), '2.2.2.2:2000'); +}); + +test('getParam:agent 未匹配时回退到顶层默认', () => { + const id3 = { ...ID, agentid: 'NONE' }; + assert.equal(getParam(CONFIG, 'player_server_tcp', id3), '1.1.1.1:1000'); +}); + +test('getParam:键不存在 → null', () => { + assert.equal(getParam(CONFIG, 'not_a_key', ID), null); +}); + +test('getParam:无 data → null', () => { + assert.equal(getParam({}, 'player_server_tcp', ID), null); +}); + +test('resolveServers:player 优先、visitor 回退,前缀 ws://', () => { + assert.deepEqual(resolveServers(CONFIG, ID), ['ws://3.3.3.3:3000', 'ws://4.4.4.4:4000']); +}); + +test('resolveServers:已带 ws:// 不重复前缀;无地址 → 空数组', () => { + const cfg: RemoteConfig = { data: { player_server_tcp: 'ws://9.9.9.9:9' } }; + assert.deepEqual(resolveServers(cfg, ID), ['ws://9.9.9.9:9']); + assert.deepEqual(resolveServers({ data: {} }, ID), []); +});