feat(framework): config 远程配置分层取参 getParam + 服务器解析 resolveServers

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 16:41:28 +08:00
co-authored by Claude Opus 4.8
parent 231e9ee31c
commit 6333ddc8e4
2 changed files with 124 additions and 0 deletions
@@ -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<RemoteConfig>; }
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 = <T extends Record<string, unknown>>(
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;
}