添加 captcha服务useRedis选项
This commit is contained in:
25
src/api/isPassedCaptcha.ts
Normal file
25
src/api/isPassedCaptcha.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { API, RequestData } from "@lib/API/API";
|
||||||
|
import ServerStdResponse from "@lib/ServerResponse/ServerStdResponse";
|
||||||
|
import { Response } from "express";
|
||||||
|
import CaptchaSession from "@lib/Service/CaptchaSession";
|
||||||
|
// 检查人机验证是否通过
|
||||||
|
class isPassedCaptcha extends API {
|
||||||
|
constructor() {
|
||||||
|
super('GET', '/isPassedCaptcha');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onRequset(data: RequestData, res: Response): Promise<void> {
|
||||||
|
// 获取session
|
||||||
|
const { session } = data;
|
||||||
|
if (!session) {
|
||||||
|
res.json({ ...ServerStdResponse.ERROR });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (await CaptchaSession.isPassed(session))
|
||||||
|
res.json(ServerStdResponse.OK);
|
||||||
|
else
|
||||||
|
res.json({ ...ServerStdResponse.ERROR, message: 'not passed' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default isPassedCaptcha;
|
||||||
@@ -18,6 +18,7 @@ const config = {
|
|||||||
},
|
},
|
||||||
service: {
|
service: {
|
||||||
captchaSession: {
|
captchaSession: {
|
||||||
|
useRedis: false,
|
||||||
allowMaxTryCount: 5,
|
allowMaxTryCount: 5,
|
||||||
allowMaxAngleDiff: 8,
|
allowMaxAngleDiff: 8,
|
||||||
expriedTimeSec: 60,
|
expriedTimeSec: 60,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ RedisConnection
|
|||||||
import GetTest from "./api/GetTest";
|
import GetTest from "./api/GetTest";
|
||||||
import GetCaptcha from "./api/GetCaptcha";
|
import GetCaptcha from "./api/GetCaptcha";
|
||||||
import CheckCaptcha from "./api/CheckCaptcha";
|
import CheckCaptcha from "./api/CheckCaptcha";
|
||||||
|
import isPassedCaptcha from "./api/isPassedCaptcha";
|
||||||
const logger = new Logger('Server')
|
const logger = new Logger('Server')
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
@@ -18,6 +19,7 @@ async function main(): Promise<void> {
|
|||||||
apiLoader.add(GetTest);
|
apiLoader.add(GetTest);
|
||||||
apiLoader.add(GetCaptcha);
|
apiLoader.add(GetCaptcha);
|
||||||
apiLoader.add(CheckCaptcha);
|
apiLoader.add(CheckCaptcha);
|
||||||
|
apiLoader.add(isPassedCaptcha);
|
||||||
|
|
||||||
await apiLoader.start(config.API_Port);
|
await apiLoader.start(config.API_Port);
|
||||||
logger.info('Server started successfully')
|
logger.info('Server started successfully')
|
||||||
|
|||||||
@@ -4,31 +4,82 @@
|
|||||||
* @description 旋转图像验证服务
|
* @description 旋转图像验证服务
|
||||||
*/
|
*/
|
||||||
import Logger from '@lib/Logger/Logger';
|
import Logger from '@lib/Logger/Logger';
|
||||||
import RedisConnection from '@lib/Database/RedisConnection';
|
import type { Redis } from '@lib/Database/RedisConnection';
|
||||||
import config from 'src/config';
|
import config from 'src/config';
|
||||||
type CaptchaSessionDataJSON = {
|
interface CaptchaSessionRedisDataJSON {
|
||||||
rotateDeg: number,
|
rotateDeg: number;
|
||||||
tryCount: number,
|
tryCount: number;
|
||||||
isPassed: boolean
|
isPassed: boolean;
|
||||||
|
expiredTimestamp?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CaptchaSessionPoolDataJSON extends CaptchaSessionRedisDataJSON {
|
||||||
|
expiredTimestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CaptchaPool = {
|
||||||
|
[key: string]: CaptchaSessionPoolDataJSON;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CaptchaSession {
|
class _CaptchaSession {
|
||||||
|
private readonly useRedis: boolean = config.service.captchaSession.useRedis;
|
||||||
private readonly logger = new Logger('Service', 'captchaSession');
|
private readonly logger = new Logger('Service', 'captchaSession');
|
||||||
private readonly AllowMaxTryCount: number = config.service.captchaSession.allowMaxTryCount;
|
private readonly AllowMaxTryCount: number = config.service.captchaSession.allowMaxTryCount;
|
||||||
private readonly AllowMaxAngleDiff: number = config.service.captchaSession.allowMaxAngleDiff;
|
private readonly AllowMaxAngleDiff: number = config.service.captchaSession.allowMaxAngleDiff;
|
||||||
private readonly ExpriedTimeSec: number = config.service.captchaSession.expriedTimeSec;
|
private readonly ExpriedTimeSec: number = config.service.captchaSession.expriedTimeSec;
|
||||||
private readonly RedisCommonKey: string = 'Service:captchaSession:';
|
private readonly RedisCommonKey: string = 'Service:captchaSession:';
|
||||||
|
private redisConnection?: Redis;
|
||||||
|
private captchaPool?: CaptchaPool;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
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');
|
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 {
|
try {
|
||||||
const result = await RedisConnection.get(this.RedisCommonKey + session);
|
if (this.useRedis) {
|
||||||
if (result === null)
|
let redisRes = await this.redisConnection!.get(this.RedisCommonKey + session);
|
||||||
|
if (!redisRes)
|
||||||
return;
|
return;
|
||||||
return JSON.parse(result);
|
return JSON.parse(redisRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
let poolRes = this.captchaPool![session];
|
||||||
|
if (!poolRes)
|
||||||
|
return;
|
||||||
|
// 如果不来自Redis,则需要处理是否过期
|
||||||
|
if (!this.useRedis && Date.now() > poolRes.expiredTimestamp)
|
||||||
|
return;
|
||||||
|
return poolRes;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Error occurred while retrieving session [${session}]: ${error}`);
|
this.logger.error(`Error occurred while retrieving session [${session}]: ${error}`);
|
||||||
return;
|
return;
|
||||||
@@ -37,7 +88,10 @@ class _CaptchaSession {
|
|||||||
|
|
||||||
private async remove(session: string): Promise<void> {
|
private async remove(session: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await RedisConnection.del(this.RedisCommonKey + session);
|
if (this.useRedis)
|
||||||
|
await this.redisConnection!.del(this.RedisCommonKey + session);
|
||||||
|
else
|
||||||
|
delete this.captchaPool![session];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to delete session [${session}]`);
|
this.logger.error(`Failed to delete session [${session}]`);
|
||||||
}
|
}
|
||||||
@@ -50,20 +104,29 @@ class _CaptchaSession {
|
|||||||
* @returns true存储成功 false存储失败
|
* @returns true存储成功 false存储失败
|
||||||
*/
|
*/
|
||||||
public async add(session: string, rotateDeg: number): Promise<boolean> {
|
public async add(session: string, rotateDeg: number): Promise<boolean> {
|
||||||
const result: CaptchaSessionDataJSON = {
|
const result: CaptchaSessionRedisDataJSON = {
|
||||||
rotateDeg: rotateDeg,
|
rotateDeg: rotateDeg,
|
||||||
tryCount: 0,
|
tryCount: 0,
|
||||||
isPassed: false
|
isPassed: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await RedisConnection.set(this.RedisCommonKey + session, JSON.stringify(result));
|
if (this.useRedis) {
|
||||||
|
const res = await this.redisConnection!.set(this.RedisCommonKey + session, JSON.stringify(result));
|
||||||
if (res && res === 'OK') {
|
if (res && res === 'OK') {
|
||||||
RedisConnection.expire(this.RedisCommonKey + session, this.ExpriedTimeSec);
|
this.redisConnection!.expire(this.RedisCommonKey + session, this.ExpriedTimeSec);
|
||||||
this.logger.info(`Session [${session}] and rotation angle [${rotateDeg}] have been stored`);
|
this.logger.info(`Session [${session}] and rotation angle [${rotateDeg}] have been stored`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
this.logger.error(`Failed to store session [${session}] and rotation angle [${rotateDeg}]`);
|
this.logger.error(`Failed to store session [${session}] and rotation angle [${rotateDeg}]`);
|
||||||
return false;
|
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) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to store session [${session}] and rotation angle [${rotateDeg}]: ${error}`);
|
this.logger.error(`Failed to store session [${session}] and rotation angle [${rotateDeg}]: ${error}`);
|
||||||
return false;
|
return false;
|
||||||
@@ -102,17 +165,25 @@ class _CaptchaSession {
|
|||||||
}
|
}
|
||||||
if (Math.abs(result.rotateDeg - rotateDeg) <= this.AllowMaxAngleDiff) {
|
if (Math.abs(result.rotateDeg - rotateDeg) <= this.AllowMaxAngleDiff) {
|
||||||
result.isPassed = true;
|
result.isPassed = true;
|
||||||
await RedisConnection.del(this.RedisCommonKey + session);
|
if (this.useRedis) {
|
||||||
await RedisConnection.set(this.RedisCommonKey + session, JSON.stringify(result));
|
await this.redisConnection!.del(this.RedisCommonKey + session);
|
||||||
RedisConnection.expire(this.RedisCommonKey + session, this.ExpriedTimeSec);
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
// 角度偏差过大,尝试次数记录+1
|
||||||
result.tryCount++;
|
result.tryCount++;
|
||||||
if (result.tryCount >= this.AllowMaxTryCount) {
|
if (result.tryCount >= this.AllowMaxTryCount) {
|
||||||
|
// 已达最大尝试次数
|
||||||
this.remove(session);
|
this.remove(session);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
RedisConnection.set(this.RedisCommonKey + session, JSON.stringify(result));
|
// 允许下一次尝试
|
||||||
|
if (this.useRedis)
|
||||||
|
this.redisConnection!.set(this.RedisCommonKey + session, JSON.stringify(result));
|
||||||
return -2;
|
return -2;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Error occurred while checking session [${session}]: ${error}`);
|
this.logger.error(`Error occurred while checking session [${session}]: ${error}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user