import { Injectable, Logger } from '@nestjs/common' import { Redis } from 'ioredis' import { RedisService } from 'nestjs-redis' import { CommonException } from 'src/common/commom.exception' import { v4 as uuidv4 } from 'uuid' /** * 登录凭证服务 * * * ### 功能说明 * * ```markdown * 1. 用户登录状态管理,用户登录鉴权后,给用户发放登录凭证,后续访问携带该登录凭证表示其身份。 * 2. 登录凭证目前只记录了对应的用户 ID。 * ``` */ @Injectable() export class TokenService { private readonly logger = new Logger(TokenService.name) private readonly redis: Redis constructor(private readonly redisService: RedisService) { this.redis = this.redisService.getClient() } /** * 获取存储登录凭证的 Redis 键名 * * @param token 登录凭证 */ private getTokenRedisKey(token: string): string { return `auth:user_id:token:${token}` } /** * 创建登录凭证 * * @param userId 用户 ID * @param expiration 有效时长,单位:秒 */ async createToken(userId: number, expiration: number = 3600 * 24 * 10): Promise<string> { /** * ### 备注 * * ```markdown * 1. 一般不会走到下面这个判断中。 * 2. 使用中使用 `userId=0` 来标记未注册用户,为避免以后不小心进入这个逻辑,所以加了这个额外判断。 * ``` */ if (userId < 1) { this.logger.error(`尝试创建一个异常登录凭证, userId => ${userId}`) throw new CommonException('登录失败!') } /** 登录凭证 */ const token: string = uuidv4().replace(/-/gu, '') const rKey: string = this.getTokenRedisKey(token) await this.redis.set(rKey, userId, 'EX', expiration) return token } /** * 通过登录凭证换取用户 ID * * @param token 登录凭证 * * * ### 说明 * * ```markdown * 1. `token` 异常情况将返回结果 `0`。 * ``` */ async getUserIdByToken(token: string): Promise<number> { const rKey = this.getTokenRedisKey(token) const result = await this.redis.get(rKey) if (result) { return parseInt(result, 10) } return 0 } /** * 禁用登录凭证(即使其失效) * * @param token 登录凭证 * * * ### 说明 * * ```markdown * 1. 删除成功则返回 `true`,键名不存在则返回 `false` * ``` */ async disableToken(token: string): Promise<boolean> { const rKey = this.getTokenRedisKey(token) const result = await this.redis.del(rKey) return !!result } }