refactor(push-notifications): 移除推送测试服务模块

删除PushTestService及其相关依赖,该服务用于在应用启动时执行挑战相关的推送测试。移除内容包括:
- 删除push-test.service.ts文件(287行代码)
- 从push-notifications.module.ts中移除PushTestService的导入和注册
- 移除了挑战提醒推送测试、活跃参与者查询等测试功能

此变更简化了推送通知模块结构,移除了仅用于测试目的的代码。
This commit is contained in:
richarjiang
2025-11-18 15:33:05 +08:00
parent bbc6924f5b
commit 07fae9bdc0
2 changed files with 0 additions and 289 deletions

View File

@@ -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: [

View File

@@ -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<boolean>('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<number>('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<void> {
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<Challenge[]> {
// 获取所有挑战,然后在内存中过滤
// 这样可以避免数据库查询时的类型转换问题
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<ChallengeParticipant[]> {
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<Map<string, UserPushToken[]>> {
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<string, UserPushToken[]>();
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<string, UserPushToken[]>,
participants: ChallengeParticipant[]
): Promise<void> {
// 创建挑战ID到挑战对象的映射
const challengeMap = new Map<string, Challenge>();
challenges.forEach(challenge => {
challengeMap.set(challenge.id, challenge);
});
// 创建用户ID到参与者的映射
const userParticipantMap = new Map<string, ChallengeParticipant>();
participants.forEach(participant => {
userParticipantMap.set(participant.userId, participant);
});
let totalSent = 0;
let totalFailed = 0;
const maxUsers = this.configService.get<number>('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 };
}
}