From 749df7cd7ef9b789ced34209d667864eadceea8b Mon Sep 17 00:00:00 2001 From: sunlei Date: Tue, 16 Jun 2026 19:24:01 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DNapCat=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E7=A0=81=E4=BC=9A=E8=AF=9D=E6=8C=81=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/qqbot-init.sql | 4 +-- sql/refactor-v3/00-full-schema.sql | 4 +-- sql/refactor-v3/99-verify.sql | 14 +++++++++ .../napcat-login-challenge.entity.ts | 2 +- .../napcat-login-state-store.service.ts | 27 +++++++++++++--- .../napcat-runtime-cleanup.entity.ts | 2 +- .../napcat-persistent-login-state.spec.ts | 31 +++++++++++++++++++ 7 files changed, 74 insertions(+), 10 deletions(-) diff --git a/sql/qqbot-init.sql b/sql/qqbot-init.sql index 76b5fbb..8e62ee5 100644 --- a/sql/qqbot-init.sql +++ b/sql/qqbot-init.sql @@ -117,7 +117,7 @@ CREATE TABLE IF NOT EXISTS `napcat_login_session` ( CREATE TABLE IF NOT EXISTS `napcat_login_challenge` ( `id` bigint NOT NULL, - `session_id` bigint NOT NULL, + `session_id` varchar(64) NOT NULL, `challenge_type` varchar(64) NOT NULL, `status` varchar(32) NOT NULL, `challenge_url` text, @@ -131,7 +131,7 @@ CREATE TABLE IF NOT EXISTS `napcat_login_challenge` ( CREATE TABLE IF NOT EXISTS `napcat_runtime_cleanup` ( `id` bigint NOT NULL, - `session_id` bigint NOT NULL, + `session_id` varchar(64) NOT NULL, `cleanup_type` varchar(64) NOT NULL, `status` varchar(32) NOT NULL, `error_message` text, diff --git a/sql/refactor-v3/00-full-schema.sql b/sql/refactor-v3/00-full-schema.sql index 8e341cf..2d7474b 100644 --- a/sql/refactor-v3/00-full-schema.sql +++ b/sql/refactor-v3/00-full-schema.sql @@ -948,7 +948,7 @@ CREATE TABLE IF NOT EXISTS napcat_login_session ( CREATE TABLE IF NOT EXISTS napcat_login_challenge ( id BIGINT NOT NULL PRIMARY KEY, - session_id BIGINT NOT NULL, + session_id VARCHAR(64) NOT NULL, challenge_type VARCHAR(64) NOT NULL, status VARCHAR(32) NOT NULL, challenge_url TEXT NULL, @@ -961,7 +961,7 @@ CREATE TABLE IF NOT EXISTS napcat_login_challenge ( CREATE TABLE IF NOT EXISTS napcat_runtime_cleanup ( id BIGINT NOT NULL PRIMARY KEY, - session_id BIGINT NOT NULL, + session_id VARCHAR(64) NOT NULL, cleanup_type VARCHAR(64) NOT NULL, status VARCHAR(32) NOT NULL, error_message TEXT NULL, diff --git a/sql/refactor-v3/99-verify.sql b/sql/refactor-v3/99-verify.sql index 18be121..4db9d28 100644 --- a/sql/refactor-v3/99-verify.sql +++ b/sql/refactor-v3/99-verify.sql @@ -108,3 +108,17 @@ FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'napcat_runtime_cleanup' AND index_name = 'idx_napcat_runtime_cleanup_session'; + +SELECT 'column_napcat_login_challenge_session_id_varchar' AS check_name, COUNT(*) AS matched_rows +FROM information_schema.columns +WHERE table_schema = DATABASE() + AND table_name = 'napcat_login_challenge' + AND column_name = 'session_id' + AND column_type = 'varchar(64)'; + +SELECT 'column_napcat_runtime_cleanup_session_id_varchar' AS check_name, COUNT(*) AS matched_rows +FROM information_schema.columns +WHERE table_schema = DATABASE() + AND table_name = 'napcat_runtime_cleanup' + AND column_name = 'session_id' + AND column_type = 'varchar(64)'; diff --git a/src/modules/qqbot/napcat/infrastructure/persistence/napcat-login-challenge.entity.ts b/src/modules/qqbot/napcat/infrastructure/persistence/napcat-login-challenge.entity.ts index e767ff3..8178626 100644 --- a/src/modules/qqbot/napcat/infrastructure/persistence/napcat-login-challenge.entity.ts +++ b/src/modules/qqbot/napcat/infrastructure/persistence/napcat-login-challenge.entity.ts @@ -15,7 +15,7 @@ export class NapcatLoginChallengeEntity { @PrimaryColumn({ type: 'bigint' }) id: string; - @Column({ name: 'session_id', type: 'bigint' }) + @Column({ length: 64, name: 'session_id', type: 'varchar' }) sessionId: string; @Column({ length: 64, name: 'challenge_type' }) diff --git a/src/modules/qqbot/napcat/infrastructure/persistence/napcat-login-state-store.service.ts b/src/modules/qqbot/napcat/infrastructure/persistence/napcat-login-state-store.service.ts index 4df7ff3..634af71 100644 --- a/src/modules/qqbot/napcat/infrastructure/persistence/napcat-login-state-store.service.ts +++ b/src/modules/qqbot/napcat/infrastructure/persistence/napcat-login-state-store.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Optional } from '@nestjs/common'; +import { Injectable, Logger, Optional } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import type { QqbotLoginScanSession } from '@/modules/qqbot/core/contract/qqbot.types'; @@ -16,6 +16,7 @@ type NapcatLoginStoreCache = Record; @Injectable() export class NapcatLoginStateStoreService { + private readonly logger = new Logger(NapcatLoginStateStoreService.name); private readonly cache: NapcatLoginStoreCache = {}; private readonly pendingSessionWrites: Record | undefined> = {}; @@ -91,7 +92,9 @@ export class NapcatLoginStateStoreService { challengeUrl: session.captchaUrl, session, status: 'pending', - }); + }).catch((err) => + this.warnPersistenceError('登录验证码 challenge 持久化失败', err), + ); } recordNewDeviceChallenge(session: QqbotLoginScanSession) { @@ -106,7 +109,9 @@ export class NapcatLoginStateStoreService { challengeUrl: session.deviceVerifyUrl || session.newDeviceQrcode || null, session, status: session.newDeviceStatus, - }); + }).catch((err) => + this.warnPersistenceError('新设备 challenge 持久化失败', err), + ); } recordRuntimeCleanup( @@ -124,7 +129,11 @@ export class NapcatLoginStateStoreService { sessionId: session.id, status: input.status, }); - void this.runtimeCleanupRepository.save(cleanup); + void this.runtimeCleanupRepository + .save(cleanup) + .catch((err) => + this.warnPersistenceError('运行态清理记录持久化失败', err), + ); } async flushSessionWrites(sessionId?: string) { @@ -295,6 +304,16 @@ export class NapcatLoginStateStoreService { await this.loginChallengeRepository.save(entity); } + private warnPersistenceError(message: string, err: unknown) { + const detail = + err instanceof Error + ? err.message + : typeof err === 'string' + ? err + : JSON.stringify(err); + this.logger.warn(`${message}: ${detail || 'unknown error'}`); + } + private isResolvedChallenge(status: string) { return ['failed', 'expired', 'verified'].includes(status); } diff --git a/src/modules/qqbot/napcat/infrastructure/persistence/napcat-runtime-cleanup.entity.ts b/src/modules/qqbot/napcat/infrastructure/persistence/napcat-runtime-cleanup.entity.ts index 33a981d..da27767 100644 --- a/src/modules/qqbot/napcat/infrastructure/persistence/napcat-runtime-cleanup.entity.ts +++ b/src/modules/qqbot/napcat/infrastructure/persistence/napcat-runtime-cleanup.entity.ts @@ -14,7 +14,7 @@ export class NapcatRuntimeCleanup { @PrimaryColumn({ type: 'bigint' }) id: string; - @Column({ name: 'session_id', type: 'bigint' }) + @Column({ length: 64, name: 'session_id', type: 'varchar' }) sessionId: string; @Column({ length: 64, name: 'cleanup_type' }) diff --git a/test/modules/qqbot/napcat/napcat-persistent-login-state.spec.ts b/test/modules/qqbot/napcat/napcat-persistent-login-state.spec.ts index 0c7343b..65313c3 100644 --- a/test/modules/qqbot/napcat/napcat-persistent-login-state.spec.ts +++ b/test/modules/qqbot/napcat/napcat-persistent-login-state.spec.ts @@ -30,6 +30,18 @@ const getEntityColumnNames = (entity: EntityClass) => { .map((column) => `${column.options.name || column.propertyName}`); }; +const getEntityColumnOptions = (entity: EntityClass, propertyName: string) => { + return getMetadataArgsStorage().columns.find( + (column) => column.target === entity && column.propertyName === propertyName, + )?.options; +}; + +const getSchemaColumnDefinition = (tableName: string, columnName: string) => { + return schema + .getTableColumns(tableName) + .find((column) => column.name === columnName)?.definition; +}; + const createRepository = >() => { const rows: T[] = []; return { @@ -126,6 +138,25 @@ describe('NapCat persistent login state contract', () => { } }); + it('stores challenge and cleanup session ids as uuid-safe text', () => { + expect( + getEntityColumnOptions(NapcatLoginChallengeEntity, 'sessionId'), + ).toMatchObject({ length: 64, type: 'varchar' }); + expect(getEntityColumnOptions(NapcatRuntimeCleanup, 'sessionId')).toMatchObject( + { length: 64, type: 'varchar' }, + ); + + expect( + getSchemaColumnDefinition('napcat_login_challenge', 'session_id'), + ).toMatch(/^session_id VARCHAR\(64\) NOT NULL/i); + expect( + getSchemaColumnDefinition('napcat_runtime_cleanup', 'session_id'), + ).toMatch(/^session_id VARCHAR\(64\) NOT NULL/i); + + const qqbotInitSql = readSource('sql/qqbot-init.sql'); + expect(qqbotInitSql).toContain('`session_id` varchar(64) NOT NULL'); + }); + it('keeps NapCat runtime tables covered by the v3 verification SQL', () => { const verifySql = readSource('sql/refactor-v3/99-verify.sql'); const requiredSignals = [