# iOS推送服务架构和接口设计 ## 1. 服务架构概览 ### 1.1 整体架构图 ```mermaid graph TB A[iOS App] --> B[APNs] B --> C[NestJS Push Service] C --> D[APNs Provider] D --> B C --> E[Push Token Service] C --> F[Push Template Service] C --> G[Push Message Service] C --> H[Database] E --> H F --> H G --> H ``` ### 1.2 模块依赖关系 ```mermaid graph LR A[PushNotificationsModule] --> B[PushNotificationsController] A --> C[PushNotificationsService] A --> D[ApnsProvider] A --> E[PushTokenService] A --> F[PushTemplateService] A --> G[PushMessageService] A --> H[DatabaseModule] A --> I[ConfigModule] ``` ## 2. 核心服务类设计 ### 2.1 PushNotificationsService ```typescript @Injectable() export class PushNotificationsService { constructor( private readonly apnsProvider: ApnsProvider, private readonly pushTokenService: PushTokenService, private readonly pushTemplateService: PushTemplateService, private readonly pushMessageService: PushMessageService, private readonly logger: Logger, ) {} // 发送单个推送 async sendNotification(userId: string, notification: PushNotificationDto): Promise // 批量发送推送 async sendBatchNotifications(userIds: string[], notification: PushNotificationDto): Promise // 使用模板发送推送 async sendNotificationByTemplate(userId: string, templateKey: string, data: any): Promise // 发送静默推送 async sendSilentNotification(userId: string, payload: any): Promise } ``` ### 2.2 ApnsProvider ```typescript @Injectable() export class ApnsProvider { private provider: apn.Provider; private multiProvider: apn.MultiProvider; constructor(private readonly configService: ConfigService) { this.initializeProvider(); } // 初始化APNs连接 private initializeProvider(): void // 发送单个通知 async send(notification: apn.Notification, deviceTokens: string[]): Promise // 批量发送通知 async sendBatch(notifications: apn.Notification[], deviceTokens: string[]): Promise // 管理推送通道 async manageChannels(notification: apn.Notification, bundleId: string, action: string): Promise // 广播实时活动通知 async broadcast(notification: apn.Notification, bundleId: string): Promise // 关闭连接 shutdown(): void } ``` ### 2.3 PushTokenService ```typescript @Injectable() export class PushTokenService { constructor( @InjectModel(UserPushToken) private readonly pushTokenModel: typeof UserPushToken, ) {} // 注册设备令牌 async registerToken(userId: string, tokenData: RegisterDeviceTokenDto): Promise // 更新设备令牌 async updateToken(userId: string, tokenData: UpdateDeviceTokenDto): Promise // 注销设备令牌 async unregisterToken(userId: string, deviceToken: string): Promise // 获取用户的所有有效令牌 async getActiveTokens(userId: string): Promise // 清理无效令牌 async cleanupInvalidTokens(): Promise // 验证令牌有效性 async validateToken(deviceToken: string): Promise } ``` ### 2.4 PushTemplateService ```typescript @Injectable() export class PushTemplateService { constructor( @InjectModel(PushTemplate) private readonly templateModel: typeof PushTemplate, ) {} // 创建推送模板 async createTemplate(templateData: CreatePushTemplateDto): Promise // 更新推送模板 async updateTemplate(id: string, templateData: UpdatePushTemplateDto): Promise // 删除推送模板 async deleteTemplate(id: string): Promise // 获取模板 async getTemplate(templateKey: string): Promise // 获取所有模板 async getAllTemplates(): Promise // 渲染模板 async renderTemplate(templateKey: string, data: any): Promise } ``` ### 2.5 PushMessageService ```typescript @Injectable() export class PushMessageService { constructor( @InjectModel(PushMessage) private readonly messageModel: typeof PushMessage, ) {} // 创建推送消息记录 async createMessage(messageData: CreatePushMessageDto): Promise // 更新消息状态 async updateMessageStatus(id: string, status: PushMessageStatus, response?: any): Promise // 获取消息历史 async getMessageHistory(userId: string, options: QueryOptions): Promise // 获取消息统计 async getMessageStats(userId?: string, timeRange?: TimeRange): Promise // 清理过期消息 async cleanupExpiredMessages(): Promise } ``` ## 3. 数据传输对象(DTO)设计 ### 3.1 推送令牌相关DTO ```typescript // 注册设备令牌 export class RegisterDeviceTokenDto { @ApiProperty({ description: '设备推送令牌' }) @IsString() @IsNotEmpty() deviceToken: string; @ApiProperty({ description: '设备类型', enum: DeviceType }) @IsEnum(DeviceType) deviceType: DeviceType; @ApiProperty({ description: '应用版本', required: false }) @IsString() @IsOptional() appVersion?: string; @ApiProperty({ description: '操作系统版本', required: false }) @IsString() @IsOptional() osVersion?: string; @ApiProperty({ description: '设备名称', required: false }) @IsString() @IsOptional() deviceName?: string; } // 更新设备令牌 export class UpdateDeviceTokenDto { @ApiProperty({ description: '新的设备推送令牌' }) @IsString() @IsNotEmpty() newDeviceToken: string; @ApiProperty({ description: '应用版本', required: false }) @IsString() @IsOptional() appVersion?: string; @ApiProperty({ description: '操作系统版本', required: false }) @IsString() @IsOptional() osVersion?: string; @ApiProperty({ description: '设备名称', required: false }) @IsString() @IsOptional() deviceName?: string; } ``` ### 3.2 推送消息相关DTO ```typescript // 发送推送通知 export class SendPushNotificationDto { @ApiProperty({ description: '用户ID列表' }) @IsArray() @IsString({ each: true }) userIds: string[]; @ApiProperty({ description: '推送标题' }) @IsString() @IsNotEmpty() title: string; @ApiProperty({ description: '推送内容' }) @IsString() @IsNotEmpty() body: string; @ApiProperty({ description: '自定义数据', required: false }) @IsObject() @IsOptional() payload?: any; @ApiProperty({ description: '推送类型', enum: PushType, required: false }) @IsEnum(PushType) @IsOptional() pushType?: PushType; @ApiProperty({ description: '优先级', required: false }) @IsNumber() @IsOptional() priority?: number; @ApiProperty({ description: '过期时间(秒)', required: false }) @IsNumber() @IsOptional() expiry?: number; @ApiProperty({ description: '折叠ID', required: false }) @IsString() @IsOptional() collapseId?: string; } // 使用模板发送推送 export class SendPushByTemplateDto { @ApiProperty({ description: '用户ID列表' }) @IsArray() @IsString({ each: true }) userIds: string[]; @ApiProperty({ description: '模板键' }) @IsString() @IsNotEmpty() templateKey: string; @ApiProperty({ description: '模板数据' }) @IsObject() @IsNotEmpty() data: any; @ApiProperty({ description: '自定义数据', required: false }) @IsObject() @IsOptional() payload?: any; } ``` ### 3.3 推送模板相关DTO ```typescript // 创建推送模板 export class CreatePushTemplateDto { @ApiProperty({ description: '模板键' }) @IsString() @IsNotEmpty() templateKey: string; @ApiProperty({ description: '模板标题' }) @IsString() @IsNotEmpty() title: string; @ApiProperty({ description: '模板内容' }) @IsString() @IsNotEmpty() body: string; @ApiProperty({ description: '负载模板', required: false }) @IsObject() @IsOptional() payloadTemplate?: any; @ApiProperty({ description: '推送类型', enum: PushType, required: false }) @IsEnum(PushType) @IsOptional() pushType?: PushType; @ApiProperty({ description: '优先级', required: false }) @IsNumber() @IsOptional() priority?: number; } // 更新推送模板 export class UpdatePushTemplateDto { @ApiProperty({ description: '模板标题', required: false }) @IsString() @IsOptional() title?: string; @ApiProperty({ description: '模板内容', required: false }) @IsString() @IsOptional() body?: string; @ApiProperty({ description: '负载模板', required: false }) @IsObject() @IsOptional() payloadTemplate?: any; @ApiProperty({ description: '推送类型', enum: PushType, required: false }) @IsEnum(PushType) @IsOptional() pushType?: PushType; @ApiProperty({ description: '优先级', required: false }) @IsNumber() @IsOptional() priority?: number; @ApiProperty({ description: '是否激活', required: false }) @IsBoolean() @IsOptional() isActive?: boolean; } ``` ### 3.4 响应DTO ```typescript // 推送响应 export class PushResponseDto { @ApiProperty({ description: '响应代码' }) code: ResponseCode; @ApiProperty({ description: '响应消息' }) message: string; @ApiProperty({ description: '推送结果' }) data: { success: boolean; sentCount: number; failedCount: number; results: PushResult[]; }; } // 批量推送响应 export class BatchPushResponseDto { @ApiProperty({ description: '响应代码' }) code: ResponseCode; @ApiProperty({ description: '响应消息' }) message: string; @ApiProperty({ description: '批量推送结果' }) data: { totalUsers: number; totalTokens: number; successCount: number; failedCount: number; results: PushResult[]; }; } // 推送结果 export class PushResult { @ApiProperty({ description: '用户ID' }) userId: string; @ApiProperty({ description: '设备令牌' }) deviceToken: string; @ApiProperty({ description: '是否成功' }) success: boolean; @ApiProperty({ description: '错误信息', required: false }) @IsString() @IsOptional() error?: string; @ApiProperty({ description: 'APNs响应', required: false }) @IsObject() @IsOptional() apnsResponse?: any; } ``` ## 4. 控制器接口设计 ### 4.1 PushNotificationsController ```typescript @Controller('push-notifications') @ApiTags('推送通知') @UseGuards(JwtAuthGuard) export class PushNotificationsController { constructor(private readonly pushNotificationsService: PushNotificationsService) {} // 注册设备令牌 @Post('register-token') @ApiOperation({ summary: '注册设备推送令牌' }) @ApiResponse({ status: 200, description: '注册成功', type: ResponseDto }) async registerToken( @CurrentUser() user: AccessTokenPayload, @Body() registerTokenDto: RegisterDeviceTokenDto, ): Promise { return this.pushNotificationsService.registerToken(user.sub, registerTokenDto); } // 更新设备令牌 @Put('update-token') @ApiOperation({ summary: '更新设备推送令牌' }) @ApiResponse({ status: 200, description: '更新成功', type: ResponseDto }) async updateToken( @CurrentUser() user: AccessTokenPayload, @Body() updateTokenDto: UpdateDeviceTokenDto, ): Promise { return this.pushNotificationsService.updateToken(user.sub, updateTokenDto); } // 注销设备令牌 @Delete('unregister-token') @ApiOperation({ summary: '注销设备推送令牌' }) @ApiResponse({ status: 200, description: '注销成功', type: ResponseDto }) async unregisterToken( @CurrentUser() user: AccessTokenPayload, @Body() body: { deviceToken: string }, ): Promise { return this.pushNotificationsService.unregisterToken(user.sub, body.deviceToken); } // 发送推送通知 @Post('send') @ApiOperation({ summary: '发送推送通知' }) @ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto }) async sendNotification( @Body() sendNotificationDto: SendPushNotificationDto, ): Promise { return this.pushNotificationsService.sendNotification(sendNotificationDto); } // 使用模板发送推送 @Post('send-by-template') @ApiOperation({ summary: '使用模板发送推送' }) @ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto }) async sendNotificationByTemplate( @Body() sendByTemplateDto: SendPushByTemplateDto, ): Promise { return this.pushNotificationsService.sendNotificationByTemplate(sendByTemplateDto); } // 批量发送推送 @Post('send-batch') @ApiOperation({ summary: '批量发送推送' }) @ApiResponse({ status: 200, description: '发送成功', type: BatchPushResponseDto }) async sendBatchNotifications( @Body() sendBatchDto: SendPushNotificationDto, ): Promise { return this.pushNotificationsService.sendBatchNotifications(sendBatchDto); } } ``` ### 4.2 PushTemplateController ```typescript @Controller('push-notifications/templates') @ApiTags('推送模板') @UseGuards(JwtAuthGuard) export class PushTemplateController { constructor(private readonly pushTemplateService: PushTemplateService) {} // 获取所有模板 @Get() @ApiOperation({ summary: '获取所有推送模板' }) @ApiResponse({ status: 200, description: '获取成功', type: [PushTemplate] }) async getAllTemplates(): Promise { return this.pushTemplateService.getAllTemplates(); } // 获取单个模板 @Get(':templateKey') @ApiOperation({ summary: '获取推送模板' }) @ApiResponse({ status: 200, description: '获取成功', type: PushTemplate }) async getTemplate( @Param('templateKey') templateKey: string, ): Promise { return this.pushTemplateService.getTemplate(templateKey); } // 创建模板 @Post() @ApiOperation({ summary: '创建推送模板' }) @ApiResponse({ status: 201, description: '创建成功', type: PushTemplate }) async createTemplate( @Body() createTemplateDto: CreatePushTemplateDto, ): Promise { return this.pushTemplateService.createTemplate(createTemplateDto); } // 更新模板 @Put(':id') @ApiOperation({ summary: '更新推送模板' }) @ApiResponse({ status: 200, description: '更新成功', type: PushTemplate }) async updateTemplate( @Param('id') id: string, @Body() updateTemplateDto: UpdatePushTemplateDto, ): Promise { return this.pushTemplateService.updateTemplate(id, updateTemplateDto); } // 删除模板 @Delete(':id') @ApiOperation({ summary: '删除推送模板' }) @ApiResponse({ status: 200, description: '删除成功' }) async deleteTemplate(@Param('id') id: string): Promise { return this.pushTemplateService.deleteTemplate(id); } } ``` ## 5. 接口使用示例 ### 5.1 注册设备令牌 ```bash POST /api/push-notifications/register-token Authorization: Bearer Content-Type: application/json { "deviceToken": "a9d0ed10e9cfd022a61cb08753f49c5a0b0dfb383697bf9f9d750a1003da19c7", "deviceType": "IOS", "appVersion": "1.0.0", "osVersion": "iOS 15.0", "deviceName": "iPhone 13" } ``` ### 5.2 发送推送通知 ```bash POST /api/push-notifications/send Content-Type: application/json { "userIds": ["user_123", "user_456"], "title": "训练提醒", "body": "您今天的普拉提训练还未完成,快来打卡吧!", "payload": { "type": "training_reminder", "trainingId": "training_123" }, "pushType": "ALERT", "priority": 10 } ``` ### 5.3 使用模板发送推送 ```bash POST /api/push-notifications/send-by-template Content-Type: application/json { "userIds": ["user_123"], "templateKey": "training_reminder", "data": { "userName": "张三", "trainingName": "核心力量训练" }, "payload": { "type": "training_reminder", "trainingId": "training_123" } } ``` ## 6. 错误处理 ### 6.1 错误类型定义 ```typescript export enum PushErrorCode { INVALID_DEVICE_TOKEN = 'INVALID_DEVICE_TOKEN', DEVICE_TOKEN_NOT_FOR_TOPIC = 'DEVICE_TOKEN_NOT_FOR_TOPIC', TOO_MANY_REQUESTS = 'TOO_MANY_REQUESTS', INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR', TEMPLATE_NOT_FOUND = 'TEMPLATE_NOT_FOUND', USER_NOT_FOUND = 'USER_NOT_FOUND', INVALID_PAYLOAD = 'INVALID_PAYLOAD', } export class PushException extends HttpException { constructor( errorCode: PushErrorCode, message: string, statusCode: HttpStatus = HttpStatus.BAD_REQUEST, ) { super({ code: errorCode, message }, statusCode); } } ``` ### 6.2 全局异常处理 ```typescript @Catch(PushException, Error) export class PushExceptionFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); if (exception instanceof PushException) { response.status(exception.getStatus()).json(exception.getResponse()); } else { response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ code: 'INTERNAL_SERVER_ERROR', message: '推送服务内部错误', }); } } } ``` ## 7. 性能优化策略 ### 7.1 连接池管理 - 使用HTTP/2连接池提高并发性能 - 实现连接复用和心跳保活 - 动态调整连接池大小 ### 7.2 批量处理优化 - 实现批量推送减少网络请求 - 使用队列系统处理大量推送请求 - 实现推送优先级和限流机制 ### 7.3 缓存策略 - 缓存用户设备令牌减少数据库查询 - 缓存推送模板提高渲染性能 - 实现分布式缓存支持集群部署