- CLAUDE.md 新增「第二准则:数据源权威、唯一,下游不兜底」 - bootstrap 删除 fallbackServers 选项与 ?? DEFAULT_GAMESERVER 猜默认 - 无 server/gameserver、远程失败、解析不出地址 → 一律抛 ConfigFetchError 显式暴露 - 同步 guide + 两篇 spec(删 fallback/降级措辞) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.4 KiB
8.4 KiB
debug/release 一键运行模式设计(framework/config runtime-mode)
状态:已与用户确认(brainstorming 通过),待转 writing-plans。 适用工程:
cocoscreator_projects/YouleNexus。 关联:配置/渠道子系统 spec2026-06-28-config-channel-design.md(本设计在其resolveBootstrap上加运行模式)、docs/guides/配置与调试指南.md(需同步更新)。
0. 目标与背景
当前「调试 ↔ 正式」只能靠改 ACTIVE_PROFILE 常量或 URL ?profile= 切换,且:
DebugProfile.isDebugger字段无人消费(没有任何日志按它开关);- 没有 release 锁定——即使正式包,URL
?profile=/?agentid=仍生效,可被篡改切到调试服或换渠道身份。
本设计引入一个一键运行模式开关,让整套行为随 debug/release 切换:
- 自动跟随 Cocos 构建类型(debug/预览构建=debug,release 构建=release),并保留可选手动覆盖常量。
- release 完全锁死 URL 覆盖:强制走
prodprofile,忽略所有 URL 参数。 - release 安全前提:正式包为 Cocos 原生形态(jsb),渠道身份走
window.settings/app_*,不依赖 URL,故锁 URL 不影响身份传递。 - 让
isDebugger真正可观测:接到NetClient的收发包日志。
命名/红线
内部命名现代化(RuntimeMode/resolveRuntimeMode),不改任何外部契约字符串(协议字段、原生接口名、远程配置键)。详见 config-channel spec §5。
1. 开关机制(新增 config/runtime-mode.ts)
export type RuntimeMode = 'debug' | 'release';
/**
* 手动覆盖运行模式。
* null = 自动跟随构建类型(推荐,发布前保持 null)。
* 'debug' / 'release' = 强制该模式(用于在 release 包里临时调试,或在编辑器预览里验证正式行为)。
*/
export const MODE_OVERRIDE: RuntimeMode | null = null;
/**
* 纯函数解析运行模式:override 优先,否则由是否 debug 构建决定。
* isDebugBuild 由调用方读 Cocos 全局 DEBUG 传入(见 §4 接入)。
*/
export function resolveRuntimeMode(isDebugBuild: boolean, override: RuntimeMode | null = MODE_OVERRIDE): RuntimeMode {
if (override) return override;
return isDebugBuild ? 'debug' : 'release';
}
isDebugBuild来源:Cocos Creator 全局DEBUG(构建期宏,release 构建为 false)。接入处用typeof DEBUG !== 'undefined' ? DEBUG : true(编辑器/预览缺省视为 debug)。读全局这一薄层不写单测;纯函数resolveRuntimeMode全测。- 一键切换三种姿势:① 不动任何东西——发 release 包自动 release、预览自动 debug;② release 包里临时调试 →
MODE_OVERRIDE='debug';③ 预览里验证正式行为 →MODE_OVERRIDE='release'。
2. 模式如何重塑 resolveBootstrap
BootstrapOptions 新增必填 mode: RuntimeMode;BootstrapResult 新增 mode: RuntimeMode 与 isDebugger: boolean。
export interface BootstrapOptions {
mode: RuntimeMode; // 新增
win: NativeHost;
search: string;
fetcher: ConfigFetcher;
isNative: boolean;
cacheBust?: () => string;
}
export interface BootstrapResult {
mode: RuntimeMode; // 新增
isDebugger: boolean; // 新增 = (mode === 'debug')
identity: ChannelIdentity;
servers: string[];
rawConfig: RemoteConfig | null;
profile: DebugProfile;
}
行为分支:
- release:
profile = PROFILES.prod(强制,忽略opts.search的?profile=)。- 身份来源仅
[() => BUILD_IDENTITY, nativeSettingsSource(opts.win)]——不挂queryStringSource,不应用profile.identity调试覆盖。即任何 URL 参数无效。 - 服务器决策照旧(prod 无
server→ 走远程配置)。
- debug:维持现状。
profile = resolveActiveProfile(opts.search)。- 身份来源
[() => BUILD_IDENTITY, runtime(isNative?native:query), () => profile.identity ?? {}, queryStringSource(search)]。
isDebugger = (mode === 'debug'),并写入结果。(release 恒 false。DebugProfile.isDebugger字段保留为档位元数据,但权威开关改由 mode 决定。)
设计要点:release 分支根本不构造
queryStringSource,从源头杜绝 URL 覆盖,而不是构造后再过滤——更不易出错。
3. 让 debug 模式可观测:NetClient 收发包日志
当前 isDebugger 无人消费。本期接上,给 NetClient 增可选日志开关:
export interface NetClientOptions {
servers: string | string[];
transportFactory: () => Transport;
clock?: Clock;
bus?: EventBus<NetClientEvents>;
debug?: boolean; // 新增:为真时 console 打印每帧 send/recv
logger?: (...args: unknown[]) => void; // 新增(可选):注入日志函数,默认 console.log,便于测试断言
}
- 发送(
send/sendLogin)与接收(onMessage入口)时,若debug为真,调用logger(默认console.log)打印方向 + 帧内容(如[net] → <frame>/[net] ← <frame>)。 - 接入:
new NetClient({ servers, transportFactory, debug: result.isDebugger })。release 静默,debug 输出。 - 日志只读不改流程(剥离 UI,纯 console),不影响协议行为。
4. 接入示例(更新到指南)
import { resolveRuntimeMode } from 'db://assets/framework/config/runtime-mode';
import { resolveBootstrap } from 'db://assets/framework/config/bootstrap';
import { HttpConfigFetcher } from 'db://assets/framework/config/remote-config-fetcher';
import { NetClient } from 'db://assets/framework/net/net-client';
import { CocosWebSocketTransport } from 'db://assets/framework/net/cocos-transport';
const isDebugBuild = typeof DEBUG !== 'undefined' ? DEBUG : true; // Cocos 全局
const mode = resolveRuntimeMode(isDebugBuild); // 'debug' | 'release'
const result = await resolveBootstrap({
mode,
win: globalThis as any,
search: location.search,
fetcher: new HttpConfigFetcher(),
isNative: location.href.indexOf('index.html') > -1,
});
const net = new NetClient({
servers: result.servers,
transportFactory: () => new CocosWebSocketTransport(),
debug: result.isDebugger,
});
net.setIdentity({ ...result.identity, openid, nickname, /* ...玩家档案 */ });
net.start();
5. 测试策略(tsx + node:test)
framework-tests/config/runtime-mode.test.ts:- override 为 'debug'/'release' 时优先返回 override(无视 isDebugBuild)。
- override 为 null 时:isDebugBuild=true → 'debug';false → 'release'。
framework-tests/config/bootstrap.test.ts(扩充):- release:
mode:'release'+search:'?profile=local&agentid=999'→profile.name==='prod'、servers走远程(非 local 直连)、identity.agentid===BUILD_IDENTITY.agentid(未被 999 覆盖)、isDebugger===false。 - release 原生身份仍生效:
mode:'release',isNative:true, fakewin.settings→identity.agentid来自原生。 - debug:
mode:'debug'维持原有覆盖行为(现有用例补传mode:'debug')。 - 结果含
mode与isDebugger字段。
- release:
framework-tests/net/net-client.test.ts(扩充):debug:true+ 注入 fakelogger→ send 与 recv 各被记录一次(含方向标记)。debug:false(或不传)→ logger 不被调用。
验收:npm run test:framework 全绿、npm run typecheck:framework exit 0。
6. 影响面与迁移
resolveBootstrap新增必填mode——所有现有调用点(目前仅测试与未来启动场景)需补传。现有 bootstrap 测试用例补mode:'debug'保持原语义。NetClient新增可选debug/logger——默认关闭,不影响现有调用与测试。DebugProfile.isDebugger字段保留(档位元数据/可读性),但运行期权威开关改由mode决定;prod.isDebugger:false与 release 自洽。- 更新
docs/guides/配置与调试指南.md:新增「一键切换 debug/release」一节、resolveRuntimeMode接入、发布前清单(确认MODE_OVERRIDE===null)、API 速查表补项。
7. 范围边界(YAGNI)
- 做:运行模式解析(自动+手动覆盖)、release URL 锁定、
isDebugger接到 NetClient 收发包日志、文档更新。 - 不做:基于模式切换资源/分包;远程下发的
isdebugger参数(原工程get_paravalue('isdebugger'),属后续 platform 层);日志分级/上报(仅 console 收发包)。