diff --git a/src/push-notifications/dto/push-response.dto.ts b/src/push-notifications/dto/push-response.dto.ts index daf15d6..5da7fff 100644 --- a/src/push-notifications/dto/push-response.dto.ts +++ b/src/push-notifications/dto/push-response.dto.ts @@ -90,4 +90,18 @@ export class UnregisterTokenResponseDto { data: { success: boolean; }; +} + +export class UpdateTokenUserIdResponseDto { + @ApiProperty({ description: '响应代码' }) + code: ResponseCode; + + @ApiProperty({ description: '响应消息' }) + message: string; + + @ApiProperty({ description: '更新结果' }) + data: { + success: boolean; + tokenId: string; + }; } \ No newline at end of file diff --git a/src/push-notifications/dto/update-token-user-id.dto.ts b/src/push-notifications/dto/update-token-user-id.dto.ts new file mode 100644 index 0000000..43a278b --- /dev/null +++ b/src/push-notifications/dto/update-token-user-id.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNotEmpty } from 'class-validator'; + +export class UpdateTokenUserIdDto { + @ApiProperty({ description: '设备推送令牌' }) + @IsString() + @IsNotEmpty() + deviceToken: string; +} \ No newline at end of file diff --git a/src/push-notifications/push-notifications.controller.ts b/src/push-notifications/push-notifications.controller.ts index 197a3c1..6f33332 100644 --- a/src/push-notifications/push-notifications.controller.ts +++ b/src/push-notifications/push-notifications.controller.ts @@ -3,10 +3,11 @@ import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/ import { PushNotificationsService } from './push-notifications.service'; import { RegisterDeviceTokenDto } from './dto/register-device-token.dto'; import { UpdateDeviceTokenDto } from './dto/update-device-token.dto'; +import { UpdateTokenUserIdDto } from './dto/update-token-user-id.dto'; import { SendPushNotificationDto } from './dto/send-push-notification.dto'; import { SendPushByTemplateDto } from './dto/send-push-by-template.dto'; import { SendPushToDevicesDto } from './dto/send-push-to-devices.dto'; -import { PushResponseDto, BatchPushResponseDto, RegisterTokenResponseDto, UpdateTokenResponseDto, UnregisterTokenResponseDto } from './dto/push-response.dto'; +import { PushResponseDto, BatchPushResponseDto, RegisterTokenResponseDto, UpdateTokenResponseDto, UnregisterTokenResponseDto, UpdateTokenUserIdResponseDto } from './dto/push-response.dto'; import { DevicePushResponseDto, BatchDevicePushResponseDto } from './dto/device-push-response.dto'; import { CurrentUser } from '../common/decorators/current-user.decorator'; import { AccessTokenPayload } from '../users/services/apple-auth.service'; @@ -15,7 +16,7 @@ import { Public } from '../common/decorators/public.decorator'; @ApiTags('推送通知') @Controller('push-notifications') - +@UseGuards(JwtAuthGuard) export class PushNotificationsController { constructor(private readonly pushNotificationsService: PushNotificationsService) { } @@ -42,6 +43,16 @@ export class PushNotificationsController { return this.pushNotificationsService.updateToken(user?.sub || '', updateTokenDto); } + @Put('update-token-user-id') + @ApiOperation({ summary: '更新令牌绑定的用户ID' }) + @ApiResponse({ status: 200, description: '更新成功', type: UpdateTokenUserIdResponseDto }) + async updateTokenUserId( + @CurrentUser() user: AccessTokenPayload, + @Body() updateTokenUserIdDto: UpdateTokenUserIdDto, + ): Promise { + return this.pushNotificationsService.updateTokenUserId(user.sub, updateTokenUserIdDto.deviceToken); + } + @Delete('unregister-token') @Public() @ApiOperation({ summary: '注销设备推送令牌' }) @@ -55,7 +66,6 @@ export class PushNotificationsController { @Post('send') @ApiOperation({ summary: '发送推送通知' }) - @UseGuards(JwtAuthGuard) @ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto }) async sendNotification( @Body() sendNotificationDto: SendPushNotificationDto, @@ -65,7 +75,6 @@ export class PushNotificationsController { @Post('send-by-template') @ApiOperation({ summary: '使用模板发送推送' }) - @UseGuards(JwtAuthGuard) @ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto }) async sendNotificationByTemplate( @Body() sendByTemplateDto: SendPushByTemplateDto, @@ -75,7 +84,6 @@ export class PushNotificationsController { @Post('send-batch') @ApiOperation({ summary: '批量发送推送' }) - @UseGuards(JwtAuthGuard) @ApiResponse({ status: 200, description: '发送成功', type: BatchPushResponseDto }) async sendBatchNotifications( @Body() sendBatchDto: SendPushNotificationDto, @@ -85,7 +93,6 @@ export class PushNotificationsController { @Post('send-silent') @ApiOperation({ summary: '发送静默推送' }) - @UseGuards(JwtAuthGuard) @ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto }) async sendSilentNotification( @Body() body: { userId: string; payload: any }, @@ -95,7 +102,6 @@ export class PushNotificationsController { @Post('send-to-devices') @ApiOperation({ summary: '向指定设备发送推送通知' }) - @UseGuards(JwtAuthGuard) @ApiResponse({ status: 200, description: '发送成功', type: DevicePushResponseDto }) async sendNotificationToDevices( @Body() sendToDevicesDto: SendPushToDevicesDto, @@ -105,7 +111,6 @@ export class PushNotificationsController { @Post('send-batch-to-devices') @ApiOperation({ summary: '批量向指定设备发送推送通知' }) - @UseGuards(JwtAuthGuard) @ApiResponse({ status: 200, description: '发送成功', type: BatchDevicePushResponseDto }) async sendBatchNotificationToDevices( @Body() sendBatchToDevicesDto: SendPushToDevicesDto, diff --git a/src/push-notifications/push-notifications.service.ts b/src/push-notifications/push-notifications.service.ts index 1bd9b24..b4b8e22 100644 --- a/src/push-notifications/push-notifications.service.ts +++ b/src/push-notifications/push-notifications.service.ts @@ -504,6 +504,34 @@ export class PushNotificationsService { } } + /** + * 更新令牌绑定的用户ID + */ + async updateTokenUserId(userId: string, deviceToken: string): Promise { + try { + const token = await this.pushTokenService.updateTokenUserId(deviceToken, userId); + this.logger.log(`Updated user ID for device token: ${deviceToken}`); + return { + code: ResponseCode.SUCCESS, + message: '令牌用户ID更新成功', + data: { + success: true, + tokenId: token.id, + }, + }; + } catch (error) { + this.logger.error(`Failed to update user ID for device token: ${error.message}`, error); + return { + code: ResponseCode.ERROR, + message: `令牌用户ID更新失败: ${error.message}`, + data: { + success: false, + tokenId: '', + }, + }; + } + } + /** * 基于设备令牌发送推送通知 */ diff --git a/src/push-notifications/push-test.service.ts b/src/push-notifications/push-test.service.ts index fac32b2..b5c94be 100644 --- a/src/push-notifications/push-test.service.ts +++ b/src/push-notifications/push-test.service.ts @@ -38,6 +38,16 @@ export class PushTestService implements OnModuleInit { 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 { diff --git a/src/push-notifications/push-token.service.ts b/src/push-notifications/push-token.service.ts index ee0469f..085c61a 100644 --- a/src/push-notifications/push-token.service.ts +++ b/src/push-notifications/push-token.service.ts @@ -351,4 +351,43 @@ export class PushTokenService { throw error; } } + + /** + * 更新令牌绑定的用户ID + */ + async updateTokenUserId(deviceToken: string, userId: string): Promise { + try { + this.logger.log(`Updating user ID for device token: ${deviceToken}`); + + // 查找设备令牌 + const token = await this.pushTokenModel.findOne({ + where: { + deviceToken, + isActive: true, + }, + }); + + if (!token) { + throw new NotFoundException('Device token not found or inactive'); + } + + // 检查是否已经绑定了其他用户 + if (token.userId && token.userId !== userId) { + this.logger.warn(`Device token ${deviceToken} is already bound to another user ${token.userId}`); + // 如果已经绑定到其他用户,我们仍然更新为当前用户,因为可能是同一用户在不同设备上登录 + } + + // 更新用户ID + await token.update({ + userId, + lastUsedAt: new Date(), + }); + + this.logger.log(`Successfully updated user ID for device token: ${deviceToken}`); + return token; + } catch (error) { + this.logger.error(`Failed to update user ID for device token: ${deviceToken}: ${error.message}`, error); + throw error; + } + } } \ No newline at end of file