|
|
@@ -0,0 +1,71 @@
|
|
|
+import { FastifyInstance } from 'fastify'
|
|
|
+import { Repository } from 'typeorm'
|
|
|
+import { Member } from '../entities/member.entity'
|
|
|
+
|
|
|
+export class MemberTokenManagerService {
|
|
|
+ private memberRepository: Repository<Member>
|
|
|
+ private app: FastifyInstance
|
|
|
+
|
|
|
+ constructor(app: FastifyInstance) {
|
|
|
+ this.app = app
|
|
|
+ this.memberRepository = app.dataSource.getRepository(Member)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用户登录时,使该用户的所有其他token失效
|
|
|
+ * 通过更新member表的lastLoginAt时间戳来实现
|
|
|
+ */
|
|
|
+ async invalidateUserTokens(userId: number): Promise<void> {
|
|
|
+ const now = new Date()
|
|
|
+ await this.memberRepository.update(
|
|
|
+ { userId },
|
|
|
+ { lastLoginAt: now }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证token是否有效
|
|
|
+ * 通过比较token的签发时间与member表的最后登录时间
|
|
|
+ */
|
|
|
+ async isTokenValid(userId: number, tokenIssuedAt: number): Promise<boolean> {
|
|
|
+ try {
|
|
|
+ const member = await this.memberRepository.findOne({
|
|
|
+ where: { userId },
|
|
|
+ select: ['id', 'lastLoginAt']
|
|
|
+ })
|
|
|
+
|
|
|
+ if (!member || !member.lastLoginAt) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将token的签发时间转换为Date对象
|
|
|
+ const tokenIssuedDate = new Date(tokenIssuedAt * 1000)
|
|
|
+
|
|
|
+ // 添加1秒的容错范围,因为JWT的iat是秒级时间戳,而数据库是毫秒级
|
|
|
+ const tolerance = 1000 // 1秒容错
|
|
|
+ const adjustedLastLoginAt = new Date(member.lastLoginAt.getTime() - tolerance)
|
|
|
+
|
|
|
+ // 如果token的签发时间早于调整后的最后登录时间,则token无效
|
|
|
+ return tokenIssuedDate >= adjustedLastLoginAt
|
|
|
+ } catch (error) {
|
|
|
+ this.app.log.error(error, '验证token有效性失败')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取用户最后登录时间
|
|
|
+ */
|
|
|
+ async getUserLastLoginTime(userId: number): Promise<Date | null> {
|
|
|
+ try {
|
|
|
+ const member = await this.memberRepository.findOne({
|
|
|
+ where: { userId },
|
|
|
+ select: ['lastLoginAt']
|
|
|
+ })
|
|
|
+ return member?.lastLoginAt || null
|
|
|
+ } catch (error) {
|
|
|
+ this.app.log.error(error, '获取用户最后登录时间失败')
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|