fix: 对齐NapCat WebUI会话契约

This commit is contained in:
sunlei 2026-06-24 11:55:50 +08:00
parent 06eac5a01d
commit 156ba8147e
4 changed files with 43 additions and 28 deletions

View File

@ -151,10 +151,12 @@ export class QqbotNapcatWebuiGatewayService {
const target = this.toGatewayTarget(runtime); const target = this.toGatewayTarget(runtime);
const gatewaySession = await this.gatewayClient.createSession({ const gatewaySession = await this.gatewayClient.createSession({
accountId: account.id, accountId: account.id,
accountName: account.name, adminUserId: input.adminUserId,
clientIp: input.clientIp || undefined,
containerId: runtime.id, containerId: runtime.id,
containerName: runtime.name, containerName: runtime.name,
selfId: account.selfId, selfId: account.selfId,
userAgent: input.userAgent || undefined,
...target, ...target,
}); });
const webuiStatus = this.toWebuiStatus(runtime); const webuiStatus = this.toWebuiStatus(runtime);
@ -216,14 +218,14 @@ export class QqbotNapcatWebuiGatewayService {
/** /**
* Converts private NapCat runtime fields into the Gateway client payload. * Converts private NapCat runtime fields into the Gateway client payload.
* @param runtime - Primary NapCat runtime containing upstream URL, port, and WebUI token. * @param runtime - Primary NapCat runtime containing upstream URL, port, and WebUI token.
* @returns Internal-only Gateway target metadata. * @returns Internal-only Gateway target metadata without exposing the raw WebUI port separately.
*/ */
private toGatewayTarget(runtime: QqbotNapcatRuntime) { private toGatewayTarget(runtime: QqbotNapcatRuntime) {
const targetBaseUrl = String(runtime.baseUrl || '').trim(); const upstreamBaseUrl = String(runtime.baseUrl || '').trim();
const webuiToken = String(runtime.webuiToken || '').trim(); const webuiToken = String(runtime.webuiToken || '').trim();
const webuiPort = Number(runtime.webuiPort); const webuiPort = Number(runtime.webuiPort);
if (!targetBaseUrl || !webuiToken || !Number.isFinite(webuiPort)) { if (!upstreamBaseUrl || !webuiToken || !Number.isFinite(webuiPort)) {
throwVbenError('NapCat WebUI 配置不完整'); throwVbenError('NapCat WebUI 配置不完整');
} }
if (webuiPort <= 0) { if (webuiPort <= 0) {
@ -231,8 +233,7 @@ export class QqbotNapcatWebuiGatewayService {
} }
return { return {
targetBaseUrl, upstreamBaseUrl,
webuiPort,
webuiToken, webuiToken,
}; };
} }

View File

@ -38,8 +38,10 @@ export class QqbotNapcatWebuiSessionResponseDto {
@ApiProperty({ type: QqbotNapcatWebuiSessionContainerDto }) @ApiProperty({ type: QqbotNapcatWebuiSessionContainerDto })
container: QqbotNapcatWebuiSessionContainerDto; container: QqbotNapcatWebuiSessionContainerDto;
@ApiProperty({ description: 'Gateway session expiry time in ISO format.' }) @ApiProperty({
expiresAt: string; description: 'Gateway session expiry timestamp in milliseconds.',
})
expiresAt: number;
@ApiProperty({ description: 'Browser-safe iframe URL served by Gateway.' }) @ApiProperty({ description: 'Browser-safe iframe URL served by Gateway.' })
iframeUrl: string; iframeUrl: string;

View File

@ -8,17 +8,18 @@ const DEFAULT_GATEWAY_TIMEOUT_MS = 5000;
export type QqbotNapcatWebuiGatewayCreateSessionRequest = { export type QqbotNapcatWebuiGatewayCreateSessionRequest = {
accountId: string; accountId: string;
accountName: string; adminUserId: string;
clientIp?: string;
containerId: string; containerId: string;
containerName: string; containerName: string;
selfId: string; selfId: string;
targetBaseUrl: string; upstreamBaseUrl: string;
webuiPort: number; userAgent?: string;
webuiToken: string; webuiToken: string;
}; };
export type QqbotNapcatWebuiGatewaySessionResult = { export type QqbotNapcatWebuiGatewaySessionResult = {
expiresAt: string; expiresAt: number;
iframeUrl: string; iframeUrl: string;
sessionId: string; sessionId: string;
}; };

View File

@ -18,6 +18,7 @@ jest.mock('axios');
const repoRoot = resolve(__dirname, '../../../..'); const repoRoot = resolve(__dirname, '../../../..');
const requestMock = axios.request as jest.Mock; const requestMock = axios.request as jest.Mock;
const EXPIRES_AT_FIXTURE = 1782268000000;
const INTERNAL_SECRET_FIXTURE = ['internal', 'secret'].join('-'); const INTERNAL_SECRET_FIXTURE = ['internal', 'secret'].join('-');
const WEBUI_TOKEN_FIXTURE = ['webui', 'token', 'fixture'].join('-'); const WEBUI_TOKEN_FIXTURE = ['webui', 'token', 'fixture'].join('-');
@ -87,7 +88,7 @@ describe('QqbotNapcatWebuiGatewayService', () => {
webuiToken: WEBUI_TOKEN_FIXTURE, webuiToken: WEBUI_TOKEN_FIXTURE,
}; };
const gatewayResult = { const gatewayResult = {
expiresAt: '2026-06-24T10:00:00.000Z', expiresAt: EXPIRES_AT_FIXTURE,
iframeUrl: '/napcat-webui-gateway/session/sess_1/', iframeUrl: '/napcat-webui-gateway/session/sess_1/',
sessionId: 'sess_1', sessionId: 'sess_1',
}; };
@ -132,18 +133,19 @@ describe('QqbotNapcatWebuiGatewayService', () => {
name: 'kt-qqbot-napcat-1914728559', name: 'kt-qqbot-napcat-1914728559',
webuiStatus: 'online', webuiStatus: 'online',
}, },
expiresAt: '2026-06-24T10:00:00.000Z', expiresAt: EXPIRES_AT_FIXTURE,
iframeUrl: '/napcat-webui-gateway/session/sess_1/', iframeUrl: '/napcat-webui-gateway/session/sess_1/',
sessionId: 'sess_1', sessionId: 'sess_1',
}); });
expect(client.createSession).toHaveBeenCalledWith({ expect(client.createSession).toHaveBeenCalledWith({
accountId: '1001', accountId: '1001',
accountName: '主机器人', adminUserId: '3001',
clientIp: '127.0.0.1',
containerId: '2001', containerId: '2001',
containerName: 'kt-qqbot-napcat-1914728559', containerName: 'kt-qqbot-napcat-1914728559',
selfId: '1914728559', selfId: '1914728559',
targetBaseUrl: 'http://172.18.0.23:6099', upstreamBaseUrl: 'http://172.18.0.23:6099',
webuiPort: 6099, userAgent: 'jest-agent',
webuiToken: WEBUI_TOKEN_FIXTURE, webuiToken: WEBUI_TOKEN_FIXTURE,
}); });
expect(serialized).not.toContain(WEBUI_TOKEN_FIXTURE); expect(serialized).not.toContain(WEBUI_TOKEN_FIXTURE);
@ -258,7 +260,7 @@ describe('QqbotNapcatWebuiGatewayClient', () => {
requestMock.mockResolvedValue({ requestMock.mockResolvedValue({
data: { data: {
data: { data: {
expiresAt: '2026-06-24T10:00:00.000Z', expiresAt: EXPIRES_AT_FIXTURE,
iframeUrl: '/napcat-webui-gateway/session/sess_1/', iframeUrl: '/napcat-webui-gateway/session/sess_1/',
sessionId: 'sess_1', sessionId: 'sess_1',
}, },
@ -277,20 +279,28 @@ describe('QqbotNapcatWebuiGatewayClient', () => {
const result = await client.createSession({ const result = await client.createSession({
accountId: '1001', accountId: '1001',
accountName: '主机器人', adminUserId: '3001',
clientIp: '127.0.0.1',
containerId: '2001', containerId: '2001',
containerName: 'kt-qqbot-napcat-1914728559', containerName: 'kt-qqbot-napcat-1914728559',
selfId: '1914728559', selfId: '1914728559',
targetBaseUrl: 'http://172.18.0.23:6099', upstreamBaseUrl: 'http://172.18.0.23:6099',
webuiPort: 6099, userAgent: 'jest-agent',
webuiToken: WEBUI_TOKEN_FIXTURE, webuiToken: WEBUI_TOKEN_FIXTURE,
}); });
expect(requestMock).toHaveBeenCalledWith({ expect(requestMock).toHaveBeenCalledWith({
data: expect.objectContaining({ data: {
targetBaseUrl: 'http://172.18.0.23:6099', accountId: '1001',
adminUserId: '3001',
clientIp: '127.0.0.1',
containerId: '2001',
containerName: 'kt-qqbot-napcat-1914728559',
selfId: '1914728559',
upstreamBaseUrl: 'http://172.18.0.23:6099',
userAgent: 'jest-agent',
webuiToken: WEBUI_TOKEN_FIXTURE, webuiToken: WEBUI_TOKEN_FIXTURE,
}), },
headers: { headers: {
'x-kt-gateway-secret': INTERNAL_SECRET_FIXTURE, 'x-kt-gateway-secret': INTERNAL_SECRET_FIXTURE,
}, },
@ -299,7 +309,7 @@ describe('QqbotNapcatWebuiGatewayClient', () => {
url: 'http://127.0.0.1:48086/internal/sessions', url: 'http://127.0.0.1:48086/internal/sessions',
}); });
expect(result).toEqual({ expect(result).toEqual({
expiresAt: '2026-06-24T10:00:00.000Z', expiresAt: EXPIRES_AT_FIXTURE,
iframeUrl: '/napcat-webui-gateway/session/sess_1/', iframeUrl: '/napcat-webui-gateway/session/sess_1/',
sessionId: 'sess_1', sessionId: 'sess_1',
}); });
@ -326,12 +336,13 @@ describe('QqbotNapcatWebuiGatewayClient', () => {
try { try {
await client.createSession({ await client.createSession({
accountId: '1001', accountId: '1001',
accountName: '主机器人', adminUserId: '3001',
clientIp: '127.0.0.1',
containerId: '2001', containerId: '2001',
containerName: 'kt-qqbot-napcat-1914728559', containerName: 'kt-qqbot-napcat-1914728559',
selfId: '1914728559', selfId: '1914728559',
targetBaseUrl: 'http://172.18.0.23:6099', upstreamBaseUrl: 'http://172.18.0.23:6099',
webuiPort: 6099, userAgent: 'jest-agent',
webuiToken: WEBUI_TOKEN_FIXTURE, webuiToken: WEBUI_TOKEN_FIXTURE,
}); });
} catch (error) { } catch (error) {