fix:修复验证码验证时TTL被重置的问题

- 修复验证失败时TTL被重置为5分钟的bug
- 保持原有的过期时间,不重置验证码有效期
- 增加详细的TTL变化日志记录
- 改进错误处理和边界情况处理
- 解决用户验证一次错误后验证码立即过期的问题
This commit is contained in:
moyin
2025-12-17 21:23:16 +08:00
parent 66f268cf17
commit de3b108503

View File

@@ -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<any> {
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;
}
}