添加 captcha服务useRedis选项

This commit is contained in:
tone
2024-09-26 13:44:06 +08:00
parent a644122205
commit c2e961ef6f
4 changed files with 123 additions and 24 deletions

View File

@@ -4,31 +4,82 @@
* @description 旋转图像验证服务
*/
import Logger from '@lib/Logger/Logger';
import RedisConnection from '@lib/Database/RedisConnection';
import type { Redis } from '@lib/Database/RedisConnection';
import config from 'src/config';
type CaptchaSessionDataJSON = {
rotateDeg: number,
tryCount: number,
isPassed: boolean
interface CaptchaSessionRedisDataJSON {
rotateDeg: number;
tryCount: number;
isPassed: boolean;
expiredTimestamp?: number;
}
interface CaptchaSessionPoolDataJSON extends CaptchaSessionRedisDataJSON {
expiredTimestamp: number;
}
type CaptchaPool = {
[key: string]: CaptchaSessionPoolDataJSON;
}
class _CaptchaSession {
private readonly useRedis: boolean = config.service.captchaSession.useRedis;
private readonly logger = new Logger('Service', 'captchaSession');
private readonly AllowMaxTryCount: number = config.service.captchaSession.allowMaxTryCount;
private readonly AllowMaxAngleDiff: number = config.service.captchaSession.allowMaxAngleDiff;
private readonly ExpriedTimeSec: number = config.service.captchaSession.expriedTimeSec;
private readonly RedisCommonKey: string = 'Service:captchaSession:';
private redisConnection?: Redis;
private captchaPool?: CaptchaPool;
constructor() {
this.logger.info('Rotation image verification service has started');
new Promise(async (resolve) => {
if (this.useRedis) {
this.redisConnection = (await import('@lib/Database/RedisConnection')).default;
this.logger.info('Redis service has been enabled');
} else {
this.captchaPool = {};
this.logger.info('CaptchaPool init, Redis service is disabled');
// 需要手动进行过期项的处理
setInterval(() => {
this.logger.info('Starting automatic cleanup of expired items');
this.cleanupExpiredItems();
this.logger.info('Automatic cleanup completed');
}, 1000 * 60 * 30);
}
this.logger.info('Rotation image verification service has started');
resolve(undefined);
})
}
private async get(session: string): Promise<CaptchaSessionDataJSON | undefined> {
private cleanupExpiredItems(): void {
const now = Date.now();
for (const session in this.captchaPool) {
if (this.captchaPool.hasOwnProperty(session)) {
const item = this.captchaPool[session];
if (item && (now > item.expiredTimestamp)) {
delete this.captchaPool[session];
}
}
}
}
// get需要实现过期时间管理
private async get(session: string): Promise<CaptchaSessionRedisDataJSON | undefined> {
try {
const result = await RedisConnection.get(this.RedisCommonKey + session);
if (result === null)
if (this.useRedis) {
let redisRes = await this.redisConnection!.get(this.RedisCommonKey + session);
if (!redisRes)
return;
return JSON.parse(redisRes);
}
let poolRes = this.captchaPool![session];
if (!poolRes)
return;
return JSON.parse(result);
// 如果不来自Redis则需要处理是否过期
if (!this.useRedis && Date.now() > poolRes.expiredTimestamp)
return;
return poolRes;
} catch (error) {
this.logger.error(`Error occurred while retrieving session [${session}]: ${error}`);
return;
@@ -37,7 +88,10 @@ class _CaptchaSession {
private async remove(session: string): Promise<void> {
try {
await RedisConnection.del(this.RedisCommonKey + session);
if (this.useRedis)
await this.redisConnection!.del(this.RedisCommonKey + session);
else
delete this.captchaPool![session];
} catch (error) {
this.logger.error(`Failed to delete session [${session}]`);
}
@@ -50,20 +104,29 @@ class _CaptchaSession {
* @returns true存储成功 false存储失败
*/
public async add(session: string, rotateDeg: number): Promise<boolean> {
const result: CaptchaSessionDataJSON = {
const result: CaptchaSessionRedisDataJSON = {
rotateDeg: rotateDeg,
tryCount: 0,
isPassed: false
isPassed: false,
}
try {
const res = await RedisConnection.set(this.RedisCommonKey + session, JSON.stringify(result));
if (res && res === 'OK') {
RedisConnection.expire(this.RedisCommonKey + session, this.ExpriedTimeSec);
this.logger.info(`Session [${session}] and rotation angle [${rotateDeg}] have been stored`);
return true;
if (this.useRedis) {
const res = await this.redisConnection!.set(this.RedisCommonKey + session, JSON.stringify(result));
if (res && res === 'OK') {
this.redisConnection!.expire(this.RedisCommonKey + session, this.ExpriedTimeSec);
this.logger.info(`Session [${session}] and rotation angle [${rotateDeg}] have been stored`);
return true;
}
this.logger.error(`Failed to store session [${session}] and rotation angle [${rotateDeg}]`);
return false;
}
this.logger.error(`Failed to store session [${session}] and rotation angle [${rotateDeg}]`);
return false;
this.captchaPool![session] = {
...result,
expiredTimestamp: Date.now() + this.ExpriedTimeSec * 1000
}
this.logger.info(`Session [${session}] and rotation angle [${rotateDeg}] have been stored`);
return true;
} catch (error) {
this.logger.error(`Failed to store session [${session}] and rotation angle [${rotateDeg}]: ${error}`);
return false;
@@ -102,17 +165,25 @@ class _CaptchaSession {
}
if (Math.abs(result.rotateDeg - rotateDeg) <= this.AllowMaxAngleDiff) {
result.isPassed = true;
await RedisConnection.del(this.RedisCommonKey + session);
await RedisConnection.set(this.RedisCommonKey + session, JSON.stringify(result));
RedisConnection.expire(this.RedisCommonKey + session, this.ExpriedTimeSec);
if (this.useRedis) {
await this.redisConnection!.del(this.RedisCommonKey + session);
await this.redisConnection!.set(this.RedisCommonKey + session, JSON.stringify(result));
this.redisConnection!.expire(this.RedisCommonKey + session, this.ExpriedTimeSec);
} else {
result.expiredTimestamp = Date.now() + this.ExpriedTimeSec * 1000;
}
return 1;
}
// 角度偏差过大,尝试次数记录+1
result.tryCount++;
if (result.tryCount >= this.AllowMaxTryCount) {
// 已达最大尝试次数
this.remove(session);
return -1;
}
RedisConnection.set(this.RedisCommonKey + session, JSON.stringify(result));
// 允许下一次尝试
if (this.useRedis)
this.redisConnection!.set(this.RedisCommonKey + session, JSON.stringify(result));
return -2;
} catch (error) {
this.logger.error(`Error occurred while checking session [${session}]: ${error}`);