fix: 修复NapCat验证码会话持久化
This commit is contained in:
parent
041052900a
commit
749df7cd7e
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)';
|
||||
|
||||
@ -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' })
|
||||
|
||||
@ -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<string, QqbotLoginScanSession>;
|
||||
|
||||
@Injectable()
|
||||
export class NapcatLoginStateStoreService {
|
||||
private readonly logger = new Logger(NapcatLoginStateStoreService.name);
|
||||
private readonly cache: NapcatLoginStoreCache = {};
|
||||
private readonly pendingSessionWrites: Record<string, Promise<void> | 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);
|
||||
}
|
||||
|
||||
@ -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' })
|
||||
|
||||
@ -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 = <T extends Record<string, any>>() => {
|
||||
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 = [
|
||||
|
||||
Loading…
Reference in New Issue
Block a user