feat: 支持 push
This commit is contained in:
288
docs/ios-push-implementation-plan.md
Normal file
288
docs/ios-push-implementation-plan.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# iOS远程推送功能实施计划
|
||||
|
||||
## 项目概述
|
||||
|
||||
本文档详细描述了在现有NestJS项目中实现iOS远程推送功能的完整实施计划。该功能将使用Apple官方APNs服务,通过@parse/node-apn库与Apple推送服务进行通信。
|
||||
|
||||
## 技术选型
|
||||
|
||||
### 核心技术栈
|
||||
- **推送服务**: Apple官方APNs (Apple Push Notification service)
|
||||
- **Node.js库**: @parse/node-apn (Trust Score: 9.8,支持HTTP/2)
|
||||
- **认证方式**: Token-based authentication (推荐)
|
||||
- **数据库**: MySQL (与现有项目保持一致)
|
||||
|
||||
### 依赖包
|
||||
```json
|
||||
{
|
||||
"@parse/node-apn": "^5.0.0",
|
||||
"uuid": "^11.1.0" // 已存在
|
||||
}
|
||||
```
|
||||
|
||||
## 实施阶段
|
||||
|
||||
### 第一阶段:基础设施搭建
|
||||
1. **创建推送模块结构**
|
||||
- 创建`src/push-notifications/`目录
|
||||
- 设置模块、控制器、服务的基础结构
|
||||
|
||||
2. **数据库设计与实现**
|
||||
- 创建推送令牌表 (t_user_push_tokens)
|
||||
- 创建推送消息表 (t_push_messages)
|
||||
- 创建推送模板表 (t_push_templates)
|
||||
- 编写数据库迁移脚本
|
||||
|
||||
3. **APNs连接配置**
|
||||
- 配置APNs认证信息
|
||||
- 实现APNs Provider服务
|
||||
- 设置连接池和错误处理
|
||||
|
||||
### 第二阶段:核心功能实现
|
||||
1. **推送令牌管理**
|
||||
- 实现设备令牌注册/更新/注销
|
||||
- 令牌有效性验证
|
||||
- 无效令牌清理机制
|
||||
|
||||
2. **推送消息发送**
|
||||
- 实现单个推送发送
|
||||
- 实现批量推送发送
|
||||
- 实现静默推送发送
|
||||
|
||||
3. **推送模板系统**
|
||||
- 模板创建/更新/删除
|
||||
- 模板渲染引擎
|
||||
- 动态数据绑定
|
||||
|
||||
### 第三阶段:API接口开发
|
||||
1. **推送令牌管理API**
|
||||
- POST /api/push-notifications/register-token
|
||||
- PUT /api/push-notifications/update-token
|
||||
- DELETE /api/push-notifications/unregister-token
|
||||
|
||||
2. **推送消息发送API**
|
||||
- POST /api/push-notifications/send
|
||||
- POST /api/push-notifications/send-by-template
|
||||
- POST /api/push-notifications/send-batch
|
||||
|
||||
3. **推送模板管理API**
|
||||
- GET /api/push-notifications/templates
|
||||
- POST /api/push-notifications/templates
|
||||
- PUT /api/push-notifications/templates/:id
|
||||
- DELETE /api/push-notifications/templates/:id
|
||||
|
||||
### 第四阶段:优化与监控
|
||||
1. **性能优化**
|
||||
- 连接池管理
|
||||
- 批量处理优化
|
||||
- 缓存策略实现
|
||||
|
||||
2. **错误处理与重试**
|
||||
- APNs错误分类处理
|
||||
- 指数退避重试机制
|
||||
- 无效令牌自动清理
|
||||
|
||||
3. **日志与监控**
|
||||
- 推送状态日志记录
|
||||
- 性能指标监控
|
||||
- 错误率统计
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
src/push-notifications/
|
||||
├── push-notifications.module.ts
|
||||
├── push-notifications.controller.ts
|
||||
├── push-notifications.service.ts
|
||||
├── apns.provider.ts
|
||||
├── push-token.service.ts
|
||||
├── push-template.service.ts
|
||||
├── push-message.service.ts
|
||||
├── models/
|
||||
│ ├── user-push-token.model.ts
|
||||
│ ├── push-message.model.ts
|
||||
│ └── push-template.model.ts
|
||||
├── dto/
|
||||
│ ├── register-device-token.dto.ts
|
||||
│ ├── update-device-token.dto.ts
|
||||
│ ├── send-push-notification.dto.ts
|
||||
│ ├── send-push-by-template.dto.ts
|
||||
│ ├── create-push-template.dto.ts
|
||||
│ ├── update-push-template.dto.ts
|
||||
│ └── push-response.dto.ts
|
||||
├── interfaces/
|
||||
│ ├── push-notification.interface.ts
|
||||
│ ├── apns-config.interface.ts
|
||||
│ └── push-stats.interface.ts
|
||||
└── enums/
|
||||
├── device-type.enum.ts
|
||||
├── push-type.enum.ts
|
||||
└── push-message-status.enum.ts
|
||||
```
|
||||
|
||||
## 环境配置
|
||||
|
||||
### 环境变量
|
||||
```bash
|
||||
# APNs配置
|
||||
APNS_KEY_ID=your_key_id
|
||||
APNS_TEAM_ID=your_team_id
|
||||
APNS_KEY_PATH=path/to/APNsAuthKey_XXXXXXXXXX.p8
|
||||
APNS_BUNDLE_ID=com.yourcompany.yourapp
|
||||
APNS_ENVIRONMENT=production # or sandbox
|
||||
|
||||
# 推送服务配置
|
||||
PUSH_RETRY_LIMIT=3
|
||||
PUSH_REQUEST_TIMEOUT=5000
|
||||
PUSH_HEARTBEAT=60000
|
||||
PUSH_BATCH_SIZE=100
|
||||
```
|
||||
|
||||
### APNs认证文件
|
||||
- 需要从Apple开发者账号下载.p8格式的私钥文件
|
||||
- 将私钥文件安全地存储在服务器上
|
||||
|
||||
## 数据库表结构
|
||||
|
||||
### 推送令牌表 (t_user_push_tokens)
|
||||
```sql
|
||||
CREATE TABLE t_user_push_tokens (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
device_token VARCHAR(255) NOT NULL,
|
||||
device_type ENUM('IOS', 'ANDROID') NOT NULL DEFAULT 'IOS',
|
||||
app_version VARCHAR(50),
|
||||
os_version VARCHAR(50),
|
||||
device_name VARCHAR(255),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
last_used_at DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_device_token (device_token),
|
||||
INDEX idx_user_device (user_id, device_token),
|
||||
UNIQUE KEY uk_user_device_token (user_id, device_token)
|
||||
);
|
||||
```
|
||||
|
||||
### 推送消息表 (t_push_messages)
|
||||
```sql
|
||||
CREATE TABLE t_push_messages (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
device_token VARCHAR(255) NOT NULL,
|
||||
message_type VARCHAR(50) NOT NULL,
|
||||
title VARCHAR(255),
|
||||
body TEXT,
|
||||
payload JSON,
|
||||
push_type ENUM('ALERT', 'BACKGROUND', 'VOIP', 'LIVEACTIVITY') DEFAULT 'ALERT',
|
||||
priority TINYINT DEFAULT 10,
|
||||
expiry DATETIME,
|
||||
collapse_id VARCHAR(64),
|
||||
status ENUM('PENDING', 'SENT', 'FAILED', 'EXPIRED') DEFAULT 'PENDING',
|
||||
apns_response JSON,
|
||||
error_message TEXT,
|
||||
sent_at DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_created_at (created_at),
|
||||
INDEX idx_message_type (message_type)
|
||||
);
|
||||
```
|
||||
|
||||
### 推送模板表 (t_push_templates)
|
||||
```sql
|
||||
CREATE TABLE t_push_templates (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
template_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
payload_template JSON,
|
||||
push_type ENUM('ALERT', 'BACKGROUND', 'VOIP', 'LIVEACTIVITY') DEFAULT 'ALERT',
|
||||
priority TINYINT DEFAULT 10,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_template_key (template_key),
|
||||
INDEX idx_is_active (is_active)
|
||||
);
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 注册设备令牌
|
||||
```typescript
|
||||
// iOS客户端获取设备令牌后,调用此API
|
||||
POST /api/push-notifications/register-token
|
||||
{
|
||||
"deviceToken": "a9d0ed10e9cfd022a61cb08753f49c5a0b0dfb383697bf9f9d750a1003da19c7",
|
||||
"deviceType": "IOS",
|
||||
"appVersion": "1.0.0",
|
||||
"osVersion": "iOS 15.0",
|
||||
"deviceName": "iPhone 13"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 发送推送通知
|
||||
```typescript
|
||||
// 在业务服务中调用推送服务
|
||||
await this.pushNotificationsService.sendNotification({
|
||||
userIds: ['user_123'],
|
||||
title: '训练提醒',
|
||||
body: '您今天的普拉提训练还未完成,快来打卡吧!',
|
||||
payload: {
|
||||
type: 'training_reminder',
|
||||
trainingId: 'training_123'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 使用模板发送推送
|
||||
```typescript
|
||||
// 使用预定义模板发送推送
|
||||
await this.pushNotificationsService.sendNotificationByTemplate(
|
||||
'user_123',
|
||||
'training_reminder',
|
||||
{
|
||||
userName: '张三',
|
||||
trainingName: '核心力量训练'
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## 预期收益
|
||||
|
||||
1. **用户体验提升**: 及时推送训练提醒、饮食记录等重要信息
|
||||
2. **用户粘性增强**: 通过个性化推送提高用户活跃度
|
||||
3. **业务目标达成**: 支持各种业务场景的推送需求
|
||||
4. **技术架构完善**: 建立可扩展的推送服务架构
|
||||
|
||||
## 风险评估
|
||||
|
||||
### 技术风险
|
||||
- **APNs连接稳定性**: 通过连接池和重试机制降低风险
|
||||
- **推送令牌管理**: 实现自动清理和验证机制
|
||||
- **性能瓶颈**: 通过批量处理和缓存优化解决
|
||||
|
||||
### 业务风险
|
||||
- **用户隐私**: 严格遵守数据保护法规
|
||||
- **推送频率**: 实现推送频率限制避免骚扰用户
|
||||
- **内容审核**: 建立推送内容审核机制
|
||||
|
||||
## 后续扩展
|
||||
|
||||
1. **多平台支持**: 扩展Android推送功能
|
||||
2. **推送策略**: 实现智能推送时机和内容优化
|
||||
3. **数据分析**: 推送效果分析和用户行为追踪
|
||||
4. **A/B测试**: 推送内容和策略的A/B测试功能
|
||||
|
||||
## 总结
|
||||
|
||||
本实施计划提供了一个完整的iOS远程推送功能解决方案,包括技术选型、架构设计、实施步骤和使用示例。该方案具有良好的可扩展性和维护性,能够满足当前业务需求并为未来扩展留有空间。
|
||||
|
||||
实施完成后,您将拥有一个功能完整、性能优良的推送服务系统,可以通过简单的API调用来发送各种类型的推送通知,提升用户体验和业务指标。
|
||||
265
docs/ios-push-notification-design.md
Normal file
265
docs/ios-push-notification-design.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# iOS远程推送功能设计方案
|
||||
|
||||
## 1. 技术方案概述
|
||||
|
||||
### 1.1 推送服务选择
|
||||
- **服务提供商**: Apple官方APNs (Apple Push Notification service)
|
||||
- **Node.js库**: @parse/node-apn (Trust Score: 9.8,支持HTTP/2,维护良好)
|
||||
- **认证方式**: Token-based authentication (推荐) 或 Certificate-based authentication
|
||||
|
||||
### 1.2 技术架构
|
||||
```
|
||||
iOS App -> APNs -> 后端服务器 (NestJS) -> APNs Provider -> iOS设备
|
||||
```
|
||||
|
||||
### 1.3 核心组件
|
||||
1. **推送令牌管理**: 存储和管理设备推送令牌
|
||||
2. **APNs服务**: 与Apple推送服务通信
|
||||
3. **消息模板系统**: 管理推送消息内容
|
||||
4. **推送日志**: 记录推送状态和结果
|
||||
5. **API接口**: 提供推送令牌注册和推送发送功能
|
||||
|
||||
## 2. 数据库设计
|
||||
|
||||
### 2.1 推送令牌表 (t_user_push_tokens)
|
||||
```sql
|
||||
CREATE TABLE t_user_push_tokens (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
device_token VARCHAR(255) NOT NULL,
|
||||
device_type ENUM('IOS', 'ANDROID') NOT NULL DEFAULT 'IOS',
|
||||
app_version VARCHAR(50),
|
||||
os_version VARCHAR(50),
|
||||
device_name VARCHAR(255),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
last_used_at DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_device_token (device_token),
|
||||
INDEX idx_user_device (user_id, device_token),
|
||||
UNIQUE KEY uk_user_device_token (user_id, device_token)
|
||||
);
|
||||
```
|
||||
|
||||
### 2.2 推送消息表 (t_push_messages)
|
||||
```sql
|
||||
CREATE TABLE t_push_messages (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
device_token VARCHAR(255) NOT NULL,
|
||||
message_type VARCHAR(50) NOT NULL,
|
||||
title VARCHAR(255),
|
||||
body TEXT,
|
||||
payload JSON,
|
||||
push_type ENUM('ALERT', 'BACKGROUND', 'VOIP', 'LIVEACTIVITY') DEFAULT 'ALERT',
|
||||
priority TINYINT DEFAULT 10,
|
||||
expiry DATETIME,
|
||||
collapse_id VARCHAR(64),
|
||||
status ENUM('PENDING', 'SENT', 'FAILED', 'EXPIRED') DEFAULT 'PENDING',
|
||||
apns_response JSON,
|
||||
error_message TEXT,
|
||||
sent_at DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_created_at (created_at),
|
||||
INDEX idx_message_type (message_type)
|
||||
);
|
||||
```
|
||||
|
||||
### 2.3 推送模板表 (t_push_templates)
|
||||
```sql
|
||||
CREATE TABLE t_push_templates (
|
||||
id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
template_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
payload_template JSON,
|
||||
push_type ENUM('ALERT', 'BACKGROUND', 'VOIP', 'LIVEACTIVITY') DEFAULT 'ALERT',
|
||||
priority TINYINT DEFAULT 10,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_template_key (template_key),
|
||||
INDEX idx_is_active (is_active)
|
||||
);
|
||||
```
|
||||
|
||||
## 3. 服务架构设计
|
||||
|
||||
### 3.1 模块结构
|
||||
```
|
||||
src/push-notifications/
|
||||
├── push-notifications.module.ts
|
||||
├── push-notifications.controller.ts
|
||||
├── push-notifications.service.ts
|
||||
├── apns.provider.ts
|
||||
├── models/
|
||||
│ ├── user-push-token.model.ts
|
||||
│ ├── push-message.model.ts
|
||||
│ └── push-template.model.ts
|
||||
├── dto/
|
||||
│ ├── register-device-token.dto.ts
|
||||
│ ├── send-push-notification.dto.ts
|
||||
│ ├── push-template.dto.ts
|
||||
│ └── push-response.dto.ts
|
||||
└── interfaces/
|
||||
├── push-notification.interface.ts
|
||||
└── apns-config.interface.ts
|
||||
```
|
||||
|
||||
### 3.2 核心服务类
|
||||
1. **PushNotificationsService**: 主要业务逻辑服务
|
||||
2. **ApnsProvider**: APNs连接和通信服务
|
||||
3. **PushTokenService**: 推送令牌管理服务
|
||||
4. **PushTemplateService**: 推送模板管理服务
|
||||
|
||||
## 4. API接口设计
|
||||
|
||||
### 4.1 推送令牌管理
|
||||
```
|
||||
POST /api/push-notifications/register-token
|
||||
PUT /api/push-notifications/update-token
|
||||
DELETE /api/push-notifications/unregister-token
|
||||
```
|
||||
|
||||
### 4.2 推送消息发送
|
||||
```
|
||||
POST /api/push-notifications/send
|
||||
POST /api/push-notifications/send-by-template
|
||||
POST /api/push-notifications/send-batch
|
||||
```
|
||||
|
||||
### 4.3 推送模板管理
|
||||
```
|
||||
GET /api/push-notifications/templates
|
||||
POST /api/push-notifications/templates
|
||||
PUT /api/push-notifications/templates/:id
|
||||
DELETE /api/push-notifications/templates/:id
|
||||
```
|
||||
|
||||
## 5. 配置要求
|
||||
|
||||
### 5.1 环境变量
|
||||
```bash
|
||||
# APNs配置
|
||||
APNS_KEY_ID=your_key_id
|
||||
APNS_TEAM_ID=your_team_id
|
||||
APNS_KEY_PATH=path/to/APNsAuthKey_XXXXXXXXXX.p8
|
||||
APNS_BUNDLE_ID=com.yourcompany.yourapp
|
||||
APNS_ENVIRONMENT=production # or sandbox
|
||||
|
||||
# 推送服务配置
|
||||
PUSH_RETRY_LIMIT=3
|
||||
PUSH_REQUEST_TIMEOUT=5000
|
||||
PUSH_HEARTBEAT=60000
|
||||
```
|
||||
|
||||
### 5.2 APNs认证配置
|
||||
- **Token-based认证** (推荐):
|
||||
- Key ID: 从Apple开发者账号获取
|
||||
- Team ID: 从Apple开发者账号获取
|
||||
- 私钥文件: .p8格式的私钥文件
|
||||
|
||||
- **Certificate-based认证**:
|
||||
- 证书文件: .pem格式的证书文件
|
||||
- 私钥文件: .pem格式的私钥文件
|
||||
|
||||
## 6. 推送消息类型
|
||||
|
||||
### 6.1 基础推送类型
|
||||
- **ALERT**: 标准推送通知,显示警告、播放声音或更新应用图标徽章
|
||||
- **BACKGROUND**: 静默推送,不显示用户界面
|
||||
- **VOIP**: VoIP推送,用于实时通信
|
||||
- **LIVEACTIVITY**: 实时活动推送
|
||||
|
||||
### 6.2 业务场景推送
|
||||
- 训练提醒
|
||||
- 饮食记录提醒
|
||||
- 挑战进度通知
|
||||
- 会员到期提醒
|
||||
- 系统通知
|
||||
|
||||
## 7. 错误处理和重试机制
|
||||
|
||||
### 7.1 常见错误处理
|
||||
- **Unregistered**: 设备令牌无效,从数据库中删除
|
||||
- **BadDeviceToken**: 设备令牌格式错误,记录并标记为无效
|
||||
- **DeviceTokenNotForTopic**: 设备令牌与Bundle ID不匹配
|
||||
- **TooManyRequests**: 请求频率过高,实现退避重试
|
||||
- **InternalServerError**: APNs服务器错误,实现重试机制
|
||||
|
||||
### 7.2 重试策略
|
||||
- 指数退避算法
|
||||
- 最大重试次数限制
|
||||
- 不同错误类型的差异化处理
|
||||
|
||||
## 8. 安全考虑
|
||||
|
||||
### 8.1 数据安全
|
||||
- 推送令牌加密存储
|
||||
- 敏感信息脱敏日志
|
||||
- API访问权限控制
|
||||
|
||||
### 8.2 隐私保护
|
||||
- 用户推送偏好设置
|
||||
- 推送内容审核机制
|
||||
- 推送频率限制
|
||||
|
||||
## 9. 监控和日志
|
||||
|
||||
### 9.1 推送监控
|
||||
- 推送成功率统计
|
||||
- 推送延迟监控
|
||||
- 错误率分析
|
||||
|
||||
### 9.2 日志记录
|
||||
- 推送请求日志
|
||||
- APNs响应日志
|
||||
- 错误详情日志
|
||||
|
||||
## 10. 性能优化
|
||||
|
||||
### 10.1 连接管理
|
||||
- HTTP/2连接池
|
||||
- 连接复用
|
||||
- 心跳保活
|
||||
|
||||
### 10.2 批量处理
|
||||
- 批量推送优化
|
||||
- 异步处理机制
|
||||
- 队列管理
|
||||
|
||||
## 11. 测试策略
|
||||
|
||||
### 11.1 单元测试
|
||||
- 服务层逻辑测试
|
||||
- 数据模型测试
|
||||
- 工具函数测试
|
||||
|
||||
### 11.2 集成测试
|
||||
- APNs连接测试
|
||||
- 推送流程测试
|
||||
- 错误处理测试
|
||||
|
||||
### 11.3 端到端测试
|
||||
- 沙盒环境测试
|
||||
- 真机推送测试
|
||||
- 性能压力测试
|
||||
|
||||
## 12. 部署和运维
|
||||
|
||||
### 12.1 环境配置
|
||||
- 开发环境: 使用APNs沙盒环境
|
||||
- 测试环境: 使用APNs沙盒环境
|
||||
- 生产环境: 使用APNs生产环境
|
||||
|
||||
### 12.2 运维监控
|
||||
- 推送服务健康检查
|
||||
- 性能指标监控
|
||||
- 告警机制设置
|
||||
474
docs/push-notifications-usage-guide.md
Normal file
474
docs/push-notifications-usage-guide.md
Normal file
@@ -0,0 +1,474 @@
|
||||
# iOS推送功能使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
本文档详细介绍了如何使用iOS远程推送功能,包括API接口使用、配置说明和代码示例。
|
||||
|
||||
## 环境配置
|
||||
|
||||
### 1. 环境变量配置
|
||||
|
||||
在`.env`文件中添加以下配置:
|
||||
|
||||
```bash
|
||||
# APNs配置
|
||||
APNS_KEY_ID=your_key_id
|
||||
APNS_TEAM_ID=your_team_id
|
||||
APNS_KEY_PATH=path/to/APNsAuthKey_XXXXXXXXXX.p8
|
||||
APNS_BUNDLE_ID=com.yourcompany.yourapp
|
||||
APNS_ENVIRONMENT=production # or sandbox
|
||||
|
||||
# 推送服务配置
|
||||
APNS_CLIENT_COUNT=2
|
||||
APNS_CONNECTION_RETRY_LIMIT=3
|
||||
APNS_HEARTBEAT=60000
|
||||
APNS_REQUEST_TIMEOUT=5000
|
||||
```
|
||||
|
||||
### 2. APNs认证文件
|
||||
|
||||
1. 登录 [Apple Developer Portal](https://developer.apple.com)
|
||||
2. 导航到 "Certificates, Identifiers & Profiles"
|
||||
3. 选择 "Keys"
|
||||
4. 创建新的密钥,并启用 "Apple Push Notifications service"
|
||||
5. 下载`.p8`格式的私钥文件
|
||||
6. 将私钥文件安全地存储在服务器上
|
||||
|
||||
### 3. 数据库迁移
|
||||
|
||||
执行以下SQL脚本创建推送相关的数据表:
|
||||
|
||||
```bash
|
||||
mysql -u username -p database_name < sql-scripts/push-notifications-tables-create.sql
|
||||
```
|
||||
|
||||
## API接口使用
|
||||
|
||||
### 1. 设备令牌管理
|
||||
|
||||
#### 注册设备令牌
|
||||
|
||||
```bash
|
||||
POST /api/push-notifications/register-token
|
||||
Authorization: Bearer <access_token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"deviceToken": "a9d0ed10e9cfd022a61cb08753f49c5a0b0dfb383697bf9f9d750a1003da19c7",
|
||||
"deviceType": "IOS",
|
||||
"appVersion": "1.0.0",
|
||||
"osVersion": "iOS 15.0",
|
||||
"deviceName": "iPhone 13"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "设备令牌注册成功",
|
||||
"data": {
|
||||
"success": true,
|
||||
"tokenId": "uuid-token-id"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 更新设备令牌
|
||||
|
||||
```bash
|
||||
PUT /api/push-notifications/update-token
|
||||
Authorization: Bearer <access_token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"currentDeviceToken": "old-device-token",
|
||||
"newDeviceToken": "new-device-token",
|
||||
"appVersion": "1.0.1",
|
||||
"osVersion": "iOS 15.1"
|
||||
}
|
||||
```
|
||||
|
||||
#### 注销设备令牌
|
||||
|
||||
```bash
|
||||
DELETE /api/push-notifications/unregister-token
|
||||
Authorization: Bearer <access_token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"deviceToken": "device-token-to-unregister"
|
||||
}
|
||||
```
|
||||
|
||||
### 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,
|
||||
"sound": "default",
|
||||
"badge": 1
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "推送发送成功",
|
||||
"data": {
|
||||
"success": true,
|
||||
"sentCount": 2,
|
||||
"failedCount": 0,
|
||||
"results": [
|
||||
{
|
||||
"userId": "user_123",
|
||||
"deviceToken": "device-token-1",
|
||||
"success": true
|
||||
},
|
||||
{
|
||||
"userId": "user_456",
|
||||
"deviceToken": "device-token-2",
|
||||
"success": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 使用模板发送推送
|
||||
|
||||
```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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 批量发送推送
|
||||
|
||||
```bash
|
||||
POST /api/push-notifications/send-batch
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"userIds": ["user_123", "user_456", "user_789"],
|
||||
"title": "系统通知",
|
||||
"body": "系统将于今晚22:00进行维护,请提前保存您的工作。",
|
||||
"payload": {
|
||||
"type": "system_maintenance",
|
||||
"maintenanceTime": "22:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 发送静默推送
|
||||
|
||||
```bash
|
||||
POST /api/push-notifications/send-silent
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"userId": "user_123",
|
||||
"payload": {
|
||||
"type": "data_sync",
|
||||
"syncData": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 推送模板管理
|
||||
|
||||
#### 获取所有模板
|
||||
|
||||
```bash
|
||||
GET /api/push-notifications/templates
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
|
||||
#### 创建推送模板
|
||||
|
||||
```bash
|
||||
POST /api/push-notifications/templates
|
||||
Authorization: Bearer <access_token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"templateKey": "custom_reminder",
|
||||
"title": "自定义提醒",
|
||||
"body": "您好{{userName}},{{reminderContent}}",
|
||||
"payloadTemplate": {
|
||||
"type": "custom_reminder",
|
||||
"reminderId": "{{reminderId}}"
|
||||
},
|
||||
"pushType": "ALERT",
|
||||
"priority": 8
|
||||
}
|
||||
```
|
||||
|
||||
#### 更新推送模板
|
||||
|
||||
```bash
|
||||
PUT /api/push-notifications/templates/:id
|
||||
Authorization: Bearer <access_token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "更新后的标题",
|
||||
"body": "更新后的内容:{{userName}},{{reminderContent}}",
|
||||
"isActive": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 删除推送模板
|
||||
|
||||
```bash
|
||||
DELETE /api/push-notifications/templates/:id
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
|
||||
## 代码示例
|
||||
|
||||
### 1. 在业务服务中使用推送功能
|
||||
|
||||
```typescript
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PushNotificationsService } from '../push-notifications/push-notifications.service';
|
||||
|
||||
@Injectable()
|
||||
export class TrainingService {
|
||||
constructor(
|
||||
private readonly pushNotificationsService: PushNotificationsService,
|
||||
) {}
|
||||
|
||||
async sendTrainingReminder(userId: string, trainingName: string): Promise<void> {
|
||||
// 使用模板发送推送
|
||||
await this.pushNotificationsService.sendNotificationByTemplate({
|
||||
userIds: [userId],
|
||||
templateKey: 'training_reminder',
|
||||
data: {
|
||||
userName: '用户', // 可以从用户服务获取
|
||||
trainingName,
|
||||
},
|
||||
payload: {
|
||||
type: 'training_reminder',
|
||||
trainingId: 'training_123',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async sendWorkoutCompletionNotification(userId: string, workoutName: string, calories: number): Promise<void> {
|
||||
// 直接发送推送
|
||||
await this.pushNotificationsService.sendNotification({
|
||||
userIds: [userId],
|
||||
title: '训练完成',
|
||||
body: `太棒了!您已完成${workoutName}训练,消耗了${calories}卡路里。`,
|
||||
payload: {
|
||||
type: 'workout_completed',
|
||||
workoutId: 'workout_123',
|
||||
calories,
|
||||
},
|
||||
sound: 'celebration.caf',
|
||||
badge: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 在控制器中处理设备令牌注册
|
||||
|
||||
```typescript
|
||||
import { Controller, Post, Body, UseGuards } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation } from '@nestjs/swagger';
|
||||
import { PushNotificationsService } from '../push-notifications/push-notifications.service';
|
||||
import { RegisterDeviceTokenDto } from '../push-notifications/dto/register-device-token.dto';
|
||||
import { CurrentUser } from '../common/decorators/current-user.decorator';
|
||||
import { AccessTokenPayload } from '../users/services/apple-auth.service';
|
||||
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
|
||||
|
||||
@ApiTags('用户设备')
|
||||
@Controller('user/device')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class UserDeviceController {
|
||||
constructor(
|
||||
private readonly pushNotificationsService: PushNotificationsService,
|
||||
) {}
|
||||
|
||||
@Post('register-token')
|
||||
@ApiOperation({ summary: '注册设备推送令牌' })
|
||||
async registerDeviceToken(
|
||||
@CurrentUser() user: AccessTokenPayload,
|
||||
@Body() registerTokenDto: RegisterDeviceTokenDto,
|
||||
) {
|
||||
return this.pushNotificationsService.registerToken(user.sub, registerTokenDto);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 预定义推送模板
|
||||
|
||||
系统已预置以下推送模板:
|
||||
|
||||
### 1. 训练提醒 (training_reminder)
|
||||
- **用途**: 提醒用户完成训练
|
||||
- **变量**: `{{userName}}`, `{{trainingName}}`
|
||||
- **示例**: "您好张三,您今天的核心力量训练还未完成,快来打卡吧!"
|
||||
|
||||
### 2. 饮食记录提醒 (diet_record_reminder)
|
||||
- **用途**: 提醒用户记录饮食
|
||||
- **变量**: `{{userName}}`
|
||||
- **示例**: "您好张三,您还没有记录今天的饮食,记得及时记录哦!"
|
||||
|
||||
### 3. 挑战进度 (challenge_progress)
|
||||
- **用途**: 通知用户挑战进度
|
||||
- **变量**: `{{challengeName}}`, `{{progress}}`
|
||||
- **示例**: "恭喜您!您已完成30天挑战的50%,继续加油!"
|
||||
|
||||
### 4. 会员到期提醒 (membership_expiring)
|
||||
- **用途**: 提醒用户会员即将到期
|
||||
- **变量**: `{{userName}}`, `{{days}}`
|
||||
- **示例**: "您好张三,您的会员将在7天后到期,请及时续费以免影响使用。"
|
||||
|
||||
### 5. 会员已到期 (membership_expired)
|
||||
- **用途**: 通知用户会员已到期
|
||||
- **变量**: `{{userName}}`
|
||||
- **示例**: "您好张三,您的会员已到期,请续费以继续享受会员服务。"
|
||||
|
||||
### 6. 成就解锁 (achievement_unlocked)
|
||||
- **用途**: 庆祝用户解锁成就
|
||||
- **变量**: `{{achievementName}}`
|
||||
- **示例**: "恭喜您解锁了"连续训练7天"成就!"
|
||||
|
||||
### 7. 训练完成 (workout_completed)
|
||||
- **用途**: 确认用户完成训练
|
||||
- **变量**: `{{workoutName}}`, `{{calories}}`
|
||||
- **示例**: "太棒了!您已完成核心力量训练,消耗了150卡路里。"
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 常见错误码
|
||||
|
||||
| 错误码 | 描述 | 解决方案 |
|
||||
|--------|------|----------|
|
||||
| 400 | 请求参数错误 | 检查请求参数格式和必填字段 |
|
||||
| 401 | 未授权访问 | 确保提供了有效的访问令牌 |
|
||||
| 404 | 资源不存在 | 检查用户ID或模板键是否正确 |
|
||||
| 429 | 请求频率过高 | 降低请求频率,实现退避重试 |
|
||||
| 500 | 服务器内部错误 | 检查服务器日志,联系技术支持 |
|
||||
|
||||
### APNs错误处理
|
||||
|
||||
系统会自动处理以下APNs错误:
|
||||
|
||||
- **Unregistered**: 自动停用无效的设备令牌
|
||||
- **BadDeviceToken**: 记录错误并停用令牌
|
||||
- **DeviceTokenNotForTopic**: 记录错误日志
|
||||
- **TooManyRequests**: 实现退避重试机制
|
||||
- **InternalServerError**: 自动重试
|
||||
|
||||
## 监控和日志
|
||||
|
||||
### 1. 推送状态监控
|
||||
|
||||
可以通过以下方式监控推送状态:
|
||||
|
||||
```typescript
|
||||
// 获取推送统计
|
||||
const stats = await this.pushMessageService.getMessageStats();
|
||||
console.log(`推送成功率: ${stats.successRate}%`);
|
||||
console.log(`错误分布:`, stats.errorBreakdown);
|
||||
```
|
||||
|
||||
### 2. 日志查看
|
||||
|
||||
推送相关日志包含以下信息:
|
||||
- 推送请求和响应
|
||||
- APNs连接状态
|
||||
- 错误详情和堆栈跟踪
|
||||
- 性能指标
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 推送时机
|
||||
- 避免在深夜或凌晨发送推送
|
||||
- 根据用户时区调整推送时间
|
||||
- 尊重用户的推送偏好设置
|
||||
|
||||
### 2. 推送内容
|
||||
- 保持推送内容简洁明了
|
||||
- 使用个性化内容提高用户参与度
|
||||
- 避免发送过于频繁的推送
|
||||
|
||||
### 3. 性能优化
|
||||
- 使用批量推送减少网络请求
|
||||
- 实现推送优先级管理
|
||||
- 定期清理无效的设备令牌
|
||||
|
||||
### 4. 安全考虑
|
||||
- 保护用户隐私数据
|
||||
- 实现推送内容审核机制
|
||||
- 使用HTTPS进行API通信
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 1. 推送不生效
|
||||
- 检查APNs配置是否正确
|
||||
- 验证设备令牌是否有效
|
||||
- 确认Bundle ID是否匹配
|
||||
|
||||
### 2. 推送延迟
|
||||
- 检查网络连接状态
|
||||
- 验证APNs服务器状态
|
||||
- 调整推送优先级设置
|
||||
|
||||
### 3. 设备令牌失效
|
||||
- 实现令牌自动更新机制
|
||||
- 定期清理无效令牌
|
||||
- 监控令牌失效率
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 1. 推送统计分析
|
||||
- 实现推送打开率统计
|
||||
- 分析用户行为数据
|
||||
- 优化推送策略
|
||||
|
||||
### 2. A/B测试
|
||||
- 实现推送内容A/B测试
|
||||
- 比较不同推送策略效果
|
||||
- 优化推送转化率
|
||||
|
||||
### 3. 多平台支持
|
||||
- 扩展Android推送功能
|
||||
- 统一推送接口设计
|
||||
- 实现平台特定功能
|
||||
|
||||
## 总结
|
||||
|
||||
iOS推送功能已完全集成到系统中,提供了完整的推送令牌管理、消息发送和模板系统。通过遵循本指南,您可以轻松地在应用中实现各种推送场景,提升用户体验和参与度。
|
||||
|
||||
如有任何问题或需要进一步的技术支持,请参考相关文档或联系开发团队。
|
||||
673
docs/push-service-architecture.md
Normal file
673
docs/push-service-architecture.md
Normal file
@@ -0,0 +1,673 @@
|
||||
# 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<PushResponseDto>
|
||||
|
||||
// 批量发送推送
|
||||
async sendBatchNotifications(userIds: string[], notification: PushNotificationDto): Promise<BatchPushResponseDto>
|
||||
|
||||
// 使用模板发送推送
|
||||
async sendNotificationByTemplate(userId: string, templateKey: string, data: any): Promise<PushResponseDto>
|
||||
|
||||
// 发送静默推送
|
||||
async sendSilentNotification(userId: string, payload: any): Promise<PushResponseDto>
|
||||
}
|
||||
```
|
||||
|
||||
### 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<apn.Results>
|
||||
|
||||
// 批量发送通知
|
||||
async sendBatch(notifications: apn.Notification[], deviceTokens: string[]): Promise<apn.Results>
|
||||
|
||||
// 管理推送通道
|
||||
async manageChannels(notification: apn.Notification, bundleId: string, action: string): Promise<any>
|
||||
|
||||
// 广播实时活动通知
|
||||
async broadcast(notification: apn.Notification, bundleId: string): Promise<any>
|
||||
|
||||
// 关闭连接
|
||||
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<UserPushToken>
|
||||
|
||||
// 更新设备令牌
|
||||
async updateToken(userId: string, tokenData: UpdateDeviceTokenDto): Promise<UserPushToken>
|
||||
|
||||
// 注销设备令牌
|
||||
async unregisterToken(userId: string, deviceToken: string): Promise<void>
|
||||
|
||||
// 获取用户的所有有效令牌
|
||||
async getActiveTokens(userId: string): Promise<UserPushToken[]>
|
||||
|
||||
// 清理无效令牌
|
||||
async cleanupInvalidTokens(): Promise<number>
|
||||
|
||||
// 验证令牌有效性
|
||||
async validateToken(deviceToken: string): Promise<boolean>
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 PushTemplateService
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class PushTemplateService {
|
||||
constructor(
|
||||
@InjectModel(PushTemplate) private readonly templateModel: typeof PushTemplate,
|
||||
) {}
|
||||
|
||||
// 创建推送模板
|
||||
async createTemplate(templateData: CreatePushTemplateDto): Promise<PushTemplate>
|
||||
|
||||
// 更新推送模板
|
||||
async updateTemplate(id: string, templateData: UpdatePushTemplateDto): Promise<PushTemplate>
|
||||
|
||||
// 删除推送模板
|
||||
async deleteTemplate(id: string): Promise<void>
|
||||
|
||||
// 获取模板
|
||||
async getTemplate(templateKey: string): Promise<PushTemplate>
|
||||
|
||||
// 获取所有模板
|
||||
async getAllTemplates(): Promise<PushTemplate[]>
|
||||
|
||||
// 渲染模板
|
||||
async renderTemplate(templateKey: string, data: any): Promise<RenderedTemplate>
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 PushMessageService
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class PushMessageService {
|
||||
constructor(
|
||||
@InjectModel(PushMessage) private readonly messageModel: typeof PushMessage,
|
||||
) {}
|
||||
|
||||
// 创建推送消息记录
|
||||
async createMessage(messageData: CreatePushMessageDto): Promise<PushMessage>
|
||||
|
||||
// 更新消息状态
|
||||
async updateMessageStatus(id: string, status: PushMessageStatus, response?: any): Promise<void>
|
||||
|
||||
// 获取消息历史
|
||||
async getMessageHistory(userId: string, options: QueryOptions): Promise<PushMessage[]>
|
||||
|
||||
// 获取消息统计
|
||||
async getMessageStats(userId?: string, timeRange?: TimeRange): Promise<PushStats>
|
||||
|
||||
// 清理过期消息
|
||||
async cleanupExpiredMessages(): Promise<number>
|
||||
}
|
||||
```
|
||||
|
||||
## 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<ResponseDto> {
|
||||
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<ResponseDto> {
|
||||
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<ResponseDto> {
|
||||
return this.pushNotificationsService.unregisterToken(user.sub, body.deviceToken);
|
||||
}
|
||||
|
||||
// 发送推送通知
|
||||
@Post('send')
|
||||
@ApiOperation({ summary: '发送推送通知' })
|
||||
@ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto })
|
||||
async sendNotification(
|
||||
@Body() sendNotificationDto: SendPushNotificationDto,
|
||||
): Promise<PushResponseDto> {
|
||||
return this.pushNotificationsService.sendNotification(sendNotificationDto);
|
||||
}
|
||||
|
||||
// 使用模板发送推送
|
||||
@Post('send-by-template')
|
||||
@ApiOperation({ summary: '使用模板发送推送' })
|
||||
@ApiResponse({ status: 200, description: '发送成功', type: PushResponseDto })
|
||||
async sendNotificationByTemplate(
|
||||
@Body() sendByTemplateDto: SendPushByTemplateDto,
|
||||
): Promise<PushResponseDto> {
|
||||
return this.pushNotificationsService.sendNotificationByTemplate(sendByTemplateDto);
|
||||
}
|
||||
|
||||
// 批量发送推送
|
||||
@Post('send-batch')
|
||||
@ApiOperation({ summary: '批量发送推送' })
|
||||
@ApiResponse({ status: 200, description: '发送成功', type: BatchPushResponseDto })
|
||||
async sendBatchNotifications(
|
||||
@Body() sendBatchDto: SendPushNotificationDto,
|
||||
): Promise<BatchPushResponseDto> {
|
||||
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<PushTemplate[]> {
|
||||
return this.pushTemplateService.getAllTemplates();
|
||||
}
|
||||
|
||||
// 获取单个模板
|
||||
@Get(':templateKey')
|
||||
@ApiOperation({ summary: '获取推送模板' })
|
||||
@ApiResponse({ status: 200, description: '获取成功', type: PushTemplate })
|
||||
async getTemplate(
|
||||
@Param('templateKey') templateKey: string,
|
||||
): Promise<PushTemplate> {
|
||||
return this.pushTemplateService.getTemplate(templateKey);
|
||||
}
|
||||
|
||||
// 创建模板
|
||||
@Post()
|
||||
@ApiOperation({ summary: '创建推送模板' })
|
||||
@ApiResponse({ status: 201, description: '创建成功', type: PushTemplate })
|
||||
async createTemplate(
|
||||
@Body() createTemplateDto: CreatePushTemplateDto,
|
||||
): Promise<PushTemplate> {
|
||||
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<PushTemplate> {
|
||||
return this.pushTemplateService.updateTemplate(id, updateTemplateDto);
|
||||
}
|
||||
|
||||
// 删除模板
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: '删除推送模板' })
|
||||
@ApiResponse({ status: 200, description: '删除成功' })
|
||||
async deleteTemplate(@Param('id') id: string): Promise<void> {
|
||||
return this.pushTemplateService.deleteTemplate(id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 接口使用示例
|
||||
|
||||
### 5.1 注册设备令牌
|
||||
```bash
|
||||
POST /api/push-notifications/register-token
|
||||
Authorization: Bearer <access_token>
|
||||
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<Response>();
|
||||
|
||||
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 缓存策略
|
||||
- 缓存用户设备令牌减少数据库查询
|
||||
- 缓存推送模板提高渲染性能
|
||||
- 实现分布式缓存支持集群部署
|
||||
Reference in New Issue
Block a user