新增公共房间管理器 PublicRoomManager,负责管理用户进入公共聊天房间的逻辑;在 ClientConfig.m 中添加对公共房间管理器的配置更新通知;在多个文件中集成公共房间管理器,确保用户信息更新和状态管理的正确性;更新相关文档以提供使用指南和集成说明。
This commit is contained in:
422
docs/AttachmentModel_Analysis_Report.md
Normal file
422
docs/AttachmentModel_Analysis_Report.md
Normal file
@@ -0,0 +1,422 @@
|
||||
# AttachmentModel 功能分析报告
|
||||
|
||||
## 目录
|
||||
- [1. 概述](#1-概述)
|
||||
- [2. AttachmentModel 核心结构](#2-attachmentmodel-核心结构)
|
||||
- [3. 消息类型分类](#3-消息类型分类)
|
||||
- [4. 使用场景分析](#4-使用场景分析)
|
||||
- [5. 功能模块分布](#5-功能模块分布)
|
||||
- [6. 关键实现细节](#6-关键实现细节)
|
||||
- [7. 最佳实践](#7-最佳实践)
|
||||
- [8. 总结](#8-总结)
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 定义
|
||||
`AttachmentModel` 是YuMi项目中用于处理NIMSDK自定义消息的核心数据模型,它实现了`NIMCustomAttachment`协议,用于在云信SDK中传输和处理各种自定义消息类型。
|
||||
|
||||
### 1.2 核心作用
|
||||
- **消息类型标识**: 通过`first`和`second`字段标识不同的消息类型和子类型
|
||||
- **数据承载**: 通过`data`字段承载具体的消息内容
|
||||
- **消息解析**: 配合`CustomAttachmentDecoder`进行消息的编码和解码
|
||||
- **业务扩展**: 支持各种业务场景的自定义消息处理
|
||||
|
||||
### 1.3 设计特点
|
||||
- **类型安全**: 使用枚举定义所有消息类型,避免硬编码
|
||||
- **扩展性强**: 支持新增消息类型而不影响现有代码
|
||||
- **统一接口**: 所有自定义消息都通过统一的接口处理
|
||||
- **数据灵活**: `data`字段支持任意类型的数据结构
|
||||
|
||||
## 2. AttachmentModel 核心结构
|
||||
|
||||
### 2.1 基础属性
|
||||
|
||||
```objc
|
||||
@interface AttachmentModel : PIBaseModel<NIMCustomAttachment>
|
||||
|
||||
@property (nonatomic, strong) id data; // 消息数据内容
|
||||
@property (nonatomic, assign) int first; // 消息类型标识
|
||||
@property (nonatomic, assign) int second; // 消息子类型标识
|
||||
@property (nonatomic, assign) BOOL isBroadcast; // 是否为广播消息
|
||||
@property (nonatomic, assign) NSInteger seq; // 本地序号,用于消息排序
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
### 2.2 编码实现
|
||||
|
||||
```objc
|
||||
- (NSString *)encodeAttachment {
|
||||
return [self toJSONString];
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 解码实现
|
||||
|
||||
```objc
|
||||
// CustomAttachmentDecoder.m
|
||||
- (id<NIMCustomAttachment>)decodeAttachment:(NSString *)content {
|
||||
id<NIMCustomAttachment> attachment;
|
||||
NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
if (data) {
|
||||
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data
|
||||
options:0
|
||||
error:nil];
|
||||
if ([dict isKindOfClass:[NSDictionary class]]) {
|
||||
int first = [dict[@"first"] intValue];
|
||||
int second = [dict[@"second"] intValue];
|
||||
id originalData = dict[@"data"];
|
||||
|
||||
AttachmentModel *model = [[AttachmentModel alloc] init];
|
||||
model.first = (short)first;
|
||||
model.second = (short)second;
|
||||
model.data = originalData;
|
||||
|
||||
attachment = model;
|
||||
}
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 消息类型分类
|
||||
|
||||
### 3.1 主要消息类型 (first字段)
|
||||
|
||||
#### 3.1.1 基础功能类
|
||||
| 类型值 | 类型名称 | 功能描述 |
|
||||
|--------|----------|----------|
|
||||
| 2 | `CustomMessageType_Room_Tip` | 房间提示消息 |
|
||||
| 3 | `CustomMessageType_Gift` | 礼物相关消息 |
|
||||
| 5 | `CustomMessageType_Account` | 账户更新消息 |
|
||||
| 6 | `CustomMessageType_Member_Online` | 关注主播上线通知 |
|
||||
| 8 | `CustomMessageType_Queue` | 队列操作消息 |
|
||||
| 9 | `CustomMessageType_Face` | 表情消息 |
|
||||
| 10 | `CustomMessageType_Tweet` | 推文消息 |
|
||||
| 12 | `CustomMessageType_AllMicroSend` | 全麦送礼物 |
|
||||
|
||||
#### 3.1.2 房间管理类
|
||||
| 类型值 | 类型名称 | 功能描述 |
|
||||
|--------|----------|----------|
|
||||
| 15 | `CustomMessageType_Car_Notify` | 座驾相关通知 |
|
||||
| 18 | `CustomMessageType_Kick_User` | 踢出房间消息 |
|
||||
| 20 | `CustomMessageType_Update_RoomInfo` | 房间信息更新 |
|
||||
| 30 | `CustomMessageType_Arrange_Mic` | 排麦相关消息 |
|
||||
| 31 | `CustomMessageType_Room_PK` | 房间内PK消息 |
|
||||
| 42 | `CustomMessageType_Room_GiftValue` | 房间礼物值同步 |
|
||||
|
||||
#### 3.1.3 社交功能类
|
||||
| 类型值 | 类型名称 | 功能描述 |
|
||||
|--------|----------|----------|
|
||||
| 19 | `CustomMessageType_Secretary` | 小秘书消息 |
|
||||
| 22 | `CustomMessageType_Application_Share` | 应用内分享 |
|
||||
| 52 | `CustomMessageType_Monents` | 动态相关消息 |
|
||||
| 60 | `CustomMessageType_RedPacket` | 红包相关消息 |
|
||||
| 62 | `CustomMessageType_FindNew` | 发现萌新消息 |
|
||||
| 64 | `CustomMessageType_CP` | CP礼物消息 |
|
||||
|
||||
#### 3.1.4 游戏娱乐类
|
||||
| 类型值 | 类型名称 | 功能描述 |
|
||||
|--------|----------|----------|
|
||||
| 26 | `CustomMessageType_Candy_Tree` | 糖果树消息 |
|
||||
| 63 | `CustomMessageType_RoomBoom` | 房间火箭消息 |
|
||||
| 71 | `CustomMessageType_Tarot` | 塔罗牌消息 |
|
||||
| 72 | `CustomMessageType_RoomPlay_Dating` | 相亲游戏消息 |
|
||||
| 81 | `CustomMessageType_Room_Sailing` | 航海游戏消息 |
|
||||
| 83 | `CustomMessageType_Across_Room_PK` | 跨房PK消息 |
|
||||
| 97 | `CustomMessageType_Treasure_Fairy` | 精灵密藏消息 |
|
||||
|
||||
#### 3.1.5 系统通知类
|
||||
| 类型值 | 类型名称 | 功能描述 |
|
||||
|--------|----------|----------|
|
||||
| 23 | `CustomMessageType_Message_Handle` | 系统通知消息 |
|
||||
| 24 | `CustomMessageType_User_UpGrade` | 用户升级消息 |
|
||||
| 49 | `CustomMessageType_Version_Update` | 版本升级消息 |
|
||||
| 75 | `CustomMessageType_Chat_Risk_Alert` | 私聊风险提醒 |
|
||||
| 76 | `CustomMessageType_First_Recharge_Reward` | 首充奖励消息 |
|
||||
| 78 | `CustomMessageType_First_VisitorRecord` | 访客记录消息 |
|
||||
| 92 | `CustomMessageType_Task_Complete` | 任务完成通知 |
|
||||
|
||||
### 3.2 子类型示例 (second字段)
|
||||
|
||||
#### 3.2.1 礼物消息子类型
|
||||
```objc
|
||||
typedef NS_ENUM(NSUInteger, CustomMessageSubGift) {
|
||||
Custom_Message_Sub_Gift_Send = 31, // 发送礼物
|
||||
Custom_Message_Sub_Gift_ChannelNotify = 32, // 全服发送礼物
|
||||
Custom_Message_Sub_Gift_LuckySend = 34, // 发送福袋礼物
|
||||
Custom_Message_Sub_Gift_EmbeddedStyle = 35, // 发送嵌入式礼物
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.2.2 红包消息子类型
|
||||
```objc
|
||||
typedef NS_ENUM(NSUInteger, CustomMessageSubRedPacket) {
|
||||
Custom_Message_Sub_RoomGiftRedPacket = 601, // 房间礼物红包
|
||||
Custom_Message_Sub_RoomDiamandRedPacket = 602, // 房间钻石红包
|
||||
Custom_Message_Sub_AllGiftRedPacket = 603, // 全服礼物红包
|
||||
Custom_Message_Sub_AllDiamandRedPacket = 604, // 全服钻石红包
|
||||
Custom_Message_Sub_OpenRedPacketSuccess = 605, // 抢红包成功
|
||||
Custom_Message_Sub_NewRoomDiamandRedPacket = 606, // 新版本房间钻石红包
|
||||
Custom_Message_Sub_LuckyPackage = 607, // 最新版本房间红包推送
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.2.3 房间PK子类型
|
||||
```objc
|
||||
typedef NS_ENUM(NSUInteger, CustomMessageSubRoomPK) {
|
||||
Custom_Message_Sub_Room_PK_Non_Empty = 311, // 从无人报名pk排麦到有人报名pk排麦
|
||||
Custom_Message_Sub_Room_PK_Empty = 312, // 从有人报名pk排麦到无人报名pk排麦
|
||||
Custom_Message_Sub_Room_PK_Mode_Open = 313, // 创建了pk模式
|
||||
Custom_Message_Sub_Room_PK_Mode_Close = 314, // 关闭pk模式
|
||||
Custom_Message_Sub_Room_PK_Start = 315, // pk开始
|
||||
Custom_Message_Sub_Room_PK_Result = 316, // pk结果
|
||||
Custom_Message_Sub_Room_PK_Re_Start = 317, // 重新开始
|
||||
Custom_Message_Sub_Room_PK_Manager_Up_Mic = 318, // 管理员邀请上麦
|
||||
};
|
||||
```
|
||||
|
||||
## 4. 使用场景分析
|
||||
|
||||
### 4.1 消息接收处理
|
||||
|
||||
#### 4.1.1 私聊消息处理
|
||||
```objc
|
||||
// TabbarViewController.m
|
||||
- (void)onRecvMessages:(NSArray<NIMMessage *> *)messages {
|
||||
for (NIMMessage *message in messages) {
|
||||
if (message.session.sessionType == NIMSessionTypeP2P) {
|
||||
if (message.messageType == NIMMessageTypeCustom) {
|
||||
NIMCustomObject *obj = (NIMCustomObject *)message.messageObject;
|
||||
if (obj.attachment != nil && [obj.attachment isKindOfClass:[AttachmentModel class]]) {
|
||||
AttachmentModel *attachment = (AttachmentModel *)obj.attachment;
|
||||
|
||||
// 处理发现萌新打招呼消息
|
||||
if (attachment.first == CustomMessageType_FindNew &&
|
||||
attachment.second == Custom_Message_Find_New_Greet_New_User) {
|
||||
FindNewGreetMessageModel *greetInfo = [FindNewGreetMessageModel modelWithDictionary:attachment.data];
|
||||
// 显示打招呼弹窗
|
||||
[self showGreetAlert:greetInfo];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.1.2 广播消息处理
|
||||
```objc
|
||||
// TabbarViewController.m
|
||||
- (void)onReceiveBroadcastMessage:(NIMBroadcastMessage *)broadcastMessage {
|
||||
if (broadcastMessage.content) {
|
||||
NSDictionary *msgDictionary = [broadcastMessage.content toJSONObject];
|
||||
AttachmentModel *attachment = [AttachmentModel modelWithJSON:msgDictionary[@"body"]];
|
||||
|
||||
// 处理红包消息
|
||||
if (attachment.first == CustomMessageType_RedPacket) {
|
||||
[self receiveRedPacketDealWithData:attachment];
|
||||
}
|
||||
// 处理版本更新消息
|
||||
else if (attachment.first == CustomMessageType_Version_Update &&
|
||||
attachment.second == Custom_Message_Version_Update_Value) {
|
||||
[self handleVersionUpdate:attachment];
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 消息发送
|
||||
|
||||
#### 4.2.1 创建自定义消息
|
||||
```objc
|
||||
// 创建礼物消息
|
||||
AttachmentModel *attachment = [[AttachmentModel alloc] init];
|
||||
attachment.first = CustomMessageType_Gift;
|
||||
attachment.second = Custom_Message_Sub_Gift_Send;
|
||||
attachment.data = @{
|
||||
@"giftId": @"123",
|
||||
@"giftName": @"玫瑰花",
|
||||
@"giftCount": @1,
|
||||
@"senderId": @"user123",
|
||||
@"receiverId": @"user456"
|
||||
};
|
||||
|
||||
// 创建NIM消息
|
||||
NIMCustomObject *customObject = [[NIMCustomObject alloc] init];
|
||||
customObject.attachment = attachment;
|
||||
NIMMessage *message = [[NIMMessage alloc] init];
|
||||
message.messageObject = customObject;
|
||||
```
|
||||
|
||||
#### 4.2.2 发送消息
|
||||
```objc
|
||||
// 发送到指定会话
|
||||
NIMSession *session = [NIMSession session:@"receiverId" type:NIMSessionTypeP2P];
|
||||
[[NIMSDK sharedSDK].chatManager sendMessage:message toSession:session error:nil];
|
||||
```
|
||||
|
||||
### 4.3 消息内容显示
|
||||
|
||||
#### 4.3.1 消息内容解析
|
||||
```objc
|
||||
// NIMMessageUtils.m
|
||||
+ (NSString *)messageContent:(NIMMessage*)message {
|
||||
switch (message.messageType) {
|
||||
case NIMMessageTypeCustom: {
|
||||
NIMCustomObject *obj = (NIMCustomObject *)message.messageObject;
|
||||
AttachmentModel *attachment = (AttachmentModel *)obj.attachment;
|
||||
|
||||
if (attachment.first == CustomMessageType_Secretary) {
|
||||
if (attachment.second == Custom_Message_Sub_Secretary_Router) {
|
||||
return attachment.data[@"title"];
|
||||
}
|
||||
} else if (attachment.first == CustomMessageType_Gift) {
|
||||
if (attachment.second == Custom_Message_Sub_Gift_Send) {
|
||||
return YMLocalizedString(@"NIMMessageUtils5"); // "发送了礼物"
|
||||
}
|
||||
} else if (attachment.first == CustomMessageType_FindNew &&
|
||||
attachment.second == Custom_Message_Find_New_Greet_New_User) {
|
||||
NSString *text = attachment.data[@"message"];
|
||||
return text.length > 0 ? text : YMLocalizedString(@"NIMMessageUtils11");
|
||||
}
|
||||
// ... 其他消息类型处理
|
||||
}
|
||||
break;
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 功能模块分布
|
||||
|
||||
### 5.1 消息模块 (YMMessage)
|
||||
- **文件**: `AttachmentModel.h/m`, `CustomAttachmentDecoder.h/m`
|
||||
- **功能**: 消息模型定义、解码器实现
|
||||
- **使用**: 所有自定义消息的基础结构
|
||||
|
||||
### 5.2 主界面模块 (YMTabbar)
|
||||
- **文件**: `TabbarViewController.m`
|
||||
- **功能**: 全局消息接收处理、广播消息处理
|
||||
- **使用**: 处理发现萌新、红包、版本更新等全局消息
|
||||
|
||||
### 5.3 房间模块 (YMRoom)
|
||||
- **文件**: 多个房间相关文件
|
||||
- **功能**: 房间内消息处理、PK、礼物、火箭等
|
||||
- **使用**: 处理房间内的各种互动消息
|
||||
|
||||
### 5.4 动态模块 (YMMonents)
|
||||
- **文件**: `XPMomentsViewController.m`
|
||||
- **功能**: 动态相关消息处理
|
||||
- **使用**: 处理动态分享、审核等消息
|
||||
|
||||
### 5.5 个人中心模块 (YMMine)
|
||||
- **文件**: 多个个人中心相关文件
|
||||
- **功能**: 个人相关消息处理
|
||||
- **使用**: 处理VIP、粉丝团、任务等个人消息
|
||||
|
||||
## 6. 关键实现细节
|
||||
|
||||
### 6.1 消息类型判断
|
||||
```objc
|
||||
// 判断是否为特定类型的消息
|
||||
if (attachment.first == CustomMessageType_Gift &&
|
||||
attachment.second == Custom_Message_Sub_Gift_Send) {
|
||||
// 处理发送礼物消息
|
||||
}
|
||||
|
||||
// 判断是否为系统消息
|
||||
if (attachment.first == CustomMessageType_System_message) {
|
||||
// 处理系统消息
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 数据解析
|
||||
```objc
|
||||
// 从data字段解析具体数据
|
||||
NSDictionary *giftData = attachment.data;
|
||||
NSString *giftId = giftData[@"giftId"];
|
||||
NSString *giftName = giftData[@"giftName"];
|
||||
NSNumber *giftCount = giftData[@"giftCount"];
|
||||
|
||||
// 使用MJExtension进行模型转换
|
||||
GiftModel *giftModel = [GiftModel modelWithDictionary:attachment.data];
|
||||
```
|
||||
|
||||
### 6.3 消息过滤
|
||||
```objc
|
||||
// 根据分区ID过滤消息
|
||||
NSString *partitionId = [NSString stringWithFormat:@"%@", attachment.data[@"partitionId"]];
|
||||
if (![partitionId isEqualToString:self.userInfo.partitionId]) {
|
||||
return; // 不是当前分区的消息,忽略
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 消息排序
|
||||
```objc
|
||||
// 使用seq字段进行消息排序
|
||||
@property (nonatomic, assign) NSInteger seq; // 本地序号,用于将一条消息分解为多条有次序的消息
|
||||
```
|
||||
|
||||
## 7. 最佳实践
|
||||
|
||||
### 7.1 消息类型定义
|
||||
1. **使用枚举**: 避免硬编码数字,提高代码可读性
|
||||
2. **分类管理**: 按功能模块分类管理消息类型
|
||||
3. **版本兼容**: 新增消息类型时保持向后兼容
|
||||
|
||||
### 7.2 消息处理
|
||||
1. **类型检查**: 在处理消息前先检查类型
|
||||
2. **数据验证**: 验证data字段的数据完整性
|
||||
3. **错误处理**: 对解析失败的消息进行适当处理
|
||||
|
||||
### 7.3 性能优化
|
||||
1. **消息过滤**: 根据业务需求过滤不需要的消息
|
||||
2. **内存管理**: 及时释放不需要的消息对象
|
||||
3. **批量处理**: 对大量消息进行批量处理
|
||||
|
||||
### 7.4 扩展性设计
|
||||
1. **模块化**: 按功能模块组织消息处理逻辑
|
||||
2. **插件化**: 支持新增消息类型而不影响现有代码
|
||||
3. **配置化**: 通过配置文件管理消息类型
|
||||
|
||||
## 8. 总结
|
||||
|
||||
### 8.1 核心价值
|
||||
`AttachmentModel` 作为YuMi项目的消息处理核心,具有以下价值:
|
||||
|
||||
1. **统一接口**: 为所有自定义消息提供统一的处理接口
|
||||
2. **类型安全**: 通过枚举定义确保消息类型的类型安全
|
||||
3. **扩展性强**: 支持灵活扩展新的消息类型
|
||||
4. **功能完整**: 覆盖了社交、游戏、系统等各个业务场景
|
||||
|
||||
### 8.2 技术特点
|
||||
1. **协议实现**: 实现了NIMSDK的`NIMCustomAttachment`协议
|
||||
2. **JSON序列化**: 支持JSON格式的消息编码和解码
|
||||
3. **模型转换**: 支持与业务模型的相互转换
|
||||
4. **类型枚举**: 使用枚举定义所有消息类型和子类型
|
||||
|
||||
### 8.3 业务覆盖
|
||||
`AttachmentModel` 覆盖了YuMi项目的所有主要业务场景:
|
||||
|
||||
- **社交功能**: 私聊、动态、关注等
|
||||
- **房间功能**: PK、礼物、火箭、红包等
|
||||
- **游戏功能**: 塔罗、航海、相亲等
|
||||
- **系统功能**: 升级、任务、通知等
|
||||
- **商业功能**: VIP、粉丝团、充值等
|
||||
|
||||
### 8.4 架构优势
|
||||
1. **解耦合**: 消息处理逻辑与业务逻辑分离
|
||||
2. **可维护**: 清晰的消息类型定义和处理流程
|
||||
3. **可测试**: 每个消息类型都可以独立测试
|
||||
4. **可扩展**: 新增功能时只需添加新的消息类型
|
||||
|
||||
`AttachmentModel` 是YuMi项目即时通讯功能的重要基础设施,为项目的各种业务场景提供了强大而灵活的消息处理能力。
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: 1.0
|
||||
**最后更新**: 2024年12月
|
||||
**维护人员**: 开发团队
|
||||
|
63
docs/ImageUpload_API_Swift.md
Normal file
63
docs/ImageUpload_API_Swift.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# 图片上传接口(Swift 封装,腾讯云 COS)
|
||||
|
||||
## 1. 参数模型
|
||||
|
||||
```swift
|
||||
struct ImageUploadRequest {
|
||||
let image: UIImage
|
||||
let fileName: String // 如 "image/xxxx.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 接口调用方法
|
||||
|
||||
```swift
|
||||
class ImageUploader {
|
||||
static func uploadImage(
|
||||
request: ImageUploadRequest,
|
||||
completion: @escaping (Result<String, Error>) -> Void
|
||||
) {
|
||||
// 1. 压缩图片,生成 NSData
|
||||
// 2. 调用腾讯云 COS SDK 上传
|
||||
// 3. 返回图片 URL
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 示例用法
|
||||
|
||||
```swift
|
||||
let image = UIImage(named: "test.jpg")!
|
||||
let request = ImageUploadRequest(image: image, fileName: "image/\(UUID().uuidString).jpg")
|
||||
ImageUploader.uploadImage(request: request) { result in
|
||||
switch result {
|
||||
case .success(let url):
|
||||
print("上传成功,图片地址:\(url)")
|
||||
case .failure(let error):
|
||||
print("上传失败: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 第三方依赖
|
||||
|
||||
- [QCloudCOSXML](https://github.com/tencentyun/qcloud-sdk-ios)(腾讯云 COS 官方 SDK)
|
||||
- [TZImagePickerController](https://github.com/banchichen/TZImagePickerController)(图片选择/裁剪,非必须)
|
||||
|
||||
## 5. 配置说明
|
||||
|
||||
- COS 配置信息(appId、region、bucket、签名等)需通过接口动态获取并初始化 SDK。
|
||||
- 上传时需指定 bucket、object(文件名)、body(NSData)。
|
||||
- 支持自定义域名、加速域名等高级配置。
|
||||
|
||||
## 6. 错误处理建议
|
||||
|
||||
- 网络异常、图片压缩失败、SDK 上传失败等需统一处理。
|
||||
- 建议统一封装 `UploadError` 类型。
|
||||
|
||||
## 7. 扩展建议
|
||||
|
||||
- 支持多图批量上传
|
||||
- 支持上传进度回调
|
||||
- 支持 async/await
|
||||
- 可结合项目网络层统一封装
|
69
docs/MomentsPublish_API_Swift.md
Normal file
69
docs/MomentsPublish_API_Swift.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# MomentsPublish 动态发布接口(Swift 封装)
|
||||
|
||||
## 1. 参数模型
|
||||
|
||||
```swift
|
||||
struct MomentsPublishRequest {
|
||||
let uid: String
|
||||
let type: String
|
||||
let worldId: String?
|
||||
let content: String
|
||||
let resList: [String]?
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 接口调用方法
|
||||
|
||||
```swift
|
||||
class MomentsAPI {
|
||||
static func publishMoment(
|
||||
request: MomentsPublishRequest,
|
||||
completion: @escaping (Result<Void, Error>) -> Void
|
||||
) {
|
||||
// 1. 构造参数字典
|
||||
// 2. 发起POST请求到 dynamic/square/publish
|
||||
// 3. 处理返回结果
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 示例用法
|
||||
|
||||
```swift
|
||||
let request = MomentsPublishRequest(
|
||||
uid: "12345",
|
||||
type: "1", // 0:文本 1:图片
|
||||
worldId: "67890",
|
||||
content: "今天很开心!",
|
||||
resList: ["image_url_1", "image_url_2"]
|
||||
)
|
||||
MomentsAPI.publishMoment(request: request) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
print("发布成功")
|
||||
case .failure(let error):
|
||||
print("发布失败: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 参数说明
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|-----------|--------------|------|----------------|
|
||||
| uid | String | 是 | 用户ID |
|
||||
| type | String | 是 | 动态类型(0文本/1图片)|
|
||||
| worldId | String? | 否 | 话题ID |
|
||||
| content | String | 是 | 动态内容 |
|
||||
| resList | [String]? | 否 | 图片资源URL数组 |
|
||||
|
||||
## 5. 错误处理建议
|
||||
|
||||
- 网络异常、参数校验、后端返回错误码均需处理
|
||||
- 建议统一封装 `APIError` 类型
|
||||
|
||||
## 6. 扩展建议
|
||||
|
||||
- 支持 async/await
|
||||
- 可扩展为支持更多动态类型
|
||||
- 可结合项目网络层统一封装
|
618
docs/NIMSDKManager_Usage_Guide.md
Normal file
618
docs/NIMSDKManager_Usage_Guide.md
Normal file
@@ -0,0 +1,618 @@
|
||||
# NIMSDKManager 使用指南
|
||||
|
||||
## 目录
|
||||
|
||||
- [1. 概述](#1-概述)
|
||||
- [2. Objective-C 使用示例](#2-objective-c-使用示例)
|
||||
- [3. Swift 桥接使用示例](#3-swift-桥接使用示例)
|
||||
- [4. 配置说明](#4-配置说明)
|
||||
- [5. 最佳实践](#5-最佳实践)
|
||||
- [6. 常见问题](#6-常见问题)
|
||||
|
||||
## 1. 概述
|
||||
|
||||
`NIMSDKManager` 是一个专门用于管理NIMSDK事务的统一管理类,提供了配置、初始化、登录/登出等完整的功能封装。该类采用单例模式设计,支持Objective-C和Swift项目使用。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- **配置管理**: 统一的NIMSDK配置管理
|
||||
- **初始化**: 简化的SDK初始化流程
|
||||
- **登录管理**: 登录、自动登录、登出等功能
|
||||
- **状态监控**: 实时监控登录状态变化
|
||||
- **消息处理**: 消息发送、接收、广播等
|
||||
- **推送管理**: APNS推送相关功能
|
||||
- **代理管理**: 支持多代理监听
|
||||
|
||||
### 1.2 设计特点
|
||||
|
||||
- **单例模式**: 全局统一管理
|
||||
- **代理模式**: 支持多代理监听
|
||||
- **Block回调**: 支持异步操作回调
|
||||
- **Swift兼容**: 完美支持Swift项目桥接
|
||||
- **错误处理**: 完善的错误处理机制
|
||||
|
||||
## 2. Objective-C 使用示例
|
||||
|
||||
### 2.1 基本配置和初始化
|
||||
|
||||
```objc
|
||||
// 1. 创建配置模型
|
||||
NIMSDKConfigModel *config = [[NIMSDKConfigModel alloc] init];
|
||||
config.appKey = KeyWithType(KeyType_NetEase);
|
||||
config.apnsCername = @"pikoDevelopPush"; // DEBUG环境
|
||||
config.shouldConsiderRevokedMessageUnreadCount = YES;
|
||||
config.shouldSyncStickTopSessionInfos = YES;
|
||||
config.enabledHttpsForInfo = NO; // DEBUG环境
|
||||
config.enabledHttpsForMessage = NO; // DEBUG环境
|
||||
|
||||
// 2. 配置并初始化
|
||||
[[NIMSDKManager sharedManager] configureWithConfig:config];
|
||||
[[NIMSDKManager sharedManager] initializeWithCompletion:^(NSError * _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"NIMSDK初始化失败: %@", error);
|
||||
} else {
|
||||
NSLog(@"NIMSDK初始化成功");
|
||||
}
|
||||
}];
|
||||
```
|
||||
|
||||
### 2.2 登录管理
|
||||
|
||||
```objc
|
||||
// 1. 设置登录状态变化监听
|
||||
[[NIMSDKManager sharedManager] setLoginStatusChangeBlock:^(NIMSDKLoginStatus status) {
|
||||
switch (status) {
|
||||
case NIMSDKLoginStatusNotLogin:
|
||||
NSLog(@"未登录");
|
||||
break;
|
||||
case NIMSDKLoginStatusLogging:
|
||||
NSLog(@"登录中");
|
||||
break;
|
||||
case NIMSDKLoginStatusLogined:
|
||||
NSLog(@"已登录");
|
||||
break;
|
||||
case NIMSDKLoginStatusLogout:
|
||||
NSLog(@"已登出");
|
||||
break;
|
||||
case NIMSDKLoginStatusKickout:
|
||||
NSLog(@"被踢出");
|
||||
break;
|
||||
case NIMSDKLoginStatusAutoLoginFailed:
|
||||
NSLog(@"自动登录失败");
|
||||
break;
|
||||
}
|
||||
}];
|
||||
|
||||
// 2. 执行登录
|
||||
[[NIMSDKManager sharedManager] loginWithAccount:@"user123"
|
||||
token:@"token123"
|
||||
completion:^(NSError * _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"登录失败: %@", error);
|
||||
} else {
|
||||
NSLog(@"登录成功");
|
||||
}
|
||||
}];
|
||||
|
||||
// 3. 自动登录
|
||||
NSDictionary *autoLoginData = @{@"account": @"user123", @"token": @"token123"};
|
||||
[[NIMSDKManager sharedManager] autoLoginWithData:autoLoginData
|
||||
completion:^(NSError * _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"自动登录失败: %@", error);
|
||||
} else {
|
||||
NSLog(@"自动登录成功");
|
||||
}
|
||||
}];
|
||||
|
||||
// 4. 登出
|
||||
[[NIMSDKManager sharedManager] logoutWithCompletion:^(NSError * _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"登出失败: %@", error);
|
||||
} else {
|
||||
NSLog(@"登出成功");
|
||||
}
|
||||
}];
|
||||
```
|
||||
|
||||
### 2.3 代理监听
|
||||
|
||||
```objc
|
||||
@interface MyViewController () <NIMSDKManagerDelegate>
|
||||
@end
|
||||
|
||||
@implementation MyViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
// 添加代理
|
||||
[[NIMSDKManager sharedManager] addDelegate:self];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
// 移除代理
|
||||
[[NIMSDKManager sharedManager] removeDelegate:self];
|
||||
}
|
||||
|
||||
#pragma mark - NIMSDKManagerDelegate
|
||||
|
||||
- (void)nimSDKManager:(id)manager didChangeLoginStatus:(NIMSDKLoginStatus)status {
|
||||
NSLog(@"登录状态变化: %ld", (long)status);
|
||||
}
|
||||
|
||||
- (void)nimSDKManager:(id)manager didAutoLoginFailed:(NSError *)error {
|
||||
NSLog(@"自动登录失败: %@", error);
|
||||
}
|
||||
|
||||
- (void)nimSDKManager:(id)manager didKickout:(NIMLoginKickoutResult *)result {
|
||||
NSLog(@"被踢出: %@", result);
|
||||
}
|
||||
|
||||
- (void)nimSDKManager:(id)manager didReceiveMessages:(NSArray<NIMMessage *> *)messages {
|
||||
NSLog(@"收到消息: %@", messages);
|
||||
}
|
||||
|
||||
- (void)nimSDKManager:(id)manager didReceiveBroadcastMessage:(NIMBroadcastMessage *)broadcastMessage {
|
||||
NSLog(@"收到广播消息: %@", broadcastMessage);
|
||||
}
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
### 2.4 消息管理
|
||||
|
||||
```objc
|
||||
// 1. 创建消息
|
||||
NIMMessage *textMessage = [[NIMSDKManager sharedManager] createTextMessage:@"Hello World"];
|
||||
|
||||
// 2. 创建自定义消息
|
||||
AttachmentModel *attachment = [[AttachmentModel alloc] init];
|
||||
attachment.first = CustomMessageType_Gift;
|
||||
attachment.second = Custom_Message_Sub_Gift_Send;
|
||||
attachment.data = @{@"giftId": @"123", @"giftName": @"玫瑰花"};
|
||||
|
||||
NIMMessage *customMessage = [[NIMSDKManager sharedManager] createCustomMessageWithAttachment:attachment];
|
||||
|
||||
// 3. 发送消息
|
||||
NIMSession *session = [NIMSession session:@"receiverId" type:NIMSessionTypeP2P];
|
||||
[[NIMSDKManager sharedManager] sendMessage:textMessage
|
||||
toSession:session
|
||||
completion:^(NSError * _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"发送失败: %@", error);
|
||||
} else {
|
||||
NSLog(@"发送成功");
|
||||
}
|
||||
}];
|
||||
|
||||
// 4. 获取未读消息数
|
||||
NSInteger unreadCount = [[NIMSDKManager sharedManager] unreadMessageCount];
|
||||
NSLog(@"未读消息数: %ld", (long)unreadCount);
|
||||
|
||||
// 5. 获取所有会话
|
||||
NSArray<NIMRecentSession *> *sessions = [[NIMSDKManager sharedManager] allRecentSessions];
|
||||
NSLog(@"会话数量: %lu", (unsigned long)sessions.count);
|
||||
```
|
||||
|
||||
### 2.5 推送管理
|
||||
|
||||
```objc
|
||||
// 1. 更新APNS设备Token
|
||||
- (void)application:(UIApplication *)app
|
||||
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
|
||||
[[NIMSDKManager sharedManager] updateApnsToken:deviceToken];
|
||||
}
|
||||
|
||||
// 2. 处理推送消息
|
||||
- (void)application:(UIApplication *)application
|
||||
didReceiveRemoteNotification:(NSDictionary *)userInfo
|
||||
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
|
||||
|
||||
BOOL handled = [[NIMSDKManager sharedManager] handlePushNotification:userInfo];
|
||||
if (handled) {
|
||||
completionHandler(UIBackgroundFetchResultNewData);
|
||||
} else {
|
||||
completionHandler(UIBackgroundFetchResultNoData);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Swift 桥接使用示例
|
||||
|
||||
### 3.1 创建桥接头文件
|
||||
|
||||
在Swift项目中创建桥接头文件 `YourProject-Bridging-Header.h`:
|
||||
|
||||
```objc
|
||||
//
|
||||
// YourProject-Bridging-Header.h
|
||||
// YourProject
|
||||
//
|
||||
|
||||
#ifndef YourProject_Bridging_Header_h
|
||||
#define YourProject_Bridging_Header_h
|
||||
|
||||
#import "NIMSDKManager.h"
|
||||
#import "AttachmentModel.h"
|
||||
#import "CustomAttachmentDecoder.h"
|
||||
|
||||
#endif /* YourProject_Bridging_Header_h */
|
||||
```
|
||||
|
||||
### 3.2 Swift 使用示例
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
class NIMSDKService {
|
||||
|
||||
static let shared = NIMSDKService()
|
||||
private let nimManager = NIMSDKManager.shared()
|
||||
|
||||
private init() {
|
||||
setupNIMSDK()
|
||||
}
|
||||
|
||||
// MARK: - 配置和初始化
|
||||
|
||||
private func setupNIMSDK() {
|
||||
// 创建配置
|
||||
let config = NIMSDKConfigModel()
|
||||
config.appKey = KeyWithType(KeyType_NetEase)
|
||||
config.apnsCername = "pikoDevelopPush" // DEBUG环境
|
||||
config.shouldConsiderRevokedMessageUnreadCount = true
|
||||
config.shouldSyncStickTopSessionInfos = true
|
||||
config.enabledHttpsForInfo = false // DEBUG环境
|
||||
config.enabledHttpsForMessage = false // DEBUG环境
|
||||
|
||||
// 配置并初始化
|
||||
nimManager.configure(with: config)
|
||||
nimManager.initialize { [weak self] error in
|
||||
if let error = error {
|
||||
print("NIMSDK初始化失败: \(error)")
|
||||
} else {
|
||||
print("NIMSDK初始化成功")
|
||||
self?.setupDelegates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setupDelegates() {
|
||||
// 设置登录状态变化监听
|
||||
nimManager.setLoginStatusChangeBlock { [weak self] status in
|
||||
self?.handleLoginStatusChange(status)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 登录管理
|
||||
|
||||
func login(account: String, token: String, completion: @escaping (Error?) -> Void) {
|
||||
nimManager.login(withAccount: account, token: token) { error in
|
||||
DispatchQueue.main.async {
|
||||
completion(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func autoLogin(data: [String: Any], completion: @escaping (Error?) -> Void) {
|
||||
nimManager.autoLogin(with: data) { error in
|
||||
DispatchQueue.main.async {
|
||||
completion(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logout(completion: @escaping (Error?) -> Void) {
|
||||
nimManager.logout { error in
|
||||
DispatchQueue.main.async {
|
||||
completion(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 状态查询
|
||||
|
||||
var isLogined: Bool {
|
||||
return nimManager.isLogined()
|
||||
}
|
||||
|
||||
var currentAccount: String? {
|
||||
return nimManager.currentAccount()
|
||||
}
|
||||
|
||||
var loginStatus: NIMSDKLoginStatus {
|
||||
return nimManager.currentLoginStatus()
|
||||
}
|
||||
|
||||
// MARK: - 消息管理
|
||||
|
||||
func sendTextMessage(_ text: String, to sessionId: String, completion: @escaping (Error?) -> Void) {
|
||||
let message = nimManager.createTextMessage(text)
|
||||
let session = NIMSession(session: sessionId, type: .p2P)
|
||||
|
||||
nimManager.send(message, to: session) { error in
|
||||
DispatchQueue.main.async {
|
||||
completion(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendCustomMessage(attachment: NIMCustomAttachment, to sessionId: String, completion: @escaping (Error?) -> Void) {
|
||||
let message = nimManager.createCustomMessage(with: attachment)
|
||||
let session = NIMSession(session: sessionId, type: .p2P)
|
||||
|
||||
nimManager.send(message, to: session) { error in
|
||||
DispatchQueue.main.async {
|
||||
completion(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unreadMessageCount: Int {
|
||||
return Int(nimManager.unreadMessageCount())
|
||||
}
|
||||
|
||||
var allRecentSessions: [NIMRecentSession] {
|
||||
return nimManager.allRecentSessions() ?? []
|
||||
}
|
||||
|
||||
// MARK: - 推送管理
|
||||
|
||||
func updateApnsToken(_ deviceToken: Data) {
|
||||
nimManager.updateApnsToken(deviceToken)
|
||||
}
|
||||
|
||||
func handlePushNotification(_ userInfo: [AnyHashable: Any]) -> Bool {
|
||||
return nimManager.handlePushNotification(userInfo)
|
||||
}
|
||||
|
||||
// MARK: - 私有方法
|
||||
|
||||
private func handleLoginStatusChange(_ status: NIMSDKLoginStatus) {
|
||||
switch status {
|
||||
case .notLogin:
|
||||
print("未登录")
|
||||
case .logging:
|
||||
print("登录中")
|
||||
case .logined:
|
||||
print("已登录")
|
||||
case .logout:
|
||||
print("已登出")
|
||||
case .kickout:
|
||||
print("被踢出")
|
||||
case .autoLoginFailed:
|
||||
print("自动登录失败")
|
||||
@unknown default:
|
||||
print("未知状态")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Swift 扩展
|
||||
|
||||
extension NIMSDKService {
|
||||
|
||||
// 创建礼物消息的便捷方法
|
||||
func createGiftMessage(giftId: String, giftName: String, giftCount: Int) -> NIMMessage? {
|
||||
let attachment = AttachmentModel()
|
||||
attachment.first = Int32(CustomMessageType_Gift)
|
||||
attachment.second = Int32(Custom_Message_Sub_Gift_Send)
|
||||
attachment.data = [
|
||||
"giftId": giftId,
|
||||
"giftName": giftName,
|
||||
"giftCount": giftCount
|
||||
]
|
||||
|
||||
return nimManager.createCustomMessage(with: attachment)
|
||||
}
|
||||
|
||||
// 发送礼物消息的便捷方法
|
||||
func sendGift(giftId: String, giftName: String, giftCount: Int, to sessionId: String, completion: @escaping (Error?) -> Void) {
|
||||
guard let message = createGiftMessage(giftId: giftId, giftName: giftName, giftCount: giftCount) else {
|
||||
completion(NSError(domain: "NIMSDKService", code: -1, userInfo: [NSLocalizedDescriptionKey: "创建礼物消息失败"]))
|
||||
return
|
||||
}
|
||||
|
||||
let session = NIMSession(session: sessionId, type: .p2P)
|
||||
nimManager.send(message, to: session) { error in
|
||||
DispatchQueue.main.async {
|
||||
completion(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Swift 视图控制器使用示例
|
||||
|
||||
```swift
|
||||
import UIKit
|
||||
|
||||
class ChatViewController: UIViewController {
|
||||
|
||||
private let nimService = NIMSDKService.shared
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupNIMSDK()
|
||||
}
|
||||
|
||||
private func setupNIMSDK() {
|
||||
// 检查登录状态
|
||||
if !nimService.isLogined {
|
||||
// 执行登录
|
||||
nimService.login(account: "user123", token: "token123") { [weak self] error in
|
||||
if let error = error {
|
||||
print("登录失败: \(error)")
|
||||
} else {
|
||||
print("登录成功")
|
||||
self?.startChat()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
startChat()
|
||||
}
|
||||
}
|
||||
|
||||
private func startChat() {
|
||||
// 开始聊天功能
|
||||
print("开始聊天")
|
||||
}
|
||||
|
||||
// MARK: - 发送消息示例
|
||||
|
||||
@IBAction func sendTextMessage(_ sender: UIButton) {
|
||||
nimService.sendTextMessage("Hello from Swift!", to: "receiver123") { error in
|
||||
if let error = error {
|
||||
print("发送失败: \(error)")
|
||||
} else {
|
||||
print("发送成功")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func sendGiftMessage(_ sender: UIButton) {
|
||||
nimService.sendGift(giftId: "123", giftName: "玫瑰花", giftCount: 1, to: "receiver123") { error in
|
||||
if let error = error {
|
||||
print("发送礼物失败: \(error)")
|
||||
} else {
|
||||
print("发送礼物成功")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 获取消息信息
|
||||
|
||||
func updateUnreadCount() {
|
||||
let count = nimService.unreadMessageCount
|
||||
print("未读消息数: \(count)")
|
||||
}
|
||||
|
||||
func loadRecentSessions() {
|
||||
let sessions = nimService.allRecentSessions
|
||||
print("会话数量: \(sessions.count)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Swift AppDelegate 集成
|
||||
|
||||
```swift
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
private let nimService = NIMSDKService.shared
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
// NIMSDK已经在NIMSDKService中初始化
|
||||
// 这里可以添加其他初始化代码
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: - 推送处理
|
||||
|
||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
nimService.updateApnsToken(deviceToken)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
||||
|
||||
let handled = nimService.handlePushNotification(userInfo)
|
||||
completionHandler(handled ? .newData : .noData)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 配置说明
|
||||
|
||||
### 4.1 环境配置
|
||||
|
||||
```objc
|
||||
// DEBUG环境配置
|
||||
#ifdef DEBUG
|
||||
config.apnsCername = @"pikoDevelopPush";
|
||||
config.enabledHttpsForInfo = NO;
|
||||
config.enabledHttpsForMessage = NO;
|
||||
#else
|
||||
config.apnsCername = @"newPiko";
|
||||
config.enabledHttpsForInfo = YES;
|
||||
config.enabledHttpsForMessage = YES;
|
||||
#endif
|
||||
```
|
||||
|
||||
### 4.2 AppKey配置
|
||||
|
||||
```objc
|
||||
// 从常量文件获取AppKey
|
||||
config.appKey = KeyWithType(KeyType_NetEase);
|
||||
|
||||
// 或者直接设置
|
||||
config.appKey = @"your_app_key_here";
|
||||
```
|
||||
|
||||
### 4.3 推送配置
|
||||
|
||||
确保在项目中正确配置了APNS证书,并在配置中设置正确的证书名称。
|
||||
|
||||
## 5. 最佳实践
|
||||
|
||||
### 5.1 初始化时机
|
||||
|
||||
- 在App启动时尽早初始化NIMSDK
|
||||
- 确保在用户登录前完成初始化
|
||||
|
||||
### 5.2 错误处理
|
||||
|
||||
- 对所有异步操作添加错误处理
|
||||
- 在UI线程中处理回调结果
|
||||
|
||||
### 5.3 内存管理
|
||||
|
||||
- 及时移除不需要的代理
|
||||
- 避免循环引用
|
||||
|
||||
### 5.4 状态管理
|
||||
|
||||
- 监听登录状态变化
|
||||
- 根据状态变化更新UI
|
||||
|
||||
### 5.5 Swift集成
|
||||
|
||||
- 使用桥接头文件正确导入Objective-C类
|
||||
- 在Swift中创建便捷的包装方法
|
||||
|
||||
## 6. 常见问题
|
||||
|
||||
### 6.1 编译错误
|
||||
**Q: 编译时提示找不到NIMSDK头文件**
|
||||
A: 确保正确导入了NIMSDK框架,并在桥接头文件中正确导入。
|
||||
|
||||
### 6.2 初始化失败
|
||||
**Q: NIMSDK初始化失败**
|
||||
A: 检查AppKey是否正确,网络连接是否正常。
|
||||
|
||||
### 6.3 登录问题
|
||||
**Q: 登录后立即被踢出**
|
||||
A: 检查账号是否在其他设备登录,或者Token是否过期。
|
||||
|
||||
### 6.4 Swift桥接问题
|
||||
**Q: Swift中无法使用NIMSDKManager**
|
||||
A: 确保在桥接头文件中正确导入了相关头文件。
|
||||
|
||||
### 6.5 推送问题
|
||||
**Q: 推送通知无法接收**
|
||||
A: 检查APNS证书配置,确保设备Token正确上传。
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: 1.0
|
||||
**最后更新**: 2024年12月
|
||||
**维护人员**: 开发团队
|
||||
|
555
docs/NIMSDK_Integration_Documentation.md
Normal file
555
docs/NIMSDK_Integration_Documentation.md
Normal file
@@ -0,0 +1,555 @@
|
||||
# NIMSDK 集成说明文档
|
||||
|
||||
## 目录
|
||||
- [1. 项目概述](#1-项目概述)
|
||||
- [2. NIMSDK导入配置](#2-nimsdk导入配置)
|
||||
- [3. NIMSDK初始化流程](#3-nimsdk初始化流程)
|
||||
- [4. 关键配置参数说明](#4-关键配置参数说明)
|
||||
- [5. 自定义消息处理](#5-自定义消息处理)
|
||||
- [6. 登录管理](#6-登录管理)
|
||||
- [7. 消息接收处理](#7-消息接收处理)
|
||||
- [8. 推送通知集成](#8-推送通知集成)
|
||||
- [9. 最佳实践](#9-最佳实践)
|
||||
- [10. 常见问题](#10-常见问题)
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
### 1.1 项目简介
|
||||
YuMi是一个基于iOS平台的社交应用,使用Objective-C开发,采用MVP架构模式。项目集成了网易云信SDK(NIMSDK)用于实现即时通讯功能。
|
||||
|
||||
### 1.2 技术栈
|
||||
- **开发语言**: Objective-C
|
||||
- **架构模式**: MVP (Model-View-Presenter)
|
||||
- **即时通讯**: 网易云信 NIMSDK_LITE
|
||||
- **依赖管理**: CocoaPods
|
||||
- **最低支持版本**: iOS 11.0
|
||||
|
||||
### 1.3 主要功能模块
|
||||
- 用户登录注册
|
||||
- 即时消息通讯
|
||||
- 聊天室功能
|
||||
- 动态发布
|
||||
- 个人中心
|
||||
- 房间直播
|
||||
|
||||
## 2. NIMSDK导入配置
|
||||
|
||||
### 2.1 Podfile配置
|
||||
|
||||
在项目的`Podfile`中添加NIMSDK依赖:
|
||||
|
||||
```ruby
|
||||
# 云信SDK
|
||||
pod 'NIMSDK_LITE'
|
||||
```
|
||||
|
||||
### 2.2 头文件导入
|
||||
|
||||
在需要使用NIMSDK的文件中导入头文件:
|
||||
|
||||
```objc
|
||||
#import <NIMSDK/NIMSDK.h>
|
||||
```
|
||||
|
||||
### 2.3 项目结构
|
||||
|
||||
```
|
||||
YuMi/
|
||||
├── Appdelegate/
|
||||
│ ├── AppDelegate.m # 主应用代理
|
||||
│ └── AppDelegate+ThirdConfig.m # 第三方SDK配置
|
||||
├── Modules/
|
||||
│ ├── YMMessage/ # 消息模块
|
||||
│ │ └── Tool/
|
||||
│ │ └── CustomAttachmentDecoder # 自定义消息解码器
|
||||
│ ├── YMTabbar/ # 主界面模块
|
||||
│ └── YMRoom/ # 房间模块
|
||||
└── Global/
|
||||
└── YUMIConstant.h # 常量定义
|
||||
```
|
||||
|
||||
## 3. NIMSDK初始化流程
|
||||
|
||||
### 3.1 初始化时序图
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as AppDelegate
|
||||
participant ThirdConfig as AppDelegate+ThirdConfig
|
||||
participant NIMSDK as NIMSDK
|
||||
participant Config as NIMSDKConfig
|
||||
participant Decoder as CustomAttachmentDecoder
|
||||
|
||||
App->>ThirdConfig: initThirdConfig()
|
||||
ThirdConfig->>ThirdConfig: configNIMSDK()
|
||||
ThirdConfig->>NIMSDK: registerWithOption(option)
|
||||
ThirdConfig->>Decoder: registerCustomDecoder()
|
||||
ThirdConfig->>Config: shouldConsiderRevokedMessageUnreadCount = YES
|
||||
ThirdConfig->>Config: setShouldSyncStickTopSessionInfos(YES)
|
||||
ThirdConfig->>Config: enabledHttpsForInfo = NO (DEBUG)
|
||||
ThirdConfig->>Config: enabledHttpsForMessage = NO (DEBUG)
|
||||
|
||||
Note over App: 应用启动完成
|
||||
App->>App: loadMainPage()
|
||||
App->>App: 检查登录状态
|
||||
App->>NIMSDK: 自动登录或手动登录
|
||||
```
|
||||
|
||||
### 3.2 初始化代码实现
|
||||
|
||||
#### 3.2.1 主应用代理初始化
|
||||
|
||||
```objc
|
||||
// AppDelegate.m
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
|
||||
// 初始化第三方SDK配置
|
||||
[self initThirdConfig];
|
||||
|
||||
// 其他初始化代码...
|
||||
|
||||
return YES;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.2 NIMSDK配置方法
|
||||
|
||||
```objc
|
||||
// AppDelegate+ThirdConfig.m
|
||||
- (void)configNIMSDK {
|
||||
// 1. 获取云信AppKey
|
||||
NSString *appKey = KeyWithType(KeyType_NetEase);
|
||||
NIMSDKOption *option = [NIMSDKOption optionWithAppKey:appKey];
|
||||
|
||||
// 2. 配置APNS证书名称
|
||||
#ifdef DEBUG
|
||||
option.apnsCername = @"pikoDevelopPush";
|
||||
#else
|
||||
option.apnsCername = @"newPiko";
|
||||
#endif
|
||||
|
||||
// 3. 注册SDK
|
||||
[[NIMSDK sharedSDK] registerWithOption:option];
|
||||
|
||||
// 4. 注册自定义消息解码器
|
||||
[NIMCustomObject registerCustomDecoder:[[CustomAttachmentDecoder alloc] init]];
|
||||
|
||||
// 5. 配置SDK参数
|
||||
[NIMSDKConfig sharedConfig].shouldConsiderRevokedMessageUnreadCount = YES;
|
||||
[[NIMSDKConfig sharedConfig] setShouldSyncStickTopSessionInfos:YES];
|
||||
|
||||
// 6. DEBUG模式下禁用HTTPS
|
||||
#ifdef DEBUG
|
||||
[NIMSDKConfig sharedConfig].enabledHttpsForInfo = NO;
|
||||
[NIMSDKConfig sharedConfig].enabledHttpsForMessage = NO;
|
||||
#endif
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 关键配置参数说明
|
||||
|
||||
### 4.1 AppKey配置
|
||||
|
||||
项目支持多环境配置,通过`YUMIConstant.m`中的`KeyWithType`方法获取:
|
||||
|
||||
```objc
|
||||
// 测试环境
|
||||
@(KeyType_NetEase): @"79bc37000f4018a2a24ea9dc6ca08d32"
|
||||
|
||||
// 生产环境
|
||||
@(KeyType_NetEase): @"7371d729710cd6ce3a50163b956b5eb6"
|
||||
```
|
||||
|
||||
### 4.2 APNS推送配置
|
||||
|
||||
```objc
|
||||
// 开发环境
|
||||
option.apnsCername = @"pikoDevelopPush";
|
||||
|
||||
// 生产环境
|
||||
option.apnsCername = @"newPiko";
|
||||
```
|
||||
|
||||
### 4.3 SDK配置参数详解
|
||||
|
||||
| 参数 | 值 | 说明 |
|
||||
|------|----|----|
|
||||
| `shouldConsiderRevokedMessageUnreadCount` | `YES` | 撤回消息计入未读数 |
|
||||
| `shouldSyncStickTopSessionInfos` | `YES` | 同步置顶会话信息 |
|
||||
| `enabledHttpsForInfo` | `NO` (DEBUG) | DEBUG模式禁用HTTPS信息传输 |
|
||||
| `enabledHttpsForMessage` | `NO` (DEBUG) | DEBUG模式禁用HTTPS消息传输 |
|
||||
|
||||
## 5. 自定义消息处理
|
||||
|
||||
### 5.1 自定义附件解码器
|
||||
|
||||
#### 5.1.1 解码器接口定义
|
||||
|
||||
```objc
|
||||
// CustomAttachmentDecoder.h
|
||||
@interface CustomAttachmentDecoder : NSObject<NIMCustomAttachmentCoding>
|
||||
@end
|
||||
```
|
||||
|
||||
#### 5.1.2 解码器实现
|
||||
|
||||
```objc
|
||||
// CustomAttachmentDecoder.m
|
||||
- (id<NIMCustomAttachment>)decodeAttachment:(NSString *)content {
|
||||
id<NIMCustomAttachment> attachment;
|
||||
NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
if (data) {
|
||||
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data
|
||||
options:0
|
||||
error:nil];
|
||||
if ([dict isKindOfClass:[NSDictionary class]]) {
|
||||
int first = [dict[@"first"] intValue];
|
||||
int second = [dict[@"second"] intValue];
|
||||
id originalData = dict[@"data"];
|
||||
|
||||
AttachmentModel *model = [[AttachmentModel alloc] init];
|
||||
model.first = (short)first;
|
||||
model.second = (short)second;
|
||||
model.data = originalData;
|
||||
|
||||
attachment = model;
|
||||
}
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 消息类型定义
|
||||
|
||||
自定义消息通过`AttachmentModel`定义结构:
|
||||
|
||||
```objc
|
||||
@interface AttachmentModel : NSObject<NIMCustomAttachment>
|
||||
|
||||
@property (nonatomic, assign) short first; // 消息类型标识
|
||||
@property (nonatomic, assign) short second; // 消息子类型标识
|
||||
@property (nonatomic, strong) id data; // 消息数据内容
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
### 5.3 消息类型示例
|
||||
|
||||
```objc
|
||||
// 打招呼消息
|
||||
if (attachment.first == CustomMessageType_FindNew &&
|
||||
attachment.second == Custom_Message_Find_New_Greet_New_User) {
|
||||
// 处理新用户打招呼消息
|
||||
FindNewGreetMessageModel *greetInfo = [FindNewGreetMessageModel modelWithDictionary:attachment.data];
|
||||
// 显示打招呼弹窗
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 登录管理
|
||||
|
||||
### 6.1 登录状态检查
|
||||
|
||||
```objc
|
||||
// 检查是否已登录
|
||||
if ([NIMSDK sharedSDK].loginManager.isLogined) {
|
||||
// 已登录,执行相关操作
|
||||
} else {
|
||||
// 未登录,跳转登录页面
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 自动登录处理
|
||||
|
||||
```objc
|
||||
// NIMLoginManagerDelegate
|
||||
- (void)onAutoLoginFailed:(NSError *)error {
|
||||
// 如果非上次登录设备 autoLogin 会返回 417
|
||||
if (error.code == 417) {
|
||||
@weakify(self);
|
||||
AccountModel* accountModel = [AccountInfoStorage instance].getCurrentAccountInfo;
|
||||
[[NIMSDK sharedSDK].loginManager login:accountModel.uid
|
||||
token:accountModel.netEaseToken
|
||||
completion:^(NSError * _Nullable error) {
|
||||
if (error) {
|
||||
@strongify(self);
|
||||
[self.presenter logout];
|
||||
}
|
||||
}];
|
||||
return;
|
||||
}
|
||||
[self.presenter logout];
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 踢出处理
|
||||
|
||||
```objc
|
||||
// NIMLoginManagerDelegate
|
||||
- (void)onKickout:(NIMLoginKickoutResult *)result {
|
||||
// 显示踢出提示
|
||||
[XNDJTDDLoadingTool showErrorWithMessage:YMLocalizedString(@"TabbarViewController0")];
|
||||
|
||||
// 清理房间状态
|
||||
if ([XPRoomMiniManager shareManager].getRoomInfo) {
|
||||
[[RtcManager instance] exitRoom];
|
||||
[[NIMSDK sharedSDK].chatroomManager exitChatroom:roomId completion:nil];
|
||||
[self.roomMineView hiddenRoomMiniView];
|
||||
}
|
||||
|
||||
// 执行登出
|
||||
[self.presenter logout];
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 手动登录
|
||||
|
||||
```objc
|
||||
// 执行登录
|
||||
[[NIMSDK sharedSDK].loginManager login:accountModel.uid
|
||||
token:accountModel.netEaseToken
|
||||
completion:^(NSError * _Nullable error) {
|
||||
if (error) {
|
||||
// 登录失败处理
|
||||
NSLog(@"登录失败: %@", error);
|
||||
} else {
|
||||
// 登录成功处理
|
||||
NSLog(@"登录成功");
|
||||
}
|
||||
}];
|
||||
```
|
||||
|
||||
## 7. 消息接收处理
|
||||
|
||||
### 7.1 消息接收代理
|
||||
|
||||
```objc
|
||||
// NIMChatManagerDelegate
|
||||
- (void)onRecvMessages:(NSArray<NIMMessage *> *)messages {
|
||||
if ([AccountInfoStorage instance].getTicket.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (NIMMessage *message in messages) {
|
||||
if (message.session.sessionType == NIMSessionTypeP2P) {
|
||||
if (message.messageType == NIMMessageTypeCustom) {
|
||||
NIMCustomObject *obj = (NIMCustomObject *)message.messageObject;
|
||||
if (obj.attachment != nil && [obj.attachment isKindOfClass:[AttachmentModel class]]) {
|
||||
AttachmentModel *attachment = (AttachmentModel *)obj.attachment;
|
||||
// 处理自定义消息
|
||||
[self handleCustomMessage:attachment];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 自定义消息处理
|
||||
|
||||
```objc
|
||||
- (void)handleCustomMessage:(AttachmentModel *)attachment {
|
||||
switch (attachment.first) {
|
||||
case CustomMessageType_FindNew:
|
||||
[self handleFindNewMessage:attachment];
|
||||
break;
|
||||
case CustomMessageType_Gift:
|
||||
[self handleGiftMessage:attachment];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleFindNewMessage:(AttachmentModel *)attachment {
|
||||
if (attachment.second == Custom_Message_Find_New_Greet_New_User) {
|
||||
FindNewGreetMessageModel *greetInfo = [FindNewGreetMessageModel modelWithDictionary:attachment.data];
|
||||
if (greetInfo.uid.integerValue != [AccountInfoStorage instance].getUid.integerValue) {
|
||||
// 显示打招呼弹窗
|
||||
[self showGreetAlert:greetInfo];
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 广播消息处理
|
||||
|
||||
```objc
|
||||
// NIMBroadcastManagerDelegate
|
||||
- (void)onReceiveBroadcastMessage:(NIMBroadcastMessage *)broadcastMessage {
|
||||
if ([AccountInfoStorage instance].getUid.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理广播消息
|
||||
NSString *content = broadcastMessage.content;
|
||||
// 解析并处理广播内容
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 推送通知集成
|
||||
|
||||
### 8.1 推送权限申请
|
||||
|
||||
```objc
|
||||
- (void)registerNot {
|
||||
if (@available(iOS 10.0, *)) {
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert |
|
||||
UNAuthorizationOptionBadge |
|
||||
UNAuthorizationOptionSound)
|
||||
completionHandler:^(BOOL granted, NSError * _Nullable error) {
|
||||
if (granted) {
|
||||
[center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
|
||||
if (settings.authorizationStatus == UNAuthorizationStatusAuthorized) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[UIApplication sharedApplication] registerForRemoteNotifications];
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 设备Token更新
|
||||
|
||||
```objc
|
||||
- (void)application:(UIApplication *)app
|
||||
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
|
||||
// 上传devicetoken至云信服务器
|
||||
[[NIMSDK sharedSDK] updateApnsToken:deviceToken];
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 推送消息处理
|
||||
|
||||
```objc
|
||||
- (void)application:(UIApplication *)application
|
||||
didReceiveRemoteNotification:(NSDictionary *)userInfo
|
||||
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
|
||||
|
||||
NSString *data = userInfo[@"data"];
|
||||
if (data) {
|
||||
NSDictionary *dataDic = [data mj_JSONObject];
|
||||
NSString *userId = dataDic[@"uid"];
|
||||
if (userId) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
|
||||
dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kOpenRoomNotification
|
||||
object:nil
|
||||
userInfo:@{@"type": @"kOpenChat",
|
||||
@"uid": userId,
|
||||
@"isNoAttention": @(YES)}];
|
||||
ClientConfig *config = [ClientConfig shareConfig];
|
||||
config.pushChatId = userId;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
completionHandler(UIBackgroundFetchResultNewData);
|
||||
}
|
||||
```
|
||||
|
||||
### 8.4 应用状态处理
|
||||
|
||||
```objc
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
// 设置应用角标为未读消息数
|
||||
NSInteger count = [NIMSDK sharedSDK].conversationManager.allUnreadCount;
|
||||
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:count];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
// 应用激活时清除角标
|
||||
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"kAppDidBecomeActive" object:nil];
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 最佳实践
|
||||
|
||||
### 9.1 初始化最佳实践
|
||||
|
||||
1. **在应用启动时初始化**: 在`AppDelegate`的`didFinishLaunchingWithOptions`中调用
|
||||
2. **配置环境参数**: 区分开发和生产环境的配置
|
||||
3. **注册自定义解码器**: 在SDK注册后立即注册自定义解码器
|
||||
4. **设置代理**: 在适当的时机添加和移除代理
|
||||
|
||||
### 9.2 登录管理最佳实践
|
||||
|
||||
1. **自动登录**: 优先使用自动登录,减少用户等待时间
|
||||
2. **错误处理**: 对登录失败进行适当的错误处理和重试
|
||||
3. **状态同步**: 保持本地登录状态与服务器状态同步
|
||||
4. **踢出处理**: 妥善处理被踢出的情况,清理相关状态
|
||||
|
||||
### 9.3 消息处理最佳实践
|
||||
|
||||
1. **消息过滤**: 根据业务需求过滤不需要的消息
|
||||
2. **自定义消息**: 合理设计自定义消息结构
|
||||
3. **性能优化**: 避免在消息处理中进行耗时操作
|
||||
4. **内存管理**: 及时释放不需要的消息对象
|
||||
|
||||
### 9.4 推送通知最佳实践
|
||||
|
||||
1. **权限申请**: 在合适的时机申请推送权限
|
||||
2. **Token更新**: 及时更新设备Token
|
||||
3. **消息解析**: 正确解析推送消息内容
|
||||
4. **状态处理**: 根据应用状态处理推送消息
|
||||
|
||||
## 10. 常见问题
|
||||
|
||||
### 10.1 初始化问题
|
||||
|
||||
**Q: SDK初始化失败怎么办?**
|
||||
A: 检查AppKey是否正确,网络连接是否正常,证书配置是否正确。
|
||||
|
||||
**Q: 自定义解码器注册失败?**
|
||||
A: 确保在SDK注册后注册解码器,解码器类实现了正确的协议。
|
||||
|
||||
### 10.2 登录问题
|
||||
|
||||
**Q: 自动登录失败,错误码417?**
|
||||
A: 这是正常情况,表示非上次登录设备,需要重新输入账号密码登录。
|
||||
|
||||
**Q: 登录后立即被踢出?**
|
||||
A: 检查账号是否在其他设备登录,或者Token是否过期。
|
||||
|
||||
### 10.3 消息问题
|
||||
|
||||
**Q: 自定义消息无法解析?**
|
||||
A: 检查消息格式是否正确,解码器是否正确注册。
|
||||
|
||||
**Q: 消息发送失败?**
|
||||
A: 检查网络连接,登录状态,以及消息内容格式。
|
||||
|
||||
### 10.4 推送问题
|
||||
|
||||
**Q: 推送通知无法接收?**
|
||||
A: 检查推送权限是否开启,证书配置是否正确,设备Token是否正确上传。
|
||||
|
||||
**Q: 推送消息解析错误?**
|
||||
A: 检查推送消息格式,确保解析逻辑正确。
|
||||
|
||||
## 11. 总结
|
||||
|
||||
本项目对NIMSDK的集成非常完整,包括:
|
||||
|
||||
1. **完整的初始化流程**: 从SDK注册到配置参数设置
|
||||
2. **自定义消息处理**: 实现了自定义附件解码器
|
||||
3. **登录状态管理**: 包含自动登录、踢出处理等
|
||||
4. **推送通知集成**: 完整的APNS推送处理
|
||||
5. **消息接收处理**: 支持自定义消息类型的处理
|
||||
6. **多环境配置**: 区分开发和生产环境的配置
|
||||
|
||||
整个集成方案遵循了NIMSDK的最佳实践,代码结构清晰,功能完整,为项目的即时通讯功能提供了坚实的基础。
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: 1.0
|
||||
**最后更新**: 2024年12月
|
||||
**维护人员**: 开发团队
|
||||
|
247
docs/PublicRoomManager_Usage_Guide.md
Normal file
247
docs/PublicRoomManager_Usage_Guide.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# PublicRoomManager 使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
`PublicRoomManager` 是一个常驻单例,负责管理用户进入公共聊天房间的逻辑。它会在用户登录成功后自动初始化,并在用户登出时自动清理。
|
||||
|
||||
## 主要功能
|
||||
|
||||
1. **自动初始化**: 用户登录成功后自动初始化
|
||||
2. **自动进房**: 根据用户的分区ID自动进入对应的公共聊天房间
|
||||
3. **消息监听**: 监听公共房间的消息
|
||||
4. **自动清理**: 用户登出时自动退出房间并清理状态
|
||||
5. **用户切换处理**: 支持多次登出-登录的重置情况
|
||||
|
||||
## 核心特性
|
||||
|
||||
### 1. 生命周期管理
|
||||
|
||||
```objc
|
||||
// 初始化(在用户登录成功后自动调用)
|
||||
[[PublicRoomManager sharedManager] initialize];
|
||||
|
||||
// 重置(在用户登出时自动调用)
|
||||
[[PublicRoomManager sharedManager] reset];
|
||||
```
|
||||
|
||||
### 2. 状态查询
|
||||
|
||||
```objc
|
||||
// 检查是否已初始化
|
||||
BOOL isInitialized = [[PublicRoomManager sharedManager] isInitialized];
|
||||
|
||||
// 检查是否已在公共房间中
|
||||
BOOL isInPublicRoom = [[PublicRoomManager sharedManager] isInPublicRoom];
|
||||
|
||||
// 获取当前公共房间ID
|
||||
NSString *roomId = [[PublicRoomManager sharedManager] currentPublicRoomId];
|
||||
```
|
||||
|
||||
### 3. 手动控制
|
||||
|
||||
```objc
|
||||
// 手动进入公共房间
|
||||
[[PublicRoomManager sharedManager] enterPublicRoomWithCompletion:^(NSError * _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"进入公共房间失败: %@", error);
|
||||
} else {
|
||||
NSLog(@"进入公共房间成功");
|
||||
}
|
||||
}];
|
||||
|
||||
// 手动退出公共房间
|
||||
[[PublicRoomManager sharedManager] exitPublicRoomWithCompletion:^(NSError * _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"退出公共房间失败: %@", error);
|
||||
} else {
|
||||
NSLog(@"退出公共房间成功");
|
||||
}
|
||||
}];
|
||||
```
|
||||
|
||||
## 集成点
|
||||
|
||||
### 1. 登录流程集成
|
||||
|
||||
在 `PILoginManager.m` 的登录成功回调中添加:
|
||||
|
||||
```objc
|
||||
// 初始化公共房间管理器
|
||||
[[PublicRoomManager sharedManager] initialize];
|
||||
```
|
||||
|
||||
### 2. 登出流程集成
|
||||
|
||||
在 `BaseMvpPresenter.m` 的 logout 方法中添加:
|
||||
|
||||
```objc
|
||||
// 重置公共房间管理器
|
||||
[[PublicRoomManager sharedManager] reset];
|
||||
```
|
||||
|
||||
### 3. 用户信息更新集成
|
||||
|
||||
在 `TabbarViewController.m` 的 `getUserInfoSuccess` 方法中添加:
|
||||
|
||||
```objc
|
||||
// 更新公共房间管理器的用户信息
|
||||
[[PublicRoomManager sharedManager] updateUserInfo:userInfo];
|
||||
```
|
||||
|
||||
### 4. 配置更新集成
|
||||
|
||||
在 `ClientConfig.m` 的配置加载完成后添加:
|
||||
|
||||
```objc
|
||||
// 通知公共房间管理器配置已更新
|
||||
[[PublicRoomManager sharedManager] updateConfig];
|
||||
```
|
||||
|
||||
## 工作原理
|
||||
|
||||
### 1. 初始化流程
|
||||
|
||||
1. 检查用户是否已登录
|
||||
2. 检查用户信息是否完整(包含 partitionId)
|
||||
3. 检查配置信息是否已加载(包含 publicChatRoomIdMap)
|
||||
4. 注册云信消息代理
|
||||
5. 根据 partitionId 获取对应的 roomId
|
||||
6. 进入公共聊天房间
|
||||
|
||||
### 2. 进房流程
|
||||
|
||||
1. 创建 NIMChatroomEnterRequest
|
||||
2. 设置用户扩展信息(头像、昵称、等级等)
|
||||
3. 调用云信 SDK 进入房间
|
||||
4. 处理进房成功/失败回调
|
||||
|
||||
### 3. 消息处理
|
||||
|
||||
1. 实现 NIMChatManagerDelegate
|
||||
2. 过滤公共房间消息
|
||||
3. 处理消息内容
|
||||
|
||||
### 4. 清理流程
|
||||
|
||||
1. 退出公共聊天房间
|
||||
2. 移除云信代理
|
||||
3. 重置所有状态
|
||||
|
||||
## 配置要求
|
||||
|
||||
### 1. 用户信息要求
|
||||
|
||||
用户信息必须包含 `partitionId` 字段:
|
||||
|
||||
```objc
|
||||
UserInfoModel *userInfo = [AccountInfoStorage instance].getHomeUserInfo;
|
||||
NSString *partitionId = userInfo.partitionId; // 必须存在
|
||||
```
|
||||
|
||||
### 2. 配置信息要求
|
||||
|
||||
配置信息必须包含 `publicChatRoomIdMap` 字段:
|
||||
|
||||
```objc
|
||||
ClientDataModel *configInfo = [ClientConfig shareConfig].configInfo;
|
||||
NSDictionary *publicChatRoomIdMap = configInfo.publicChatRoomIdMap; // 必须存在
|
||||
```
|
||||
|
||||
`publicChatRoomIdMap` 的格式应该是:
|
||||
|
||||
```json
|
||||
{
|
||||
"1": "roomId1", // 分区1对应的房间ID
|
||||
"2": "roomId2", // 分区2对应的房间ID
|
||||
"3": "roomId3" // 分区3对应的房间ID
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 1. 初始化失败
|
||||
|
||||
- 用户未登录:等待用户登录
|
||||
- 用户信息不完整:等待用户信息更新
|
||||
- 配置信息未加载:等待配置更新
|
||||
|
||||
### 2. 进房失败
|
||||
|
||||
- 网络错误:记录日志,可重试
|
||||
- 房间不存在:记录日志,跳过
|
||||
- 权限不足:记录日志,跳过
|
||||
|
||||
### 3. 用户切换
|
||||
|
||||
- 检测到用户ID变化时自动重置
|
||||
- 清理旧用户状态
|
||||
- 重新初始化新用户
|
||||
|
||||
## 日志输出
|
||||
|
||||
PublicRoomManager 会输出详细的日志信息:
|
||||
|
||||
```
|
||||
PublicRoomManager: 初始化成功,用户ID: 123456, 分区ID: 1
|
||||
PublicRoomManager: 尝试进入公共房间,分区ID: 1, 房间ID: roomId1
|
||||
PublicRoomManager: 进入公共房间成功,房间ID: roomId1
|
||||
PublicRoomManager: 收到公共房间消息: Hello World
|
||||
PublicRoomManager: 检测到用户切换,重置管理器
|
||||
PublicRoomManager: 开始重置
|
||||
PublicRoomManager: 退出公共房间成功
|
||||
PublicRoomManager: 重置完成
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **线程安全**: 所有操作都在主线程执行
|
||||
2. **内存管理**: 使用单例模式,避免内存泄漏
|
||||
3. **错误恢复**: 支持自动重试和错误恢复
|
||||
4. **状态同步**: 确保状态与实际云信状态同步
|
||||
5. **性能优化**: 避免重复初始化和不必要的操作
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 1. 消息处理扩展
|
||||
|
||||
可以在 `onRecvMessages` 方法中添加自定义的消息处理逻辑:
|
||||
|
||||
```objc
|
||||
- (void)onRecvMessages:(NSArray<NIMMessage *> *)messages {
|
||||
for (NIMMessage *message in messages) {
|
||||
if (message.session.sessionType == NIMSessionTypeChatroom) {
|
||||
NSString *sessionId = message.session.sessionId;
|
||||
if ([sessionId isEqualToString:self.currentPublicRoomId]) {
|
||||
// 添加自定义消息处理逻辑
|
||||
[self handlePublicRoomMessage:message];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 状态监听扩展
|
||||
|
||||
可以添加状态变化的通知:
|
||||
|
||||
```objc
|
||||
// 在状态变化时发送通知
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"PublicRoomManagerStateChanged"
|
||||
object:nil
|
||||
userInfo:@{@"isInPublicRoom": @(self.isInPublicRoom)}];
|
||||
```
|
||||
|
||||
### 3. 重试机制扩展
|
||||
|
||||
可以添加更复杂的重试逻辑:
|
||||
|
||||
```objc
|
||||
- (void)retryEnterPublicRoom {
|
||||
if (self.retryCount < 3) {
|
||||
self.retryCount++;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self tryEnterPublicRoom];
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
293
docs/getUserInfo_API_Documentation.md
Normal file
293
docs/getUserInfo_API_Documentation.md
Normal file
@@ -0,0 +1,293 @@
|
||||
# getUserInfo API 使用说明文档
|
||||
|
||||
## 方法概述
|
||||
|
||||
`getUserInfo:uid:` 是 `Api` 类中的一个静态方法,用于获取指定用户的详细信息。
|
||||
|
||||
## 方法签名
|
||||
|
||||
```objc
|
||||
+ (void)getUserInfo:(HttpRequestHelperCompletion)completion uid:(NSString *)uid;
|
||||
```
|
||||
|
||||
## 参数说明
|
||||
|
||||
### 输入参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| completion | HttpRequestHelperCompletion | 是 | 请求完成后的回调函数 |
|
||||
| uid | NSString | 是 | 要查询的用户ID |
|
||||
|
||||
### 回调函数格式
|
||||
|
||||
```objc
|
||||
typedef void(^HttpRequestHelperCompletion)(BaseModel* _Nullable data, NSInteger code, NSString * _Nullable msg);
|
||||
```
|
||||
|
||||
**回调参数说明:**
|
||||
- `data`: BaseModel 对象,包含服务器返回的数据
|
||||
- `code`: NSInteger,HTTP 状态码或业务状态码
|
||||
- `msg`: NSString,服务器返回的消息或错误信息
|
||||
|
||||
## 实现原理
|
||||
|
||||
### 1. API 端点
|
||||
- **Base64 编码的路径**: `dXNlci9nZXQ=`
|
||||
- **解码后的路径**: `user/get`
|
||||
- **请求方法**: GET
|
||||
|
||||
### 2. 请求流程
|
||||
1. 将用户ID作为参数传递给 `makeRequest` 方法
|
||||
2. `makeRequest` 方法通过 `__FUNCTION__` 宏自动解析参数名
|
||||
3. 构造请求参数字典:`@{@"uid": uid}`
|
||||
4. 调用 `HttpRequestHelper` 发送 GET 请求
|
||||
|
||||
### 3. 参数自动映射
|
||||
该方法使用了特殊的参数映射机制:
|
||||
- 通过 `__FUNCTION__` 宏获取方法名
|
||||
- 解析方法名中的参数部分(冒号后的部分)
|
||||
- 自动将传入的参数值与参数名对应
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本用法
|
||||
|
||||
```objc
|
||||
// 获取用户ID为 "12345" 的用户信息
|
||||
[Api getUserInfo:^(BaseModel *data, NSInteger code, NSString *msg) {
|
||||
if (code == 200) {
|
||||
// 请求成功
|
||||
NSLog(@"用户信息: %@", data.data);
|
||||
NSLog(@"消息: %@", msg);
|
||||
} else {
|
||||
// 请求失败
|
||||
NSLog(@"错误码: %ld", (long)code);
|
||||
NSLog(@"错误信息: %@", msg);
|
||||
}
|
||||
} uid:@"12345"];
|
||||
```
|
||||
|
||||
### 错误处理示例
|
||||
|
||||
```objc
|
||||
[Api getUserInfo:^(BaseModel *data, NSInteger code, NSString *msg) {
|
||||
switch (code) {
|
||||
case 200:
|
||||
// 成功获取用户信息
|
||||
[self handleUserInfoSuccess:data.data];
|
||||
break;
|
||||
case 404:
|
||||
// 用户不存在
|
||||
[self showUserNotFoundAlert];
|
||||
break;
|
||||
case 401:
|
||||
// 未授权访问
|
||||
[self handleUnauthorizedAccess];
|
||||
break;
|
||||
default:
|
||||
// 其他错误
|
||||
[self showErrorAlert:msg];
|
||||
break;
|
||||
}
|
||||
} uid:userId];
|
||||
```
|
||||
|
||||
### 在 ViewController 中使用
|
||||
|
||||
```objc
|
||||
@interface UserProfileViewController ()
|
||||
@property (nonatomic, strong) NSString *currentUserId;
|
||||
@end
|
||||
|
||||
@implementation UserProfileViewController
|
||||
|
||||
- (void)loadUserInfo {
|
||||
if (!self.currentUserId) {
|
||||
NSLog(@"用户ID不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
[Api getUserInfo:^(BaseModel *data, NSInteger code, NSString *msg) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (code == 200) {
|
||||
[self updateUIWithUserInfo:data.data];
|
||||
} else {
|
||||
[self showErrorWithMessage:msg];
|
||||
}
|
||||
});
|
||||
} uid:self.currentUserId];
|
||||
}
|
||||
|
||||
- (void)updateUIWithUserInfo:(id)userInfo {
|
||||
// 更新UI显示用户信息
|
||||
// userInfo 的具体结构需要根据后端返回的数据格式来确定
|
||||
}
|
||||
|
||||
- (void)showErrorWithMessage:(NSString *)message {
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"错误"
|
||||
message:message
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
## 返回数据结构
|
||||
|
||||
### BaseModel 结构
|
||||
|
||||
```objc
|
||||
@interface BaseModel : NSObject
|
||||
@property(nonatomic,assign) long timestamp; // 时间戳
|
||||
@property (nonatomic , strong) id data; // 返回的数据
|
||||
@property (nonatomic , assign) NSInteger code; // 状态码
|
||||
@property (nonatomic , copy) NSString *message; // 消息
|
||||
@property (nonatomic,assign) long long cancelDate; // 注销时间戳
|
||||
@property (nonatomic,copy) NSString *date; // 日期
|
||||
@property (nonatomic,copy) NSString *reason; // 封禁理由
|
||||
@end
|
||||
```
|
||||
|
||||
### 成功响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"timestamp": 1640995200000,
|
||||
"data": {
|
||||
"uid": "12345",
|
||||
"nickname": "用户昵称",
|
||||
"avatar": "头像URL",
|
||||
"level": 10,
|
||||
"vip": false
|
||||
// 其他用户信息字段...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 错误响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 404,
|
||||
"message": "用户不存在",
|
||||
"timestamp": 1640995200000,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 线程安全
|
||||
- 回调函数在后台线程执行
|
||||
- UI 更新操作需要在主线程进行
|
||||
|
||||
### 2. 参数验证
|
||||
- 确保 `uid` 参数不为空
|
||||
- 建议在使用前验证 `uid` 的格式
|
||||
|
||||
### 3. 内存管理
|
||||
- 避免在回调中造成循环引用
|
||||
- 使用 `__weak` 修饰符防止内存泄漏
|
||||
|
||||
### 4. 网络状态
|
||||
- 建议在调用前检查网络连接状态
|
||||
- 处理网络超时和连接失败的情况
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 参数验证
|
||||
|
||||
```objc
|
||||
- (void)getUserInfoWithValidation:(NSString *)uid {
|
||||
if (!uid || uid.length == 0) {
|
||||
NSLog(@"用户ID不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证UID格式(根据实际需求调整)
|
||||
if (![self isValidUID:uid]) {
|
||||
NSLog(@"用户ID格式不正确");
|
||||
return;
|
||||
}
|
||||
|
||||
[Api getUserInfo:^(BaseModel *data, NSInteger code, NSString *msg) {
|
||||
// 处理响应
|
||||
} uid:uid];
|
||||
}
|
||||
|
||||
- (BOOL)isValidUID:(NSString *)uid {
|
||||
// 根据实际业务需求验证UID格式
|
||||
return uid.length > 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 错误处理
|
||||
|
||||
```objc
|
||||
- (void)handleApiError:(NSInteger)code message:(NSString *)msg {
|
||||
switch (code) {
|
||||
case 200:
|
||||
// 成功
|
||||
break;
|
||||
case 400:
|
||||
NSLog(@"请求参数错误: %@", msg);
|
||||
break;
|
||||
case 401:
|
||||
NSLog(@"未授权访问,需要重新登录");
|
||||
[self redirectToLogin];
|
||||
break;
|
||||
case 404:
|
||||
NSLog(@"用户不存在: %@", msg);
|
||||
break;
|
||||
case 500:
|
||||
NSLog(@"服务器内部错误: %@", msg);
|
||||
break;
|
||||
default:
|
||||
NSLog(@"未知错误 (code: %ld): %@", (long)code, msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 缓存策略
|
||||
|
||||
```objc
|
||||
- (void)getUserInfoWithCache:(NSString *)uid {
|
||||
// 先检查本地缓存
|
||||
UserInfo *cachedUser = [self getCachedUserInfo:uid];
|
||||
if (cachedUser) {
|
||||
[self updateUIWithUserInfo:cachedUser];
|
||||
}
|
||||
|
||||
// 从服务器获取最新数据
|
||||
[Api getUserInfo:^(BaseModel *data, NSInteger code, NSString *msg) {
|
||||
if (code == 200) {
|
||||
// 更新缓存
|
||||
[self cacheUserInfo:data.data forUID:uid];
|
||||
[self updateUIWithUserInfo:data.data];
|
||||
} else {
|
||||
// 如果服务器请求失败,使用缓存数据(如果有)
|
||||
if (!cachedUser) {
|
||||
[self showErrorWithMessage:msg];
|
||||
}
|
||||
}
|
||||
} uid:uid];
|
||||
}
|
||||
```
|
||||
|
||||
## 相关方法
|
||||
|
||||
- `getUserInfos:uids:` - 批量获取多个用户信息
|
||||
- `completeUserInfo:userInfo:` - 补全用户资料
|
||||
- `getUserWalletInfo:uid:ticket:` - 获取用户钱包信息
|
||||
|
||||
## 版本信息
|
||||
|
||||
- **iOS 最低版本**: iOS 15.6
|
||||
- **创建时间**: 2021/9/6
|
||||
- **最后更新**: 当前版本
|
Reference in New Issue
Block a user