feat(push-notifications): 新增更新令牌用户ID功能
添加新的API端点用于更新设备推送令牌绑定的用户ID,包括: - 新增UpdateTokenUserIdDto和UpdateTokenUserIdResponseDto - 在控制器中添加updateTokenUserId端点 - 在服务层实现updateTokenUserId方法 - 在push-token服务中添加底层更新逻辑 - 优化推送测试服务,仅在主进程中执行
This commit is contained in:
@@ -91,3 +91,17 @@ export class UnregisterTokenResponseDto {
|
|||||||
success: boolean;
|
success: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdateTokenUserIdResponseDto {
|
||||||
|
@ApiProperty({ description: '响应代码' })
|
||||||
|
code: ResponseCode;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '响应消息' })
|
||||||
|
message: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新结果' })
|
||||||
|
data: {
|
||||||
|
success: boolean;
|
||||||
|
tokenId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
9
src/push-notifications/dto/update-token-user-id.dto.ts
Normal file
9
src/push-notifications/dto/update-token-user-id.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsString, IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export class UpdateTokenUserIdDto {
|
||||||
|
@ApiProperty({ description: '设备推送令牌' })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
deviceToken: string;
|
||||||
|
}
|
||||||
@@ -3,10 +3,11 @@ import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/
|
|||||||
import { PushNotificationsService } from './push-notifications.service';
|
import { PushNotificationsService } from './push-notifications.service';
|
||||||
import { RegisterDeviceTokenDto } from './dto/register-device-token.dto';
|
import { RegisterDeviceTokenDto } from './dto/register-device-token.dto';
|
||||||
import { UpdateDeviceTokenDto } from './dto/update-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 { SendPushNotificationDto } from './dto/send-push-notification.dto';
|
||||||
import { SendPushByTemplateDto } from './dto/send-push-by-template.dto';
|
import { SendPushByTemplateDto } from './dto/send-push-by-template.dto';
|
||||||
import { SendPushToDevicesDto } from './dto/send-push-to-devices.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 { DevicePushResponseDto, BatchDevicePushResponseDto } from './dto/device-push-response.dto';
|
||||||
import { CurrentUser } from '../common/decorators/current-user.decorator';
|
import { CurrentUser } from '../common/decorators/current-user.decorator';
|
||||||
import { AccessTokenPayload } from '../users/services/apple-auth.service';
|
import { AccessTokenPayload } from '../users/services/apple-auth.service';
|
||||||
@@ -15,7 +16,7 @@ import { Public } from '../common/decorators/public.decorator';
|
|||||||
|
|
||||||
@ApiTags('推送通知')
|
@ApiTags('推送通知')
|
||||||
@Controller('push-notifications')
|
@Controller('push-notifications')
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
export class PushNotificationsController {
|
export class PushNotificationsController {
|
||||||
constructor(private readonly pushNotificationsService: PushNotificationsService) { }
|
constructor(private readonly pushNotificationsService: PushNotificationsService) { }
|
||||||
|
|
||||||
@@ -42,6 +43,16 @@ export class PushNotificationsController {
|
|||||||
return this.pushNotificationsService.updateToken(user?.sub || '', updateTokenDto);
|
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<UpdateTokenUserIdResponseDto> {
|
||||||
|
return this.pushNotificationsService.updateTokenUserId(user.sub, updateTokenUserIdDto.deviceToken);
|
||||||
|
}
|
||||||
|
|
||||||
@Delete('unregister-token')
|
@Delete('unregister-token')
|
||||||
@Public()
|
@Public()
|
||||||
@ApiOperation({ summary: '注销设备推送令牌' })
|
@ApiOperation({ summary: '注销设备推送令牌' })
|
||||||
@@ -55,7 +66,6 @@ export class PushNotificationsController {
|
|||||||
|
|
||||||
@Post('send')
|
@Post('send')
|
||||||
@ApiOperation({ summary: '发送推送通知' })
|
@ApiOperation({ summary: '发送推送通知' })
|
||||||
@UseGuards(JwtAuthGuard)
|
|
||||||
@ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto })
|
@ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto })
|
||||||
async sendNotification(
|
async sendNotification(
|
||||||
@Body() sendNotificationDto: SendPushNotificationDto,
|
@Body() sendNotificationDto: SendPushNotificationDto,
|
||||||
@@ -65,7 +75,6 @@ export class PushNotificationsController {
|
|||||||
|
|
||||||
@Post('send-by-template')
|
@Post('send-by-template')
|
||||||
@ApiOperation({ summary: '使用模板发送推送' })
|
@ApiOperation({ summary: '使用模板发送推送' })
|
||||||
@UseGuards(JwtAuthGuard)
|
|
||||||
@ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto })
|
@ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto })
|
||||||
async sendNotificationByTemplate(
|
async sendNotificationByTemplate(
|
||||||
@Body() sendByTemplateDto: SendPushByTemplateDto,
|
@Body() sendByTemplateDto: SendPushByTemplateDto,
|
||||||
@@ -75,7 +84,6 @@ export class PushNotificationsController {
|
|||||||
|
|
||||||
@Post('send-batch')
|
@Post('send-batch')
|
||||||
@ApiOperation({ summary: '批量发送推送' })
|
@ApiOperation({ summary: '批量发送推送' })
|
||||||
@UseGuards(JwtAuthGuard)
|
|
||||||
@ApiResponse({ status: 200, description: '发送成功', type: BatchPushResponseDto })
|
@ApiResponse({ status: 200, description: '发送成功', type: BatchPushResponseDto })
|
||||||
async sendBatchNotifications(
|
async sendBatchNotifications(
|
||||||
@Body() sendBatchDto: SendPushNotificationDto,
|
@Body() sendBatchDto: SendPushNotificationDto,
|
||||||
@@ -85,7 +93,6 @@ export class PushNotificationsController {
|
|||||||
|
|
||||||
@Post('send-silent')
|
@Post('send-silent')
|
||||||
@ApiOperation({ summary: '发送静默推送' })
|
@ApiOperation({ summary: '发送静默推送' })
|
||||||
@UseGuards(JwtAuthGuard)
|
|
||||||
@ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto })
|
@ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto })
|
||||||
async sendSilentNotification(
|
async sendSilentNotification(
|
||||||
@Body() body: { userId: string; payload: any },
|
@Body() body: { userId: string; payload: any },
|
||||||
@@ -95,7 +102,6 @@ export class PushNotificationsController {
|
|||||||
|
|
||||||
@Post('send-to-devices')
|
@Post('send-to-devices')
|
||||||
@ApiOperation({ summary: '向指定设备发送推送通知' })
|
@ApiOperation({ summary: '向指定设备发送推送通知' })
|
||||||
@UseGuards(JwtAuthGuard)
|
|
||||||
@ApiResponse({ status: 200, description: '发送成功', type: DevicePushResponseDto })
|
@ApiResponse({ status: 200, description: '发送成功', type: DevicePushResponseDto })
|
||||||
async sendNotificationToDevices(
|
async sendNotificationToDevices(
|
||||||
@Body() sendToDevicesDto: SendPushToDevicesDto,
|
@Body() sendToDevicesDto: SendPushToDevicesDto,
|
||||||
@@ -105,7 +111,6 @@ export class PushNotificationsController {
|
|||||||
|
|
||||||
@Post('send-batch-to-devices')
|
@Post('send-batch-to-devices')
|
||||||
@ApiOperation({ summary: '批量向指定设备发送推送通知' })
|
@ApiOperation({ summary: '批量向指定设备发送推送通知' })
|
||||||
@UseGuards(JwtAuthGuard)
|
|
||||||
@ApiResponse({ status: 200, description: '发送成功', type: BatchDevicePushResponseDto })
|
@ApiResponse({ status: 200, description: '发送成功', type: BatchDevicePushResponseDto })
|
||||||
async sendBatchNotificationToDevices(
|
async sendBatchNotificationToDevices(
|
||||||
@Body() sendBatchToDevicesDto: SendPushToDevicesDto,
|
@Body() sendBatchToDevicesDto: SendPushToDevicesDto,
|
||||||
|
|||||||
@@ -504,6 +504,34 @@ export class PushNotificationsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新令牌绑定的用户ID
|
||||||
|
*/
|
||||||
|
async updateTokenUserId(userId: string, deviceToken: string): Promise<any> {
|
||||||
|
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: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于设备令牌发送推送通知
|
* 基于设备令牌发送推送通知
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ export class PushTestService implements OnModuleInit {
|
|||||||
return;
|
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 () => {
|
setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -351,4 +351,43 @@ export class PushTokenService {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新令牌绑定的用户ID
|
||||||
|
*/
|
||||||
|
async updateTokenUserId(deviceToken: string, userId: string): Promise<UserPushToken> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user