From 07fae9bdc0fb9ee616654206bb9cecaf73afb980 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Tue, 18 Nov 2025 15:33:05 +0800 Subject: [PATCH] =?UTF-8?q?refactor(push-notifications):=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E6=8E=A8=E9=80=81=E6=B5=8B=E8=AF=95=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除PushTestService及其相关依赖,该服务用于在应用启动时执行挑战相关的推送测试。移除内容包括: - 删除push-test.service.ts文件(287行代码) - 从push-notifications.module.ts中移除PushTestService的导入和注册 - 移除了挑战提醒推送测试、活跃参与者查询等测试功能 此变更简化了推送通知模块结构,移除了仅用于测试目的的代码。 --- .../push-notifications.module.ts | 2 - src/push-notifications/push-test.service.ts | 287 ------------------ 2 files changed, 289 deletions(-) delete mode 100644 src/push-notifications/push-test.service.ts diff --git a/src/push-notifications/push-notifications.module.ts b/src/push-notifications/push-notifications.module.ts index c7cde1c..4820f14 100644 --- a/src/push-notifications/push-notifications.module.ts +++ b/src/push-notifications/push-notifications.module.ts @@ -7,7 +7,6 @@ import { ApnsProvider } from './apns.provider'; import { PushTokenService } from './push-token.service'; import { PushTemplateService } from './push-template.service'; import { PushMessageService } from './push-message.service'; -import { PushTestService } from './push-test.service'; import { ChallengeReminderService } from './challenge-reminder.service'; import { UserPushToken } from './models/user-push-token.model'; import { PushMessage } from './models/push-message.model'; @@ -43,7 +42,6 @@ import { ChallengeParticipant } from '../challenges/models/challenge-participant PushTokenService, PushTemplateService, PushMessageService, - PushTestService, ChallengeReminderService, ], exports: [ diff --git a/src/push-notifications/push-test.service.ts b/src/push-notifications/push-test.service.ts deleted file mode 100644 index d3a6ef3..0000000 --- a/src/push-notifications/push-test.service.ts +++ /dev/null @@ -1,287 +0,0 @@ -import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { PushNotificationsService } from './push-notifications.service'; -import { PushTokenService } from './push-token.service'; -import { UserPushToken } from './models/user-push-token.model'; -import { InjectModel } from '@nestjs/sequelize'; -import { Op } from 'sequelize'; -import { PushType } from 'apns2'; -import { ChallengesService } from '../challenges/challenges.service'; -import { Challenge } from '../challenges/models/challenge.model'; -import { ChallengeParticipant, ChallengeParticipantStatus } from '../challenges/models/challenge-participant.model'; -import * as dayjs from 'dayjs'; - -@Injectable() -export class PushTestService implements OnModuleInit { - private readonly logger = new Logger(PushTestService.name); - - constructor( - @InjectModel(UserPushToken) - private readonly pushTokenModel: typeof UserPushToken, - @InjectModel(ChallengeParticipant) - private readonly participantModel: typeof ChallengeParticipant, - private readonly pushNotificationsService: PushNotificationsService, - private readonly pushTokenService: PushTokenService, - private readonly challengesService: ChallengesService, - private readonly configService: ConfigService, - ) { } - - /** - * 模块初始化时执行 - */ - async onModuleInit() { - // 检查是否启用推送测试 - const enablePushTest = this.configService.get('ENABLE_PUSH_TEST', false); - return - if (!enablePushTest) { - this.logger.log('Push test is disabled. Skipping...'); - return; - } - - // 检查是否为主进程(NODE_APP_INSTANCE 为 0) - const nodeAppInstance = this.configService.get('NODE_APP_INSTANCE', 0); - this.logger.log(`nodeAppInstance: ${nodeAppInstance}`) - if (Number(nodeAppInstance) !== 0) { - this.logger.log(`Not the primary process (instance: ${nodeAppInstance}). Skipping push test...`); - return; - } - - this.logger.log('Primary process detected. Running push test...'); - - // 延迟执行,确保应用完全启动 - setTimeout(async () => { - try { - await this.performPushTest(); - } catch (error) { - this.logger.error(`Push test failed: ${error.message}`, error); - } - }, 5000); // 5秒后执行 - } - - /** - * 执行推送测试 - */ - private async performPushTest(): Promise { - this.logger.log('Starting challenge-based push test...'); - - try { - // 1. 获取正在进行中的挑战 - const ongoingChallenges = await this.getOngoingChallenges(); - - if (ongoingChallenges.length === 0) { - this.logger.log('No ongoing challenges found for testing'); - return; - } - - this.logger.log(`Found ${ongoingChallenges.length} ongoing challenges`); - - // 2. 获取参与这些挑战的活跃用户 - const activeParticipants = await this.getActiveParticipants(ongoingChallenges); - - if (activeParticipants.length === 0) { - this.logger.log('No active participants found in ongoing challenges'); - return; - } - - this.logger.log(`Found ${activeParticipants.length} active participants`); - - // 3. 获取这些用户的活跃推送令牌 - const userTokensMap = await this.getUserTokensMap(activeParticipants); - - if (userTokensMap.size === 0) { - this.logger.log('No active push tokens found for challenge participants'); - return; - } - - this.logger.log(`Found push tokens for ${userTokensMap.size} users`); - - // 4. 发送挑战相关推送 - await this.sendChallengeReminders(ongoingChallenges, userTokensMap, activeParticipants); - - } catch (error) { - this.logger.error(`Error during challenge-based push test: ${error.message}`, error); - } - } - - /** - * 获取正在进行中的挑战 - */ - private async getOngoingChallenges(): Promise { - // 获取所有挑战,然后在内存中过滤 - // 这样可以避免数据库查询时的类型转换问题 - const challenges = await this.challengesService['challengeModel'].findAll({ - order: [['startAt', 'ASC']], - }); - - // 直接实现状态计算逻辑,过滤出真正进行中的挑战 - return challenges.filter(challenge => { - const start = dayjs(challenge.startAt); - const end = dayjs(challenge.endAt); - const current = dayjs(); - - // 检查当前时间是否在开始和结束时间之间 - return current.isAfter(start, 'minute') && current.isBefore(end, 'minute'); - }); - } - - /** - * 获取参与挑战的活跃用户 - */ - private async getActiveParticipants(challenges: Challenge[]): Promise { - const challengeIds = challenges.map(challenge => challenge.id); - - const participants = await this.participantModel.findAll({ - where: { - challengeId: { - [Op.in]: challengeIds, - }, - status: ChallengeParticipantStatus.ACTIVE, - }, - }); - - return participants; - } - - /** - * 获取用户的推送令牌映射 - */ - private async getUserTokensMap(participants: ChallengeParticipant[]): Promise> { - const userIds = [...new Set(participants.map(p => p.userId))]; - - const tokens = await this.pushTokenModel.findAll({ - where: { - userId: { - [Op.in]: userIds, - }, - isActive: true, - }, - }); - - const userTokensMap = new Map(); - - tokens.forEach(token => { - if (token.userId) { - if (!userTokensMap.has(token.userId)) { - userTokensMap.set(token.userId, []); - } - userTokensMap.get(token.userId)!.push(token); - } - }); - - return userTokensMap; - } - - /** - * 发送挑战提醒推送 - */ - private async sendChallengeReminders( - challenges: Challenge[], - userTokensMap: Map, - participants: ChallengeParticipant[] - ): Promise { - // 创建挑战ID到挑战对象的映射 - const challengeMap = new Map(); - challenges.forEach(challenge => { - challengeMap.set(challenge.id, challenge); - }); - - // 创建用户ID到参与者的映射 - const userParticipantMap = new Map(); - participants.forEach(participant => { - userParticipantMap.set(participant.userId, participant); - }); - - let totalSent = 0; - let totalFailed = 0; - const maxUsers = this.configService.get('PUSH_TEST_MAX_USERS', 50); // 限制推送用户数量 - let userCount = 0; - - // 为每个用户发送推送 - for (const [userId, tokens] of userTokensMap) { - if (userCount >= maxUsers) { - this.logger.log(`Reached maximum user limit (${maxUsers}), stopping push test`); - break; - } - - const participant = userParticipantMap.get(userId); - const challenge = participant ? challengeMap.get(participant.challengeId) : null; - - if (!challenge || !participant) { - continue; - } - - // 个性化推送内容 - const { title, body } = this.generateChallengePushContent(challenge, participant); - - // 发送推送 - const result = await this.pushNotificationsService.sendBatchNotificationToDevices({ - deviceTokens: tokens.map(token => token.deviceToken), - title, - body, - pushType: PushType.alert, - }); - - if (result.code === 0) { - totalSent += result.data.successCount; - totalFailed += result.data.failedCount; - - this.logger.log(`Challenge reminder sent to user ${userId} for challenge "${challenge.title}". Success: ${result.data.successCount}, Failed: ${result.data.failedCount}`); - } else { - totalFailed += tokens.length; - this.logger.warn(`Failed to send challenge reminder to user ${userId}: ${result.message}`); - } - - userCount++; - } - - this.logger.log(`Challenge-based push test completed. Total sent: ${totalSent}, Total failed: ${totalFailed}`); - } - - /** - * 生成挑战推送内容 - */ - private generateChallengePushContent( - challenge: Challenge, - participant: ChallengeParticipant - ): { title: string; body: string } { - const progress = participant.progressValue; - const target = participant.targetValue; - const remaining = Math.max(target - progress, 0); - - // 根据挑战类型生成不同的推送内容 - let title = '挑战提醒'; - let body = `您正在参与的"${challenge.title}"挑战进行中!`; - - switch (challenge.type) { - case 'water': - title = '饮水挑战提醒'; - body = `今日饮水挑战进行中!已完成 ${progress}/${target} 天,继续加油!`; - break; - case 'exercise': - title = '运动挑战提醒'; - body = `今日运动挑战进行中!已完成 ${progress}/${target} 天,坚持就是胜利!`; - break; - case 'diet': - title = '饮食挑战提醒'; - body = `健康饮食挑战进行中!已完成 ${progress}/${target} 天,保持良好饮食习惯!`; - break; - case 'mood': - title = '心情记录提醒'; - body = `心情记录挑战进行中!已完成 ${progress}/${target} 天,记录今日心情吧!`; - break; - case 'sleep': - title = '睡眠挑战提醒'; - body = `优质睡眠挑战进行中!已完成 ${progress}/${target} 天,保持规律作息!`; - break; - case 'weight': - title = '体重管理提醒'; - body = `体重管理挑战进行中!已完成 ${progress}/${target} 天,坚持健康生活方式!`; - break; - default: - body = `您正在参与的"${challenge.title}"挑战进行中!已完成 ${progress}/${target} 天,还需 ${remaining} 天完成挑战,加油!`; - } - - return { title, body }; - } - -} \ No newline at end of file