feat(framework): net NetClient 可选 debug 收发包日志

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 18:16:36 +08:00
co-authored by Claude Opus 4.8
parent 2c2e487d8f
commit 5ab4a0a4b6
2 changed files with 53 additions and 2 deletions
@@ -23,6 +23,8 @@ export interface NetClientOptions {
transportFactory: () => Transport;
clock?: Clock;
bus?: EventBus<NetClientEvents>;
debug?: boolean; // 为真时打印每帧 send/recv
logger?: (...args: unknown[]) => void; // 日志函数,默认 console.log(便于测试注入)
}
/** 网络客户端:编排 transport/codec/heartbeat/reconnect,事件驱动,忠实 docs/protocol/01。 */
@@ -41,6 +43,8 @@ export class NetClient {
private loginGuard: unknown = null;
private reconnectTimer: unknown = null;
private stopped = false;
private debug: boolean;
private logger: (...args: unknown[]) => void;
constructor(opts: NetClientOptions) {
this.bus = opts.bus ?? new EventBus<NetClientEvents>();
@@ -48,6 +52,8 @@ export class NetClient {
this.clock = opts.clock ?? realClock;
this.policy = new ReconnectPolicy(opts.servers, SERVER_ROTATE_EVERY);
this.watchdog = new HeartbeatWatchdog(RECV_TIMEOUT_MS, () => this.onRecvTimeout(), this.clock);
this.debug = opts.debug ?? false;
this.logger = opts.logger ?? console.log;
}
setIdentity(identity: LoginRequestData): void { this.identity = identity; }
@@ -66,7 +72,13 @@ export class NetClient {
/** 发业务包(单层信封)。 */
send(route: string, rpc: string, data: unknown): void {
this.transport?.send(encodeOutbound(route, rpc, data));
this.sendFrame(encodeOutbound(route, rpc, data));
}
/** 统一发送切面:debug 时打印出站帧。 */
private sendFrame(frame: string): void {
if (this.debug) this.logger('[net] →', frame);
this.transport?.send(frame);
}
private connect(url: string): void {
@@ -87,12 +99,13 @@ export class NetClient {
private sendLogin(): void {
if (!this.identity) return; // 无身份不发(docs §6.1:首登需身份)
this.isSendLoginState = true;
this.transport?.send(encodeOutbound(Route.agent, 'player_login', this.identity));
this.sendFrame(encodeOutbound(Route.agent, 'player_login', this.identity));
if (this.loginGuard != null) this.clock.clearTimeout(this.loginGuard);
this.loginGuard = this.clock.setTimeout(() => { this.loginGuard = null; this.onLoginGuardTimeout(); }, LOGIN_GUARD_MS); // docs §6.2
}
private onMessage(frame: string): void {
if (this.debug) this.logger('[net] ←', frame);
const r = decodeFrame(frame);
if (r.kind === 'ignore') return;
this.watchdog.feed(); // 收任意有效帧重置看门狗 docs §4.1
@@ -123,3 +123,41 @@ test('TcpID 去重:旧连接的残留包被丢弃', async () => {
await t1.flush();
assert.equal(messages.length, 0);
});
test('debug=true 记录 send 与 recv(注入 logger)', async () => {
const t = new FakeTransport();
const { clock } = fakeClock();
const logs: unknown[][] = [];
const client = new NetClient({
servers: 'ws://a',
transportFactory: () => t,
clock,
debug: true,
logger: (...a: unknown[]) => logs.push(a),
});
client.setIdentity(IDENTITY);
client.start();
await t.flush();
assert.ok(logs.some((l) => l[0] === '[net] →')); // 发出 player_login 被记录
t.serverPush(JSON.stringify({ data: JSON.stringify({ route: 'agent', rpc: 'player_login', data: { state: 0, playerid: 9 } }) }));
await t.flush();
assert.ok(logs.some((l) => l[0] === '[net] ←')); // 收包被记录
});
test('debug 默认关闭:logger 不被调用', async () => {
const t = new FakeTransport();
const { clock } = fakeClock();
const logs: unknown[][] = [];
const client = new NetClient({
servers: 'ws://a',
transportFactory: () => t,
clock,
logger: (...a: unknown[]) => logs.push(a),
});
client.setIdentity(IDENTITY);
client.start();
await t.flush();
t.serverPush(JSON.stringify({ data: JSON.stringify({ route: 'agent', rpc: 'player_login', data: { state: 0, playerid: 9 } }) }));
await t.flush();
assert.equal(logs.length, 0);
});