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 gatewaySession = await this.gatewayClient.createSession({
accountId: account.id,
accountName: account.name,
adminUserId: input.adminUserId,
clientIp: input.clientIp || undefined,
containerId: runtime.id,
containerName: runtime.name,
selfId: account.selfId,
userAgent: input.userAgent || undefined,
...target,
});
const webuiStatus = this.toWebuiStatus(runtime);
@ -216,14 +218,14 @@ export class QqbotNapcatWebuiGatewayService {
/**
* Converts private NapCat runtime fields into the Gateway client payload.
* @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) {
const targetBaseUrl = String(runtime.baseUrl || '').trim();
const upstreamBaseUrl = String(runtime.baseUrl || '').trim();
const webuiToken = String(runtime.webuiToken || '').trim();
const webuiPort = Number(runtime.webuiPort);
if (!targetBaseUrl || !webuiToken || !Number.isFinite(webuiPort)) {
if (!upstreamBaseUrl || !webuiToken || !Number.isFinite(webuiPort)) {
throwVbenError('NapCat WebUI 配置不完整');
}
if (webuiPort <= 0) {
@ -231,8 +233,7 @@ export class QqbotNapcatWebuiGatewayService {
}
return {
targetBaseUrl,
webuiPort,
upstreamBaseUrl,
webuiToken,
};
}

View File

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

View File

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

View File

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