diff --git a/src/lib/Service/CaptchaSession.ts b/src/lib/Service/CaptchaSession.ts new file mode 100644 index 0000000..9c416fd --- /dev/null +++ b/src/lib/Service/CaptchaSession.ts @@ -0,0 +1,125 @@ +/** + * @file CaptchaSession.ts + * @version 1.0.0 + * @description 旋转图像验证服务 + */ +import Logger from '@lib/Logger/Logger'; +import RedisConnection from '@lib/Database/RedisConnection'; +import config from 'src/config'; +type CaptchaSessionDataJSON = { + rotateDeg: number, + tryCount: number, + isPassed: boolean +} + +class _CaptchaSession { + 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:'; + + constructor() { + this.logger.info('Rotation image verification service has started'); + } + + private async get(session: string): Promise { + try { + const result = await RedisConnection.get(this.RedisCommonKey + session); + if (result === null) + return; + return JSON.parse(result); + } catch (error) { + this.logger.error(`Error occurred while retrieving session [${session}]: ${error}`); + return; + } + } + + private async remove(session: string): Promise { + try { + await RedisConnection.del(this.RedisCommonKey + session); + } catch (error) { + this.logger.error(`Failed to delete session [${session}]`); + } + } + + /** + * + * @param session 验证会话标识符 + * @param rotateDeg 图片旋转角度 + * @returns true存储成功 false存储失败 + */ + public async add(session: string, rotateDeg: number): Promise { + const result: CaptchaSessionDataJSON = { + rotateDeg: rotateDeg, + tryCount: 0, + 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; + } + this.logger.error(`Failed to store session [${session}] and rotation angle [${rotateDeg}]`); + return false; + } catch (error) { + this.logger.error(`Failed to store session [${session}] and rotation angle [${rotateDeg}]: ${error}`); + return false; + } + } + + /** + * + * @param session 验证会话标识符 + * @returns true已验证过期 false未通过 + */ + public async isPassed(session: string): Promise { + const result = await this.get(session); + if (!result) + return false; + if (result.isPassed) + this.remove(session); + return result.isPassed; + } + + /** + * + * @param session 验证会话标识符 + * @param rotateDeg 图片旋转角度 + * @returns 0验证已过期或服务器错误 1通过验证 -1超过最大允许尝试次数 -2角度差异过大 + */ + public async check(session: string, rotateDeg: number): Promise { + try { + let result = await this.get(session); + if (!result) { + return 0; + } + if (result.isPassed) { + this.logger.info(`Session [${session}] has already been verified, no need to verify again`); + return 1; + } + 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); + return 1; + } + result.tryCount++; + if (result.tryCount >= this.AllowMaxTryCount) { + this.remove(session); + return -1; + } + RedisConnection.set(this.RedisCommonKey + session, JSON.stringify(result)); + return -2; + } catch (error) { + this.logger.error(`Error occurred while checking session [${session}]: ${error}`); + return 0; + } + } +} + +const CaptchaSession = new _CaptchaSession(); +export default CaptchaSession; \ No newline at end of file