From 19791bbae852416820ae97a3cd2edbcd94b3b770 Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 25 Jul 2022 23:05:35 +0800 Subject: [PATCH] feat: table scan to delete outdate token Signed-off-by: Innei --- src/app.config.ts | 2 +- src/processors/helper/helper.jwt.service.ts | 59 +++++++++++++++++++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/app.config.ts b/src/app.config.ts index 2272d5a9..de600101 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -59,7 +59,7 @@ export const AXIOS_CONFIG: AxiosRequestConfig = { export const SECURITY = { jwtSecret: argv.jwt_secret || argv.jwtSecret, - jwtExpire: '7d', + jwtExpire: +argv.jwt_expire || 14, // 跳过登陆鉴权 skipAuth: isTest ? true : false, } diff --git a/src/processors/helper/helper.jwt.service.ts b/src/processors/helper/helper.jwt.service.ts index 87c8097b..2a665534 100644 --- a/src/processors/helper/helper.jwt.service.ts +++ b/src/processors/helper/helper.jwt.service.ts @@ -1,10 +1,13 @@ import cluster from 'cluster' +import dayjs from 'dayjs' import { sign, verify } from 'jsonwebtoken' import { machineIdSync } from 'node-machine-id' -import { Injectable } from '@nestjs/common' +import { Injectable, Logger } from '@nestjs/common' +import { CronExpression } from '@nestjs/schedule' import { CLUSTER, SECURITY } from '~/app.config' +import { CronOnce } from '~/common/decorator/cron-once.decorator' import { RedisKeys } from '~/constants/cache.constant' import { getRedisKey, md5 } from '~/utils' @@ -13,8 +16,10 @@ import { CacheService } from '../redis/cache.service' @Injectable() export class JWTService { private secret = '' + private readonly logger: Logger constructor(private readonly cacheService: CacheService) { this.init() + this.logger = new Logger(JWTService.name) } init() { @@ -101,15 +106,63 @@ export class JWTService { JSON.stringify({ date: new Date().toISOString(), ...info, - }), + } as StoreJWTPayload), ) } + private readonly expiresDay = SECURITY.jwtExpire + sign(id: string, info?: { ip: string; ua: string }) { const token = sign({ id }, this.secret, { - expiresIn: '30d', + expiresIn: `${this.expiresDay}d`, }) this.storeTokenInRedis(token, info || {}) return token } + + @CronOnce(CronExpression.EVERY_DAY_AT_1AM) + async scanTable() { + this.logger.log('--> 开始扫表,清除过期的 token') + const redis = this.cacheService.getClient() + const keys = await redis.hkeys(getRedisKey(RedisKeys.JWTStore)) + let deleteCount = 0 + await Promise.all( + keys.map(async (key) => { + const value = await redis.hget(getRedisKey(RedisKeys.JWTStore), key) + if (!value) { + return null + } + const parsed = JSON.safeParse(value) as StoreJWTPayload + if (!parsed) { + return null + } + + const date = dayjs(new Date(parsed.date)) + if (date.add(this.expiresDay, 'd').diff(new Date(), 'd') < 0) { + this.logger.debug( + `--> 删除过期的 token:${key}, 签发于 ${date.format( + 'YYYY-MM-DD H:mm:ss', + )}`, + ) + + return await redis + .hdel(getRedisKey(RedisKeys.JWTStore), key) + .then(() => { + deleteCount += 1 + }) + } + return null + }), + ) + + this.logger.log(`--> 删除了 ${deleteCount} 个过期的 token`) + } +} + +interface StoreJWTPayload { + /** + * ISODateString + */ + date: string + [k: string]: any }