From de3b108503c8a4bf070a69b9957fea4e12fdd640 Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Wed, 17 Dec 2025 21:23:16 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E7=A0=81=E9=AA=8C=E8=AF=81=E6=97=B6TTL=E8=A2=AB?= =?UTF-8?q?=E9=87=8D=E7=BD=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复验证失败时TTL被重置为5分钟的bug - 保持原有的过期时间,不重置验证码有效期 - 增加详细的TTL变化日志记录 - 改进错误处理和边界情况处理 - 解决用户验证一次错误后验证码立即过期的问题 --- .../verification/verification.service.ts | 84 +++++++++++++++++-- 1 file changed, 78 insertions(+), 6 deletions(-) diff --git a/src/core/utils/verification/verification.service.ts b/src/core/utils/verification/verification.service.ts index b9f0f12..51207ab 100644 --- a/src/core/utils/verification/verification.service.ts +++ b/src/core/utils/verification/verification.service.ts @@ -111,24 +111,50 @@ export class VerificationService { const codeInfoStr = await this.redis.get(key); if (!codeInfoStr) { + this.logger.warn(`验证码不存在或已过期: ${identifier} (${type})`); throw new BadRequestException('验证码不存在或已过期'); } - const codeInfo: VerificationCodeInfo = JSON.parse(codeInfoStr); + let codeInfo: VerificationCodeInfo; + try { + codeInfo = JSON.parse(codeInfoStr); + } catch (error) { + this.logger.error(`验证码数据解析失败: ${identifier} (${type})`, error); + await this.redis.del(key); + throw new BadRequestException('验证码数据异常,请重新获取'); + } // 检查尝试次数 if (codeInfo.attempts >= codeInfo.maxAttempts) { + this.logger.warn(`验证码尝试次数已达上限: ${identifier} (${type}), 尝试次数: ${codeInfo.attempts}`); await this.redis.del(key); throw new BadRequestException('验证码尝试次数过多,请重新获取'); } - // 增加尝试次数 - codeInfo.attempts++; - await this.redis.set(key, JSON.stringify(codeInfo), this.CODE_EXPIRE_TIME); + // 获取当前TTL + const currentTTL = await this.redis.ttl(key); + this.logger.debug(`验证码当前TTL: ${identifier} (${type}), TTL: ${currentTTL}秒`); // 验证验证码 if (codeInfo.code !== inputCode) { - this.logger.warn(`验证码验证失败: ${identifier} (${type}) - 剩余尝试次数: ${codeInfo.maxAttempts - codeInfo.attempts}`); + // 增加尝试次数 + codeInfo.attempts++; + + // 保持原有的TTL,不重置过期时间 + if (currentTTL > 0) { + // 使用剩余的TTL时间 + await this.redis.set(key, JSON.stringify(codeInfo), currentTTL); + this.logger.warn(`验证码验证失败: ${identifier} (${type}) - 剩余尝试次数: ${codeInfo.maxAttempts - codeInfo.attempts}, 剩余时间: ${currentTTL}秒`); + } else if (currentTTL === -1) { + // 永不过期的情况,保持永不过期 + await this.redis.set(key, JSON.stringify(codeInfo)); + this.logger.warn(`验证码验证失败: ${identifier} (${type}) - 剩余尝试次数: ${codeInfo.maxAttempts - codeInfo.attempts}, 永不过期`); + } else { + // TTL为-2表示键不存在,这种情况理论上不应该发生 + this.logger.error(`验证码TTL异常: ${identifier} (${type}), TTL: ${currentTTL}`); + throw new BadRequestException('验证码状态异常,请重新获取'); + } + throw new BadRequestException(`验证码错误,剩余尝试次数: ${codeInfo.maxAttempts - codeInfo.attempts}`); } @@ -288,13 +314,16 @@ export class VerificationService { ttl: number; attempts?: number; maxAttempts?: number; + code?: string; + createdAt?: number; }> { const key = this.buildRedisKey(identifier, type); const exists = await this.redis.exists(key); const ttl = await this.redis.ttl(key); if (!exists) { - return { exists: false, ttl: -1 }; + this.logger.debug(`验证码不存在: ${identifier} (${type})`); + return { exists: false, ttl: -2 }; } const codeInfoStr = await this.redis.get(key); @@ -307,11 +336,54 @@ export class VerificationService { codeInfo = {} as VerificationCodeInfo; } + this.logger.debug(`验证码统计: ${identifier} (${type}), TTL: ${ttl}, 尝试次数: ${codeInfo.attempts}/${codeInfo.maxAttempts}`); + return { exists: true, ttl, attempts: codeInfo.attempts, maxAttempts: codeInfo.maxAttempts, + code: codeInfo.code, // 仅用于调试,生产环境应该移除 + createdAt: codeInfo.createdAt, }; } + + /** + * 调试方法:获取验证码详细信息 + * 仅用于开发和调试,生产环境应该移除或限制访问 + * + * @param identifier 标识符 + * @param type 验证码类型 + * @returns 详细信息 + */ + async debugCodeInfo(identifier: string, type: VerificationCodeType): Promise { + const key = this.buildRedisKey(identifier, type); + + const [exists, ttl, rawData] = await Promise.all([ + this.redis.exists(key), + this.redis.ttl(key), + this.redis.get(key) + ]); + + const result = { + key, + exists, + ttl, + rawData, + parsedData: null as any, + currentTime: Date.now(), + timeFormatted: new Date().toISOString() + }; + + if (rawData) { + try { + result.parsedData = JSON.parse(rawData); + } catch (error) { + result.parsedData = { error: 'JSON解析失败', raw: rawData }; + } + } + + this.logger.debug(`调试验证码信息: ${JSON.stringify(result, null, 2)}`); + return result; + } } \ No newline at end of file