import { Injectable, Logger, NotFoundException, ConflictException } from '@nestjs/common'; import { InjectModel } from '@nestjs/sequelize'; import { Op } from 'sequelize'; import { UserPushToken } from './models/user-push-token.model'; import { DeviceType } from './enums/device-type.enum'; import { RegisterDeviceTokenDto } from './dto/register-device-token.dto'; import { UpdateDeviceTokenDto } from './dto/update-device-token.dto'; @Injectable() export class PushTokenService { private readonly logger = new Logger(PushTokenService.name); constructor( @InjectModel(UserPushToken) private readonly pushTokenModel: typeof UserPushToken, ) { } /** * 注册设备令牌 */ async registerToken(tokenData: RegisterDeviceTokenDto, userId?: string): Promise { try { this.logger.log(`Registering push token for device ${tokenData.deviceToken}`); // 检查是否已存在相同的令牌 const existingToken = await this.pushTokenModel.findOne({ where: { deviceToken: tokenData.deviceToken, }, }); if (existingToken) { // 更新现有令牌信息 await existingToken.update({ deviceType: tokenData.deviceType, appVersion: tokenData.appVersion, osVersion: tokenData.osVersion, deviceName: tokenData.deviceName, isActive: true, lastUsedAt: new Date(), }); this.logger.log(`Updated existing push token for device ${tokenData.deviceToken}`); return existingToken; } // 创建新令牌 const newToken = await this.pushTokenModel.create({ userId, deviceToken: tokenData.deviceToken, deviceType: tokenData.deviceType, appVersion: tokenData.appVersion, osVersion: tokenData.osVersion, deviceName: tokenData.deviceName, isActive: true, lastUsedAt: new Date(), }); this.logger.log(`Successfully registered new push token for device ${tokenData.deviceToken}`); return newToken; } catch (error) { this.logger.error(`Failed to register push token for device ${tokenData.deviceToken}: ${error.message}`, error); throw error; } } /** * 更新设备令牌 */ async updateToken(userId: string, tokenData: UpdateDeviceTokenDto): Promise { try { this.logger.log(`Updating push token for user ${userId}`); // 查找当前令牌 const currentToken = await this.pushTokenModel.findOne({ where: { userId, deviceToken: tokenData.currentDeviceToken, isActive: true, }, }); if (!currentToken) { throw new NotFoundException('Current device token not found or inactive'); } // 检查新令牌是否已存在 const existingNewToken = await this.pushTokenModel.findOne({ where: { userId, deviceToken: tokenData.newDeviceToken, }, }); if (existingNewToken) { // 如果新令牌已存在,激活它并停用当前令牌 await existingNewToken.update({ isActive: true, lastUsedAt: new Date(), appVersion: tokenData.appVersion || existingNewToken.appVersion, osVersion: tokenData.osVersion || existingNewToken.osVersion, deviceName: tokenData.deviceName || existingNewToken.deviceName, }); await currentToken.update({ isActive: false, }); this.logger.log(`Activated existing new token and deactivated old token for user ${userId}`); return existingNewToken; } // 更新当前令牌为新令牌 await currentToken.update({ deviceToken: tokenData.newDeviceToken, appVersion: tokenData.appVersion, osVersion: tokenData.osVersion, deviceName: tokenData.deviceName, lastUsedAt: new Date(), }); this.logger.log(`Successfully updated push token for user ${userId}`); return currentToken; } catch (error) { this.logger.error(`Failed to update push token for user ${userId}: ${error.message}`, error); throw error; } } /** * 注销设备令牌 */ async unregisterToken(userId: string, deviceToken: string): Promise { try { this.logger.log(`Unregistering push token for user ${userId}`); const token = await this.pushTokenModel.findOne({ where: { userId, deviceToken, isActive: true, }, }); if (!token) { throw new NotFoundException('Device token not found or inactive'); } await token.update({ isActive: false, }); this.logger.log(`Successfully unregistered push token for user ${userId}`); } catch (error) { this.logger.error(`Failed to unregister push token for user ${userId}: ${error.message}`, error); throw error; } } /** * 获取用户的所有有效令牌 */ async getActiveTokens(userId: string): Promise { try { const tokens = await this.pushTokenModel.findAll({ where: { userId, isActive: true, }, order: [['lastUsedAt', 'DESC']], }); this.logger.log(`Found ${tokens.length} active tokens for user ${userId}`); return tokens; } catch (error) { this.logger.error(`Failed to get active tokens for user ${userId}: ${error.message}`, error); throw error; } } /** * 获取用户的所有令牌(包括非活跃的) */ async getAllTokens(userId: string): Promise { try { const tokens = await this.pushTokenModel.findAll({ where: { userId, }, order: [['createdAt', 'DESC']], }); this.logger.log(`Found ${tokens.length} total tokens for user ${userId}`); return tokens; } catch (error) { this.logger.error(`Failed to get all tokens for user ${userId}: ${error.message}`, error); throw error; } } /** * 验证令牌有效性 */ async validateToken(deviceToken: string): Promise { try { const token = await this.pushTokenModel.findOne({ where: { deviceToken, isActive: true, }, }); return !!token; } catch (error) { this.logger.error(`Failed to validate token: ${error.message}`, error); return false; } } /** * 清理无效令牌 */ async cleanupInvalidTokens(): Promise { try { this.logger.log('Starting cleanup of invalid tokens'); // 清理超过30天未使用的令牌 const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); const result = await this.pushTokenModel.update( { isActive: false, }, { where: { isActive: true, lastUsedAt: { [Op.lt]: thirtyDaysAgo, }, }, }, ); const cleanedCount = result[0]; this.logger.log(`Cleaned up ${cleanedCount} inactive tokens`); return cleanedCount; } catch (error) { this.logger.error(`Failed to cleanup invalid tokens: ${error.message}`, error); throw error; } } /** * 根据设备令牌获取用户ID */ async getUserIdByDeviceToken(deviceToken: string): Promise { try { const token = await this.pushTokenModel.findOne({ where: { deviceToken, isActive: true, }, }); return token ? token.userId : null; } catch (error) { this.logger.error(`Failed to get user ID by device token: ${error.message}`, error); return null; } } /** * 批量获取用户的设备令牌 */ async getDeviceTokensByUserIds(userIds: string[]): Promise> { try { const tokens = await this.pushTokenModel.findAll({ where: { userId: { [Op.in]: userIds, }, isActive: true, }, }); const userTokensMap = new Map(); tokens.forEach((token) => { if (!userTokensMap.has(token.userId)) { userTokensMap.set(token.userId, []); } userTokensMap.get(token.userId)!.push(token.deviceToken); }); return userTokensMap; } catch (error) { this.logger.error(`Failed to get device tokens by user IDs: ${error.message}`, error); throw error; } } /** * 更新令牌最后使用时间 */ async updateLastUsedTime(deviceToken: string): Promise { try { await this.pushTokenModel.update( { lastUsedAt: new Date(), }, { where: { deviceToken, isActive: true, }, }, ); } catch (error) { this.logger.error(`Failed to update last used time: ${error.message}`, error); } } /** * 直接停用设备令牌(不需要用户ID) */ async deactivateToken(deviceToken: string): Promise { try { this.logger.log(`Deactivating push token: ${deviceToken}`); const token = await this.pushTokenModel.findOne({ where: { deviceToken, isActive: true, }, }); if (!token) { this.logger.warn(`Device token not found or already inactive: ${deviceToken}`); return; } await token.update({ isActive: false, }); this.logger.log(`Successfully deactivated push token: ${deviceToken}`); } catch (error) { this.logger.error(`Failed to deactivate push token: ${deviceToken}: ${error.message}`, error); throw error; } } }