refactor(config): VersionResolver 抽成统一取值函数接口(与字段解耦)
matchedLayers() 一次算命中层链(含根伪层) + pickStr()/pickVer() 对任意字段做 4 层逐级回退取值;resolve() 各字段一行 selector 复用同一取值逻辑,新增字段免改取值核心。 替换原逐字段硬编码的 Acc.merge。行为不变,12 单测通过。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a6ef891507
commit
e5d8eeb3b1
@@ -1,19 +1,22 @@
|
||||
/**
|
||||
* 分层版本决策 —— **4 层逐级回退模型**(纯函数,无 IO,便于单测)。
|
||||
* 分层版本决策 —— 4 层逐级回退模型(纯函数,无 IO,便于单测)。
|
||||
*
|
||||
* 层级(由浅入深 1→4):agentlist(1) → gamelist(2) → channellist(3) → marketlist(4),
|
||||
* 逐层用 agentid / gameid / channelid / marketid 匹配(marketid 数字与本地字符串归一比较)。
|
||||
*
|
||||
* 取值规则(**每个字段独立**):取**最深命中层**的非空值;该层缺失则逐级回退到上一层,
|
||||
* 直到根节点(showmessage 根层可携带;game/app 升级字段根层无、回退至多到 agent 层)。
|
||||
* 实现等价:由浅入深遍历,每命中一层就对全字段做"非空(zip/download/size/msg)或非零(version)即覆盖前层",
|
||||
* 故越深的非空值覆盖越浅、深层缺省则保留浅层值。"0"/空视为未设置、不覆盖。
|
||||
* 不再区分"资源字段只在 game/channel/market、app 字段只在 market"——四层对所有字段一视同仁。
|
||||
* 取值是**统一函数接口**,与具体字段解耦:
|
||||
* 1. matchedLayers() 一次算出命中层链(由浅入深;遇首个不匹配即止),含根伪层(仅 showmessage)。
|
||||
* 2. pickStr()/pickVer() 对**任意字段**用同一规则取值:取最深命中层的非空值,
|
||||
* 深层缺省则逐级回退到浅层直至根。空串(字符串)、"0"/空(版本) 视为未设置、不参与覆盖。
|
||||
* 新增可下发字段 = resolve() 里加一行 selector,无需改取值逻辑。
|
||||
*/
|
||||
import { AgentNode, ChannelNode, GameNode, MarketNode,
|
||||
MatchKeys, RemoteConfig, Scalar, VersionDecision, VersionFields, asStr } from '../config/RemoteConfig';
|
||||
|
||||
/** 版本号转整数(空/"0"/非数字 → 0)。 */
|
||||
/** 从一层节点取某字段标量(不存在 → undefined)。供统一取值函数注入。 */
|
||||
export type FieldSelector = (n: VersionFields) => Scalar | undefined;
|
||||
|
||||
/** 版本号转整数(空、"0"、非数字 → 0)。 */
|
||||
function parseVer(v: Scalar | undefined): number {
|
||||
const s: string = asStr(v);
|
||||
if (s === '') {
|
||||
@@ -23,90 +26,74 @@ function parseVer(v: Scalar | undefined): number {
|
||||
return isNaN(n) ? 0 : n;
|
||||
}
|
||||
|
||||
/** 版本是否"有效非零"(用于决定是否覆盖累积值)。 */
|
||||
function hasVer(v: Scalar | undefined): boolean {
|
||||
return parseVer(v) > 0;
|
||||
}
|
||||
|
||||
/** 累积器:由浅入深逐层 merge,每字段非空/非零覆盖前层(= 取最深命中层值、深层缺省回退浅层)。 */
|
||||
class Acc {
|
||||
gameZip: string = '';
|
||||
gameVersion: number = 0;
|
||||
gameSize: string = '';
|
||||
appDownload: string = '';
|
||||
appVersion: number = 0;
|
||||
appSize: string = '';
|
||||
showmessage: string = '';
|
||||
|
||||
/** 单层全字段覆盖(agent/game/channel/market 每命中一层都调用)。 */
|
||||
merge(n: VersionFields): void {
|
||||
const zip: string = n.game_zip ?? '';
|
||||
if (zip !== '') {
|
||||
this.gameZip = zip;
|
||||
}
|
||||
if (hasVer(n.game_version)) {
|
||||
this.gameVersion = parseVer(n.game_version);
|
||||
}
|
||||
const gsize: string = n.game_size ?? '';
|
||||
if (gsize !== '') {
|
||||
this.gameSize = gsize;
|
||||
}
|
||||
const dl: string = n.app_download ?? '';
|
||||
if (dl !== '') {
|
||||
this.appDownload = dl;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class VersionResolver {
|
||||
/** 4 层逐级回退:agent → game → channel → market,逐层 merge 全字段(深层覆盖浅层、缺省回退)。 */
|
||||
static resolve(remote: RemoteConfig, keys: MatchKeys): VersionDecision {
|
||||
const acc: Acc = new Acc();
|
||||
const rootMsg: string = remote.showmessage ?? '';
|
||||
if (rootMsg !== '') {
|
||||
acc.showmessage = rootMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 命中层链(由浅入深):根伪层(仅 showmessage) → agent → game → channel → market,
|
||||
* 遇首个不匹配的层即止。后续 pickStr/pickVer 在此链上做"深层覆盖浅层"。
|
||||
*/
|
||||
static matchedLayers(remote: RemoteConfig, keys: MatchKeys): VersionFields[] {
|
||||
const layers: VersionFields[] = [{ showmessage: remote.showmessage }];
|
||||
const agent: AgentNode | undefined =
|
||||
(remote.agentlist ?? []).find((a: AgentNode) => (a.agentid ?? '') === keys.agentid);
|
||||
if (agent !== undefined) {
|
||||
acc.merge(agent); // 层1 agent
|
||||
const game: GameNode | undefined =
|
||||
(agent.gamelist ?? []).find((g: GameNode) => (g.gameid ?? '') === keys.gameid);
|
||||
if (game !== undefined) {
|
||||
acc.merge(game); // 层2 game
|
||||
const channel: ChannelNode | undefined =
|
||||
(game.channellist ?? []).find((c: ChannelNode) => (c.channelid ?? '') === keys.channelid);
|
||||
if (channel !== undefined) {
|
||||
acc.merge(channel); // 层3 channel
|
||||
const market: MarketNode | undefined =
|
||||
(channel.marketlist ?? []).find((m: MarketNode) => asStr(m.marketid) === keys.marketid);
|
||||
if (market !== undefined) {
|
||||
acc.merge(market); // 层4 market
|
||||
}
|
||||
}
|
||||
if (agent === undefined) {
|
||||
return layers;
|
||||
}
|
||||
layers.push(agent);
|
||||
const game: GameNode | undefined =
|
||||
(agent.gamelist ?? []).find((g: GameNode) => (g.gameid ?? '') === keys.gameid);
|
||||
if (game === undefined) {
|
||||
return layers;
|
||||
}
|
||||
layers.push(game);
|
||||
const channel: ChannelNode | undefined =
|
||||
(game.channellist ?? []).find((c: ChannelNode) => (c.channelid ?? '') === keys.channelid);
|
||||
if (channel === undefined) {
|
||||
return layers;
|
||||
}
|
||||
layers.push(channel);
|
||||
const market: MarketNode | undefined =
|
||||
(channel.marketlist ?? []).find((m: MarketNode) => asStr(m.marketid) === keys.marketid);
|
||||
if (market !== undefined) {
|
||||
layers.push(market);
|
||||
}
|
||||
return layers;
|
||||
}
|
||||
|
||||
/** 统一取值(字符串字段):层链里取最深命中层的非空值;空串视为未设置。 */
|
||||
static pickStr(layers: VersionFields[], select: FieldSelector): string {
|
||||
let result: string = '';
|
||||
for (let i = 0; i < layers.length; i++) {
|
||||
const s: string = asStr(select(layers[i]));
|
||||
if (s !== '') {
|
||||
result = s; // 由浅入深,越深覆盖越浅
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 统一取值(版本字段):层链里取最深命中层的非零版本;"0"/空视为未设置。 */
|
||||
static pickVer(layers: VersionFields[], select: FieldSelector): number {
|
||||
let result: number = 0;
|
||||
for (let i = 0; i < layers.length; i++) {
|
||||
const v: number = parseVer(select(layers[i]));
|
||||
if (v > 0) {
|
||||
result = v;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 产出版本决策:算一次层链,各字段用统一取值函数 + selector 解析(4 层逐级回退)。 */
|
||||
static resolve(remote: RemoteConfig, keys: MatchKeys): VersionDecision {
|
||||
const layers: VersionFields[] = VersionResolver.matchedLayers(remote, keys);
|
||||
return {
|
||||
showmessage: acc.showmessage,
|
||||
appVersion: acc.appVersion,
|
||||
appDownload: acc.appDownload,
|
||||
appSize: acc.appSize,
|
||||
gameVersion: acc.gameVersion,
|
||||
gameDownload: acc.gameZip,
|
||||
gameSize: acc.gameSize,
|
||||
showmessage: VersionResolver.pickStr(layers, (n: VersionFields) => n.showmessage),
|
||||
appVersion: VersionResolver.pickVer(layers, (n: VersionFields) => n.app_version),
|
||||
appDownload: VersionResolver.pickStr(layers, (n: VersionFields) => n.app_download),
|
||||
appSize: VersionResolver.pickStr(layers, (n: VersionFields) => n.app_size),
|
||||
gameVersion: VersionResolver.pickVer(layers, (n: VersionFields) => n.game_version),
|
||||
gameDownload: VersionResolver.pickStr(layers, (n: VersionFields) => n.game_zip),
|
||||
gameSize: VersionResolver.pickStr(layers, (n: VersionFields) => n.game_size),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user