新增公共房间管理器 PublicRoomManager,负责管理用户进入公共聊天房间的逻辑;在 ClientConfig.m 中添加对公共房间管理器的配置更新通知;在多个文件中集成公共房间管理器,确保用户信息更新和状态管理的正确性;更新相关文档以提供使用指南和集成说明。
This commit is contained in:
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -19,6 +19,7 @@
|
||||
"exper",
|
||||
"Headwear",
|
||||
"HWDMP",
|
||||
"ifndef",
|
||||
"Interitem",
|
||||
"kindof",
|
||||
"MAXFLOAT",
|
||||
@@ -27,6 +28,7 @@
|
||||
"Nonnull",
|
||||
"NSEC",
|
||||
"NSURL",
|
||||
"objc",
|
||||
"Offical",
|
||||
"Procotol",
|
||||
"QGVAP",
|
||||
|
@@ -587,6 +587,7 @@
|
||||
4CF67BA52DF9568C00EE5A28 /* BaseModelVo.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CF67BA42DF9568C00EE5A28 /* BaseModelVo.m */; };
|
||||
4CFBE0CA2DAD085700A923AF /* BravoGiftTabInfomationModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CFBE0C92DAD085700A923AF /* BravoGiftTabInfomationModel.m */; };
|
||||
4CFBE0CD2DAD0FC400A923AF /* PIGiftBravoGiftBroadcastView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CFBE0CC2DAD0FC400A923AF /* PIGiftBravoGiftBroadcastView.m */; };
|
||||
4CFE7F422E45ECEC00F77776 /* PublicRoomManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CFE7F402E45ECEC00F77776 /* PublicRoomManager.m */; };
|
||||
4CFFEFCD2D3A4E410035D016 /* AppOfficalManagerActionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CFFEFCC2D3A4E410035D016 /* AppOfficalManagerActionsViewController.m */; };
|
||||
4CFFEFD02D3A5E130035D016 /* Api+SuperAdmin.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CFFEFCF2D3A5E130035D016 /* Api+SuperAdmin.m */; };
|
||||
540EC1D02C89925F00F3BF0D /* GiftComboView.m in Sources */ = {isa = PBXBuildFile; fileRef = 540EC1CF2C89925F00F3BF0D /* GiftComboView.m */; };
|
||||
@@ -2829,6 +2830,8 @@
|
||||
4CFBE0C92DAD085700A923AF /* BravoGiftTabInfomationModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BravoGiftTabInfomationModel.m; sourceTree = "<group>"; };
|
||||
4CFBE0CB2DAD0FC400A923AF /* PIGiftBravoGiftBroadcastView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PIGiftBravoGiftBroadcastView.h; sourceTree = "<group>"; };
|
||||
4CFBE0CC2DAD0FC400A923AF /* PIGiftBravoGiftBroadcastView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PIGiftBravoGiftBroadcastView.m; sourceTree = "<group>"; };
|
||||
4CFE7F3F2E45ECEC00F77776 /* PublicRoomManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PublicRoomManager.h; sourceTree = "<group>"; };
|
||||
4CFE7F402E45ECEC00F77776 /* PublicRoomManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PublicRoomManager.m; sourceTree = "<group>"; };
|
||||
4CFFEFCB2D3A4E410035D016 /* AppOfficalManagerActionsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppOfficalManagerActionsViewController.h; sourceTree = "<group>"; };
|
||||
4CFFEFCC2D3A4E410035D016 /* AppOfficalManagerActionsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppOfficalManagerActionsViewController.m; sourceTree = "<group>"; };
|
||||
4CFFEFCE2D3A5E130035D016 /* Api+SuperAdmin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Api+SuperAdmin.h"; sourceTree = "<group>"; };
|
||||
@@ -6926,6 +6929,15 @@
|
||||
path = gift;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CFE7F412E45ECEC00F77776 /* Manager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CFE7F3F2E45ECEC00F77776 /* PublicRoomManager.h */,
|
||||
4CFE7F402E45ECEC00F77776 /* PublicRoomManager.m */,
|
||||
);
|
||||
path = Manager;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54283CE22CE48884009729B5 /* ShoppingMall */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -10754,6 +10766,7 @@
|
||||
E8AEAED3271412D00017FCE0 /* YMRoom */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CFE7F412E45ECEC00F77776 /* Manager */,
|
||||
54E82E9B2CA684A600C931D9 /* Features */,
|
||||
E804875F2717DD70008595F2 /* Model */,
|
||||
E80487602717DD89008595F2 /* Api */,
|
||||
@@ -13054,6 +13067,7 @@
|
||||
9B85F3532806AB9A006EDF51 /* XPAnchorPKResultView.m in Sources */,
|
||||
E8DEC99527648FA50078CB70 /* ClientConfig.m in Sources */,
|
||||
9B6E8577281ABECC0041A321 /* XPRoomInsideRecommendEmptyCell.m in Sources */,
|
||||
4CFE7F422E45ECEC00F77776 /* PublicRoomManager.m in Sources */,
|
||||
E85E7BC22A4EE82300B6D00A /* XPMineListCell.m in Sources */,
|
||||
E880B3A6278BD69900A83B0D /* XPAcrossRoomPKTableViewCell.m in Sources */,
|
||||
4C1392932D6D963700A6DFB5 /* SubRechargersViewController.m in Sources */,
|
||||
|
@@ -17,6 +17,7 @@
|
||||
#import "XPWeakTimer.h"
|
||||
#import "Api+Main.h"
|
||||
#import "ChatFaceVo.h"
|
||||
#import "PublicRoomManager.h"
|
||||
|
||||
@interface ClientConfig ()
|
||||
///重试的次数 10次 如果你还是失败的话 那就算了 没办法了
|
||||
@@ -105,6 +106,9 @@
|
||||
|
||||
self.configInfo = model;
|
||||
|
||||
// 通知公共房间管理器配置已更新
|
||||
[[PublicRoomManager sharedManager] updateConfig];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"reloadAfterLoadConfig" object:nil];
|
||||
|
||||
[self requestFaceTabNewList];
|
||||
|
@@ -74,7 +74,7 @@ typedef NS_ENUM(NSInteger, FaceLivenessStrategy) {
|
||||
@property (nonatomic, strong) NSArray<NSString *> *officialMsgUids;
|
||||
///官方账号 小秘书 红包消息
|
||||
@property (nonatomic,strong) NSArray<NSString *> *officialAccountUids;
|
||||
//@property(nonatomic,copy) NSDictionary *publicChatRoomIdMap; // 公聊大厅房间 IDs,已不使用该业务逻辑
|
||||
@property(nonatomic,copy) NSDictionary *publicChatRoomIdMap; // 公聊大厅房间 IDs,已不使用该业务逻辑 -> 又要使用了
|
||||
///星座礼物顶部是否开启
|
||||
@property (nonatomic,assign) BOOL twelveStarSwitch;
|
||||
/// 开房是否需要实名
|
||||
@@ -108,6 +108,10 @@ typedef NS_ENUM(NSInteger, FaceLivenessStrategy) {
|
||||
|
||||
@property(nonatomic, assign) BOOL captchaSwitch;
|
||||
|
||||
@property (nonatomic, copy) NSString *sudId;
|
||||
@property (nonatomic, copy) NSString *sudKey;
|
||||
@property (nonatomic, copy) NSString *nimKey;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -19,6 +19,7 @@
|
||||
#import "UserInfoModel.h"
|
||||
#import "XPLoginAuthCodeVC.h"
|
||||
#import "FirstRechargeManager.h"
|
||||
|
||||
@implementation PILoginManager
|
||||
+(void)loginWithVC:(MvpViewController *)VC isLoginPhone:(BOOL)isLoginPhone{
|
||||
[XNDJTDDLoadingTool showLoading];
|
||||
@@ -70,6 +71,7 @@
|
||||
return;
|
||||
}
|
||||
[XNDJTDDLoadingTool showSuccessWithMessage:YMLocalizedString(@"PKIDLoginViewController0")];
|
||||
|
||||
[PILoginManager jumpToHomeVCWithInviteCode:@""];
|
||||
});
|
||||
|
||||
|
@@ -121,21 +121,22 @@ typedef NS_ENUM(NSUInteger, LoginType) {
|
||||
}
|
||||
|
||||
- (void)setupBottomPolicy {
|
||||
|
||||
UIView *view_1 = [[UIView alloc] init];
|
||||
UIView *view_2 = [[UIView alloc] init];
|
||||
UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[
|
||||
view_1,
|
||||
self.agreeButton,
|
||||
self.policyLabel,
|
||||
view_2
|
||||
]];
|
||||
stackView.spacing = 8;
|
||||
stackView.alignment = UIStackViewAlignmentCenter;
|
||||
stackView.distribution = UIStackViewDistributionFill;
|
||||
[self.view addSubview:stackView];
|
||||
[stackView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerX.mas_equalTo(self.view);
|
||||
if (isMSRTL()) {
|
||||
make.width.mas_equalTo(290);
|
||||
}
|
||||
make.bottom.mas_equalTo(self.view).offset(-44);
|
||||
make.height.mas_equalTo(34);
|
||||
make.width.mas_lessThanOrEqualTo(self.view).offset(-32);
|
||||
}];
|
||||
|
||||
[self.agreeButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
@@ -150,7 +151,7 @@ typedef NS_ENUM(NSUInteger, LoginType) {
|
||||
[self.view addSubview:smsLogin];
|
||||
[smsLogin mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerX.mas_equalTo(self.view);
|
||||
make.bottom.mas_equalTo(self.view).offset(-104);
|
||||
make.bottom.mas_equalTo(self.view).offset(-124);
|
||||
make.size.mas_equalTo(CGSizeMake(48, 48));
|
||||
}];
|
||||
}
|
||||
@@ -357,6 +358,9 @@ typedef NS_ENUM(NSUInteger, LoginType) {
|
||||
_policyLabel.numberOfLines = 0;
|
||||
|
||||
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:YMLocalizedString(@"XPLoginViewController6")];
|
||||
if ([[UIScreen mainScreen] bounds].size.width < 450 && (isMSPT() || isMSTR())) {
|
||||
attString = [[NSMutableAttributedString alloc] initWithString:YMLocalizedString(@"XPLoginViewController6.1")];
|
||||
}
|
||||
attString.yy_color = UIColorFromRGB(0x7B7B7D);
|
||||
NSRange userRange = [attString.string rangeOfString:YMLocalizedString(@"XPLoginViewController7")];
|
||||
[attString addAttributes:@{NSForegroundColorAttributeName:UIColorFromRGB(0x313131)} range:userRange];
|
||||
|
260
YuMi/Modules/YMMessage/Tool/NIMSDKManager.h
Normal file
260
YuMi/Modules/YMMessage/Tool/NIMSDKManager.h
Normal file
@@ -0,0 +1,260 @@
|
||||
//
|
||||
// NIMSDKManager.h
|
||||
// YUMI
|
||||
//
|
||||
// Created by YUMI on 2024/12/19.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <NIMSDK/NIMSDK.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// MARK: - NIMSDK配置模型
|
||||
@interface NIMSDKConfigModel : NSObject
|
||||
|
||||
@property (nonatomic, copy) NSString *appKey; // 云信AppKey
|
||||
@property (nonatomic, copy) NSString *apnsCername; // APNS证书名称
|
||||
@property (nonatomic, assign) BOOL shouldConsiderRevokedMessageUnreadCount; // 撤回消息计入未读数
|
||||
@property (nonatomic, assign) BOOL shouldSyncStickTopSessionInfos; // 同步置顶会话信息
|
||||
@property (nonatomic, assign) BOOL enabledHttpsForInfo; // 启用HTTPS信息传输
|
||||
@property (nonatomic, assign) BOOL enabledHttpsForMessage; // 启用HTTPS消息传输
|
||||
|
||||
@end
|
||||
|
||||
// MARK: - NIMSDK登录状态枚举
|
||||
typedef NS_ENUM(NSInteger, NIMSDKLoginStatus) {
|
||||
NIMSDKLoginStatusNotLogin = 0, // 未登录
|
||||
NIMSDKLoginStatusLogging, // 登录中
|
||||
NIMSDKLoginStatusLogined, // 已登录
|
||||
NIMSDKLoginStatusLogout, // 已登出
|
||||
NIMSDKLoginStatusKickout, // 被踢出
|
||||
NIMSDKLoginStatusAutoLoginFailed // 自动登录失败
|
||||
};
|
||||
|
||||
// MARK: - NIMSDK登录回调
|
||||
typedef void(^NIMSDKLoginCompletion)(NSError * _Nullable error);
|
||||
typedef void(^NIMSDKLogoutCompletion)(NSError * _Nullable error);
|
||||
typedef void(^NIMSDKStatusChangeBlock)(NIMSDKLoginStatus status);
|
||||
|
||||
// MARK: - NIMSDKManager代理协议
|
||||
@protocol NIMSDKManagerDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
// 登录状态变化
|
||||
- (void)nimSDKManager:(id)manager didChangeLoginStatus:(NIMSDKLoginStatus)status;
|
||||
// 自动登录失败
|
||||
- (void)nimSDKManager:(id)manager didAutoLoginFailed:(NSError *)error;
|
||||
// 被踢出
|
||||
- (void)nimSDKManager:(id)manager didKickout:(NIMLoginKickoutResult *)result;
|
||||
// 收到消息
|
||||
- (void)nimSDKManager:(id)manager didReceiveMessages:(NSArray<NIMMessage *> *)messages;
|
||||
// 收到广播消息
|
||||
- (void)nimSDKManager:(id)manager didReceiveBroadcastMessage:(NIMBroadcastMessage *)broadcastMessage;
|
||||
|
||||
@end
|
||||
|
||||
// MARK: - NIMSDKManager主类
|
||||
@interface NIMSDKManager : NSObject
|
||||
|
||||
// MARK: - 单例方法
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
// MARK: - 配置和初始化
|
||||
/**
|
||||
配置NIMSDK
|
||||
@param config 配置模型
|
||||
*/
|
||||
- (void)configureWithConfig:(NIMSDKConfigModel *)config;
|
||||
|
||||
/**
|
||||
初始化NIMSDK
|
||||
@param completion 完成回调
|
||||
*/
|
||||
- (void)initializeWithCompletion:(void(^)(NSError * _Nullable error))completion;
|
||||
|
||||
/**
|
||||
注册自定义消息解码器
|
||||
@param decoder 解码器实例
|
||||
*/
|
||||
- (void)registerCustomDecoder:(id<NIMCustomAttachmentCoding>)decoder;
|
||||
|
||||
// MARK: - 登录管理
|
||||
/**
|
||||
登录
|
||||
@param account 账号
|
||||
@param token 登录token
|
||||
@param completion 完成回调
|
||||
*/
|
||||
- (void)loginWithAccount:(NSString *)account
|
||||
token:(NSString *)token
|
||||
completion:(NIMSDKLoginCompletion)completion;
|
||||
|
||||
/**
|
||||
自动登录
|
||||
@param data 自动登录数据
|
||||
@param completion 完成回调
|
||||
*/
|
||||
- (void)autoLoginWithData:(NSDictionary *)data
|
||||
completion:(NIMSDKLoginCompletion)completion;
|
||||
|
||||
/**
|
||||
登出
|
||||
@param completion 完成回调
|
||||
*/
|
||||
- (void)logoutWithCompletion:(NIMSDKLogoutCompletion)completion;
|
||||
|
||||
/**
|
||||
强制登出
|
||||
@param completion 完成回调
|
||||
*/
|
||||
- (void)forceLogoutWithCompletion:(NIMSDKLogoutCompletion)completion;
|
||||
|
||||
// MARK: - 状态查询
|
||||
/**
|
||||
获取当前登录状态
|
||||
@return 登录状态
|
||||
*/
|
||||
- (NIMSDKLoginStatus)currentLoginStatus;
|
||||
|
||||
/**
|
||||
是否已登录
|
||||
@return 是否已登录
|
||||
*/
|
||||
- (BOOL)isLogined;
|
||||
|
||||
/**
|
||||
获取当前登录账号
|
||||
@return 当前登录账号
|
||||
*/
|
||||
- (NSString *)currentAccount;
|
||||
|
||||
// MARK: - 推送管理
|
||||
/**
|
||||
更新APNS设备Token
|
||||
@param deviceToken 设备Token
|
||||
*/
|
||||
- (void)updateApnsToken:(NSData *)deviceToken;
|
||||
|
||||
/**
|
||||
处理推送消息
|
||||
@param userInfo 推送消息内容
|
||||
@return 是否处理成功
|
||||
*/
|
||||
- (BOOL)handlePushNotification:(NSDictionary *)userInfo;
|
||||
|
||||
// MARK: - 代理管理
|
||||
/**
|
||||
添加代理
|
||||
@param delegate 代理对象
|
||||
*/
|
||||
- (void)addDelegate:(id<NIMSDKManagerDelegate>)delegate;
|
||||
|
||||
/**
|
||||
移除代理
|
||||
@param delegate 代理对象
|
||||
*/
|
||||
- (void)removeDelegate:(id<NIMSDKManagerDelegate>)delegate;
|
||||
|
||||
/**
|
||||
设置登录状态变化回调
|
||||
@param block 回调block
|
||||
*/
|
||||
- (void)setLoginStatusChangeBlock:(NIMSDKStatusChangeBlock)block;
|
||||
|
||||
// MARK: - 消息管理
|
||||
/**
|
||||
发送消息
|
||||
@param message 消息对象
|
||||
@param session 会话对象
|
||||
@param completion 完成回调
|
||||
*/
|
||||
- (void)sendMessage:(NIMMessage *)message
|
||||
toSession:(NIMSession *)session
|
||||
completion:(void(^)(NSError * _Nullable error))completion;
|
||||
|
||||
/**
|
||||
获取未读消息数
|
||||
@return 未读消息数
|
||||
*/
|
||||
- (NSInteger)unreadMessageCount;
|
||||
|
||||
/**
|
||||
获取所有会话
|
||||
@return 会话列表
|
||||
*/
|
||||
- (NSArray<NIMRecentSession *> *)allRecentSessions;
|
||||
|
||||
// MARK: - 用户管理
|
||||
/**
|
||||
获取用户信息
|
||||
@param userId 用户ID
|
||||
@return 用户信息
|
||||
*/
|
||||
- (NIMUser *)userInfo:(NSString *)userId;
|
||||
|
||||
/**
|
||||
获取用户信息(异步)
|
||||
@param userIds 用户ID数组
|
||||
@param completion 完成回调
|
||||
*/
|
||||
- (void)fetchUserInfos:(NSArray<NSString *> *)userIds
|
||||
completion:(void(^)(NSArray<NIMUser *> * _Nullable users, NSError * _Nullable error))completion;
|
||||
|
||||
// MARK: - 聊天室管理
|
||||
/**
|
||||
进入聊天室
|
||||
@param roomId 房间ID
|
||||
@param completion 完成回调
|
||||
*/
|
||||
- (void)enterChatroom:(NSString *)roomId
|
||||
completion:(void(^)(NSError * _Nullable error))completion;
|
||||
|
||||
/**
|
||||
退出聊天室
|
||||
@param roomId 房间ID
|
||||
@param completion 完成回调
|
||||
*/
|
||||
- (void)exitChatroom:(NSString *)roomId
|
||||
completion:(void(^)(NSError * _Nullable error))completion;
|
||||
|
||||
// MARK: - 工具方法
|
||||
/**
|
||||
创建自定义消息
|
||||
@param attachment 自定义附件
|
||||
@return 消息对象
|
||||
*/
|
||||
- (NIMMessage *)createCustomMessageWithAttachment:(id<NIMCustomAttachment>)attachment;
|
||||
|
||||
/**
|
||||
创建文本消息
|
||||
@param text 文本内容
|
||||
@return 消息对象
|
||||
*/
|
||||
- (NIMMessage *)createTextMessage:(NSString *)text;
|
||||
|
||||
/**
|
||||
创建图片消息
|
||||
@param image 图片对象
|
||||
@return 消息对象
|
||||
*/
|
||||
- (NIMMessage *)createImageMessage:(UIImage *)image;
|
||||
|
||||
/**
|
||||
创建音频消息
|
||||
@param filePath 音频文件路径
|
||||
@param duration 音频时长
|
||||
@return 消息对象
|
||||
*/
|
||||
- (NIMMessage *)createAudioMessage:(NSString *)filePath duration:(NSTimeInterval)duration;
|
||||
|
||||
// MARK: - 清理方法
|
||||
/**
|
||||
清理资源
|
||||
*/
|
||||
- (void)cleanup;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
406
YuMi/Modules/YMMessage/Tool/NIMSDKManager.m
Normal file
406
YuMi/Modules/YMMessage/Tool/NIMSDKManager.m
Normal file
@@ -0,0 +1,406 @@
|
||||
//
|
||||
// NIMSDKManager.m
|
||||
// YUMI
|
||||
//
|
||||
// Created by YUMI on 2024/12/19.
|
||||
//
|
||||
|
||||
#import "NIMSDKManager.h"
|
||||
#import "CustomAttachmentDecoder.h"
|
||||
#import "YUMIConstant.h"
|
||||
|
||||
@interface NIMSDKManager () <NIMLoginManagerDelegate, NIMChatManagerDelegate, NIMSystemNotificationManagerDelegate, NIMBroadcastManagerDelegate>
|
||||
|
||||
@property (nonatomic, strong) NIMSDKConfigModel *config;
|
||||
@property (nonatomic, assign) NIMSDKLoginStatus loginStatus;
|
||||
@property (nonatomic, strong) NSMutableArray<id<NIMSDKManagerDelegate>> *delegates;
|
||||
@property (nonatomic, copy) NIMSDKStatusChangeBlock statusChangeBlock;
|
||||
@property (nonatomic, assign) BOOL isInitialized;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NIMSDKManager
|
||||
|
||||
#pragma mark - 单例方法
|
||||
|
||||
+ (instancetype)sharedManager {
|
||||
static NIMSDKManager *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[NIMSDKManager alloc] init];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_delegates = [NSMutableArray array];
|
||||
_loginStatus = NIMSDKLoginStatusNotLogin;
|
||||
_isInitialized = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - 配置和初始化
|
||||
|
||||
- (void)configureWithConfig:(NIMSDKConfigModel *)config {
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
- (void)initializeWithCompletion:(void(^)(NSError * _Nullable error))completion {
|
||||
if (self.isInitialized) {
|
||||
if (completion) {
|
||||
completion(nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查配置
|
||||
if (!self.config || !self.config.appKey) {
|
||||
NSError *error = [NSError errorWithDomain:@"NIMSDKManager"
|
||||
code:-1
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"NIMSDK配置不完整"}];
|
||||
if (completion) {
|
||||
completion(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建SDK选项
|
||||
NIMSDKOption *option = [NIMSDKOption optionWithAppKey:self.config.appKey];
|
||||
if (self.config.apnsCername) {
|
||||
option.apnsCername = self.config.apnsCername;
|
||||
}
|
||||
|
||||
// 注册SDK
|
||||
[[NIMSDK sharedSDK] registerWithOption:option];
|
||||
|
||||
// 注册自定义解码器
|
||||
[self registerCustomDecoder:[[CustomAttachmentDecoder alloc] init]];
|
||||
|
||||
// 配置SDK参数
|
||||
[NIMSDKConfig sharedConfig].shouldConsiderRevokedMessageUnreadCount = self.config.shouldConsiderRevokedMessageUnreadCount;
|
||||
[[NIMSDKConfig sharedConfig] setShouldSyncStickTopSessionInfos:self.config.shouldSyncStickTopSessionInfos];
|
||||
[NIMSDKConfig sharedConfig].enabledHttpsForInfo = self.config.enabledHttpsForInfo;
|
||||
[NIMSDKConfig sharedConfig].enabledHttpsForMessage = self.config.enabledHttpsForMessage;
|
||||
|
||||
// 添加代理
|
||||
[[NIMSDK sharedSDK].loginManager addDelegate:self];
|
||||
[[NIMSDK sharedSDK].chatManager addDelegate:self];
|
||||
[[NIMSDK sharedSDK].systemNotificationManager addDelegate:self];
|
||||
[[NIMSDK sharedSDK].broadcastManager addDelegate:self];
|
||||
|
||||
self.isInitialized = YES;
|
||||
|
||||
if (completion) {
|
||||
completion(nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)registerCustomDecoder:(id<NIMCustomAttachmentCoding>)decoder {
|
||||
[NIMCustomObject registerCustomDecoder:decoder];
|
||||
}
|
||||
|
||||
#pragma mark - 登录管理
|
||||
|
||||
- (void)loginWithAccount:(NSString *)account
|
||||
token:(NSString *)token
|
||||
completion:(NIMSDKLoginCompletion)completion {
|
||||
|
||||
if (!self.isInitialized) {
|
||||
NSError *error = [NSError errorWithDomain:@"NIMSDKManager"
|
||||
code:-2
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"NIMSDK未初始化"}];
|
||||
if (completion) {
|
||||
completion(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
self.loginStatus = NIMSDKLoginStatusLogging;
|
||||
[self notifyStatusChange];
|
||||
|
||||
[[NIMSDK sharedSDK].loginManager login:account
|
||||
token:token
|
||||
completion:^(NSError * _Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (error) {
|
||||
self.loginStatus = NIMSDKLoginStatusNotLogin;
|
||||
} else {
|
||||
self.loginStatus = NIMSDKLoginStatusLogined;
|
||||
}
|
||||
[self notifyStatusChange];
|
||||
|
||||
if (completion) {
|
||||
completion(error);
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)autoLoginWithData:(NSDictionary *)data
|
||||
completion:(NIMSDKLoginCompletion)completion {
|
||||
|
||||
if (!self.isInitialized) {
|
||||
NSError *error = [NSError errorWithDomain:@"NIMSDKManager"
|
||||
code:-2
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"NIMSDK未初始化"}];
|
||||
if (completion) {
|
||||
completion(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
self.loginStatus = NIMSDKLoginStatusLogging;
|
||||
[self notifyStatusChange];
|
||||
|
||||
[[NIMSDK sharedSDK].loginManager autoLogin:data];
|
||||
|
||||
// 注意:自动登录的结果通过代理方法回调
|
||||
// 这里暂时返回成功,实际结果通过代理通知
|
||||
if (completion) {
|
||||
completion(nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)logoutWithCompletion:(NIMSDKLogoutCompletion)completion {
|
||||
[[NIMSDK sharedSDK].loginManager logout:^(NSError * _Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (!error) {
|
||||
self.loginStatus = NIMSDKLoginStatusLogout;
|
||||
[self notifyStatusChange];
|
||||
}
|
||||
|
||||
if (completion) {
|
||||
completion(error);
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)forceLogoutWithCompletion:(NIMSDKLogoutCompletion)completion {
|
||||
// 强制登出,清理所有状态
|
||||
[[NIMSDK sharedSDK].loginManager logout:^(NSError * _Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.loginStatus = NIMSDKLoginStatusLogout;
|
||||
[self notifyStatusChange];
|
||||
|
||||
if (completion) {
|
||||
completion(error);
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - 状态查询
|
||||
|
||||
- (NIMSDKLoginStatus)currentLoginStatus {
|
||||
return self.loginStatus;
|
||||
}
|
||||
|
||||
- (BOOL)isLogined {
|
||||
return [[NIMSDK sharedSDK].loginManager isLogined];
|
||||
}
|
||||
|
||||
- (NSString *)currentAccount {
|
||||
return [[NIMSDK sharedSDK].loginManager currentAccount];
|
||||
}
|
||||
|
||||
#pragma mark - 推送管理
|
||||
|
||||
- (void)updateApnsToken:(NSData *)deviceToken {
|
||||
[[NIMSDK sharedSDK] updateApnsToken:deviceToken];
|
||||
}
|
||||
|
||||
- (BOOL)handlePushNotification:(NSDictionary *)userInfo {
|
||||
// 处理推送消息的逻辑
|
||||
// 这里可以根据具体的推送格式进行解析和处理
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - 代理管理
|
||||
|
||||
- (void)addDelegate:(id<NIMSDKManagerDelegate>)delegate {
|
||||
if (delegate && ![self.delegates containsObject:delegate]) {
|
||||
[self.delegates addObject:delegate];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeDelegate:(id<NIMSDKManagerDelegate>)delegate {
|
||||
[self.delegates removeObject:delegate];
|
||||
}
|
||||
|
||||
- (void)setLoginStatusChangeBlock:(NIMSDKStatusChangeBlock)block {
|
||||
self.statusChangeBlock = block;
|
||||
}
|
||||
|
||||
#pragma mark - 消息管理
|
||||
|
||||
- (void)sendMessage:(NIMMessage *)message
|
||||
toSession:(NIMSession *)session
|
||||
completion:(void(^)(NSError * _Nullable error))completion {
|
||||
[[NIMSDK sharedSDK].chatManager sendMessage:message
|
||||
toSession:session
|
||||
error:nil];
|
||||
// 注意:实际发送结果通过代理方法回调
|
||||
if (completion) {
|
||||
completion(nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)unreadMessageCount {
|
||||
return [[NIMSDK sharedSDK].conversationManager allUnreadCount];
|
||||
}
|
||||
|
||||
- (NSArray<NIMRecentSession *> *)allRecentSessions {
|
||||
return [[NIMSDK sharedSDK].conversationManager allRecentSessions];
|
||||
}
|
||||
|
||||
#pragma mark - 用户管理
|
||||
|
||||
- (NIMUser *)userInfo:(NSString *)userId {
|
||||
return [[NIMSDK sharedSDK].userManager userInfo:userId];
|
||||
}
|
||||
|
||||
- (void)fetchUserInfos:(NSArray<NSString *> *)userIds
|
||||
completion:(void(^)(NSArray<NIMUser *> * _Nullable users, NSError * _Nullable error))completion {
|
||||
[[NIMSDK sharedSDK].userManager fetchUserInfos:userIds
|
||||
completion:completion];
|
||||
}
|
||||
|
||||
#pragma mark - 聊天室管理
|
||||
|
||||
- (void)enterChatroom:(NSString *)roomId
|
||||
completion:(void(^)(NSError * _Nullable error))completion {
|
||||
[[NIMSDK sharedSDK].chatroomManager enterChatroom:roomId
|
||||
completion:completion];
|
||||
}
|
||||
|
||||
- (void)exitChatroom:(NSString *)roomId
|
||||
completion:(void(^)(NSError * _Nullable error))completion {
|
||||
[[NIMSDK sharedSDK].chatroomManager exitChatroom:roomId
|
||||
completion:completion];
|
||||
}
|
||||
|
||||
#pragma mark - 工具方法
|
||||
|
||||
- (NIMMessage *)createCustomMessageWithAttachment:(id<NIMCustomAttachment>)attachment {
|
||||
NIMCustomObject *customObject = [[NIMCustomObject alloc] init];
|
||||
customObject.attachment = attachment;
|
||||
|
||||
NIMMessage *message = [[NIMMessage alloc] init];
|
||||
message.messageObject = customObject;
|
||||
return message;
|
||||
}
|
||||
|
||||
- (NIMMessage *)createTextMessage:(NSString *)text {
|
||||
return [[NIMMessage alloc] initWithText:text];
|
||||
}
|
||||
|
||||
- (NIMMessage *)createImageMessage:(UIImage *)image {
|
||||
NIMImageObject *imageObject = [[NIMImageObject alloc] initWithImage:image];
|
||||
NIMMessage *message = [[NIMMessage alloc] init];
|
||||
message.messageObject = imageObject;
|
||||
return message;
|
||||
}
|
||||
|
||||
- (NIMMessage *)createAudioMessage:(NSString *)filePath duration:(NSTimeInterval)duration {
|
||||
NIMAudioObject *audioObject = [[NIMAudioObject alloc] initWithSourcePath:filePath];
|
||||
audioObject.duration = duration;
|
||||
|
||||
NIMMessage *message = [[NIMMessage alloc] init];
|
||||
message.messageObject = audioObject;
|
||||
return message;
|
||||
}
|
||||
|
||||
#pragma mark - 清理方法
|
||||
|
||||
- (void)cleanup {
|
||||
// 移除代理
|
||||
[[NIMSDK sharedSDK].loginManager removeDelegate:self];
|
||||
[[NIMSDK sharedSDK].chatManager removeDelegate:self];
|
||||
[[NIMSDK sharedSDK].systemNotificationManager removeDelegate:self];
|
||||
[[NIMSDK sharedSDK].broadcastManager removeDelegate:self];
|
||||
|
||||
// 清理状态
|
||||
self.loginStatus = NIMSDKLoginStatusNotLogin;
|
||||
self.isInitialized = NO;
|
||||
[self.delegates removeAllObjects];
|
||||
self.statusChangeBlock = nil;
|
||||
}
|
||||
|
||||
#pragma mark - 私有方法
|
||||
|
||||
- (void)notifyStatusChange {
|
||||
// 通知代理
|
||||
for (id<NIMSDKManagerDelegate> delegate in self.delegates) {
|
||||
if ([delegate respondsToSelector:@selector(nimSDKManager:didChangeLoginStatus:)]) {
|
||||
[delegate nimSDKManager:self didChangeLoginStatus:self.loginStatus];
|
||||
}
|
||||
}
|
||||
|
||||
// 通知block
|
||||
if (self.statusChangeBlock) {
|
||||
self.statusChangeBlock(self.loginStatus);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - NIMLoginManagerDelegate
|
||||
|
||||
- (void)onAutoLoginFailed:(NSError *)error {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.loginStatus = NIMSDKLoginStatusAutoLoginFailed;
|
||||
[self notifyStatusChange];
|
||||
|
||||
// 通知代理
|
||||
for (id<NIMSDKManagerDelegate> delegate in self.delegates) {
|
||||
if ([delegate respondsToSelector:@selector(nimSDKManager:didAutoLoginFailed:)]) {
|
||||
[delegate nimSDKManager:self didAutoLoginFailed:error];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)onKickout:(NIMLoginKickoutResult *)result {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.loginStatus = NIMSDKLoginStatusKickout;
|
||||
[self notifyStatusChange];
|
||||
|
||||
// 通知代理
|
||||
for (id<NIMSDKManagerDelegate> delegate in self.delegates) {
|
||||
if ([delegate respondsToSelector:@selector(nimSDKManager:didKickout:)]) {
|
||||
[delegate nimSDKManager:self didKickout:result];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - NIMChatManagerDelegate
|
||||
|
||||
- (void)onRecvMessages:(NSArray<NIMMessage *> *)messages {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// 通知代理
|
||||
for (id<NIMSDKManagerDelegate> delegate in self.delegates) {
|
||||
if ([delegate respondsToSelector:@selector(nimSDKManager:didReceiveMessages:)]) {
|
||||
[delegate nimSDKManager:self didReceiveMessages:messages];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - NIMBroadcastManagerDelegate
|
||||
|
||||
- (void)onReceiveBroadcastMessage:(NIMBroadcastMessage *)broadcastMessage {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// 通知代理
|
||||
for (id<NIMSDKManagerDelegate> delegate in self.delegates) {
|
||||
if ([delegate respondsToSelector:@selector(nimSDKManager:didReceiveBroadcastMessage:)]) {
|
||||
[delegate nimSDKManager:self didReceiveBroadcastMessage:broadcastMessage];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -42,7 +42,7 @@
|
||||
}
|
||||
|
||||
- (void)setupUI {
|
||||
_backgroundImage = [[UIImageView alloc] init];
|
||||
_backgroundImage = [[UIImageView alloc] initWithImage:kImage(@"cp_bg")];
|
||||
self.backgroundImage.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[self addSubview:self.backgroundImage];
|
||||
[self.backgroundImage mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
|
73
YuMi/Modules/YMRoom/Manager/PublicRoomManager.h
Normal file
73
YuMi/Modules/YMRoom/Manager/PublicRoomManager.h
Normal file
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// PublicRoomManager.h
|
||||
// YUMI
|
||||
//
|
||||
// Created by YUMI on 2024/12/19.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <NIMSDK/NIMSDK.h>
|
||||
|
||||
@class UserInfoModel;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* 公共聊天房间管理器
|
||||
* 负责管理用户进入公共聊天房间的逻辑
|
||||
*/
|
||||
@interface PublicRoomManager : NSObject
|
||||
|
||||
#pragma mark - 单例方法
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
#pragma mark - 生命周期管理
|
||||
/**
|
||||
* 初始化公共房间管理器
|
||||
* 在用户登录成功后调用
|
||||
*/
|
||||
- (void)initialize;
|
||||
|
||||
/**
|
||||
* 重置公共房间管理器
|
||||
* 在用户登出时调用,清理所有状态
|
||||
*/
|
||||
- (void)reset;
|
||||
|
||||
#pragma mark - 状态查询
|
||||
/**
|
||||
* 是否已初始化
|
||||
*/
|
||||
- (BOOL)isInitialized;
|
||||
|
||||
/**
|
||||
* 是否已进入公共房间
|
||||
*/
|
||||
- (BOOL)isInPublicRoom;
|
||||
|
||||
/**
|
||||
* 获取当前公共房间ID
|
||||
*/
|
||||
- (NSString *)currentPublicRoomId;
|
||||
|
||||
// 更新来自 config 的数据
|
||||
- (void)updateConfig;
|
||||
|
||||
- (void)updateUserInfo:(UserInfoModel *)userInfo;
|
||||
|
||||
#pragma mark - 手动控制
|
||||
/**
|
||||
* 手动进入公共房间
|
||||
* @param completion 完成回调
|
||||
*/
|
||||
- (void)enterPublicRoomWithCompletion:(void(^)(NSError * _Nullable error))completion;
|
||||
|
||||
/**
|
||||
* 手动退出公共房间
|
||||
* @param completion 完成回调
|
||||
*/
|
||||
- (void)exitPublicRoomWithCompletion:(void(^)(NSError * _Nullable error))completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
346
YuMi/Modules/YMRoom/Manager/PublicRoomManager.m
Normal file
346
YuMi/Modules/YMRoom/Manager/PublicRoomManager.m
Normal file
@@ -0,0 +1,346 @@
|
||||
//
|
||||
// PublicRoomManager.m
|
||||
// YUMI
|
||||
//
|
||||
// Created by YUMI on 2024/12/19.
|
||||
//
|
||||
|
||||
#import "PublicRoomManager.h"
|
||||
#import "UserInfoModel.h"
|
||||
#import "ClientConfig.h"
|
||||
#import "AccountInfoStorage.h"
|
||||
#import "XPMessageRemoteExtModel.h"
|
||||
|
||||
@interface PublicRoomManager () <NIMChatManagerDelegate>
|
||||
|
||||
@property (nonatomic, assign) BOOL isInitialized;
|
||||
@property (nonatomic, assign) BOOL isInPublicRoom;
|
||||
@property (nonatomic, copy) NSString *currentPublicRoomId;
|
||||
@property (nonatomic, copy) NSString *currentUserId;
|
||||
@property (nonatomic, strong) UserInfoModel *userInfo;
|
||||
|
||||
@end
|
||||
|
||||
@implementation PublicRoomManager
|
||||
|
||||
#pragma mark - 单例方法
|
||||
|
||||
+ (instancetype)sharedManager {
|
||||
static dispatch_once_t onceToken;
|
||||
static PublicRoomManager *instance;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[self alloc] init];
|
||||
[instance initialize];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_isInitialized = NO;
|
||||
_isInPublicRoom = NO;
|
||||
_currentPublicRoomId = nil;
|
||||
_currentUserId = nil;
|
||||
_userInfo = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - 生命周期管理
|
||||
|
||||
- (void)initialize {
|
||||
// 防止重复初始化
|
||||
if (self.isInitialized) {
|
||||
NSLog(@"PublicRoomManager: 已经初始化,跳过重复初始化");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查用户是否已登录
|
||||
// NSString *uid = [AccountInfoStorage instance].getUid;
|
||||
// if (uid.length == 0) {
|
||||
// NSLog(@"PublicRoomManager: 用户未登录,无法初始化");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 检查是否已获取到用户信息
|
||||
// UserInfoModel *userInfo = [AccountInfoStorage instance].getHomeUserInfo;
|
||||
// if (!userInfo || !userInfo.partitionId) {
|
||||
// NSLog(@"PublicRoomManager: 用户信息不完整,等待用户信息更新");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 检查配置信息是否已加载
|
||||
// ClientDataModel *configInfo = [ClientConfig shareConfig].configInfo;
|
||||
// if (!configInfo || !configInfo.publicChatRoomIdMap) {
|
||||
// NSLog(@"PublicRoomManager: 配置信息未加载,等待配置更新");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 保存用户信息
|
||||
// self.userInfo = userInfo;
|
||||
// self.currentUserId = uid;
|
||||
|
||||
// 注册云信代理
|
||||
[[NIMSDK sharedSDK].chatManager addDelegate:self];
|
||||
|
||||
// 标记为已初始化
|
||||
self.isInitialized = YES;
|
||||
|
||||
// NSLog(@"PublicRoomManager: 初始化成功,用户ID: %@, 分区ID: %@", uid, userInfo.partitionId);
|
||||
|
||||
// 尝试进入公共房间
|
||||
// [self tryEnterPublicRoom];
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
NSLog(@"PublicRoomManager: 开始重置");
|
||||
|
||||
// 退出公共房间
|
||||
if (self.isInPublicRoom && self.currentPublicRoomId) {
|
||||
[self exitPublicRoomWithCompletion:^(NSError * _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"PublicRoomManager: 退出公共房间失败: %@", error);
|
||||
} else {
|
||||
NSLog(@"PublicRoomManager: 退出公共房间成功");
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
// 移除云信代理
|
||||
[[NIMSDK sharedSDK].chatManager removeDelegate:self];
|
||||
|
||||
// 重置状态
|
||||
self.isInitialized = NO;
|
||||
self.isInPublicRoom = NO;
|
||||
self.currentPublicRoomId = nil;
|
||||
self.currentUserId = nil;
|
||||
self.userInfo = nil;
|
||||
|
||||
NSLog(@"PublicRoomManager: 重置完成");
|
||||
}
|
||||
|
||||
#pragma mark - 状态查询
|
||||
|
||||
- (BOOL)isInitialized {
|
||||
return _isInitialized;
|
||||
}
|
||||
|
||||
- (BOOL)isInPublicRoom {
|
||||
return _isInPublicRoom;
|
||||
}
|
||||
|
||||
- (NSString *)currentPublicRoomId {
|
||||
return _currentPublicRoomId;
|
||||
}
|
||||
|
||||
#pragma mark - 私有方法
|
||||
|
||||
- (void)tryEnterPublicRoom {
|
||||
if (!self.isInitialized || !self.userInfo) {
|
||||
NSLog(@"PublicRoomManager: 未初始化或用户信息缺失,无法进入公共房间");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取公共房间ID
|
||||
NSString *partitionId = self.userInfo.partitionId;
|
||||
ClientDataModel *configInfo = [ClientConfig shareConfig].configInfo;
|
||||
NSDictionary *publicChatRoomIdMap = configInfo.publicChatRoomIdMap;
|
||||
|
||||
if (!publicChatRoomIdMap || !partitionId) {
|
||||
NSLog(@"PublicRoomManager: 公共房间配置或分区ID缺失");
|
||||
return;
|
||||
}
|
||||
|
||||
NSNumber *roomId = publicChatRoomIdMap[partitionId];
|
||||
if (!roomId) {
|
||||
NSLog(@"PublicRoomManager: 未找到分区 %@ 对应的公共房间ID", partitionId);
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"PublicRoomManager: 尝试进入公共房间,分区ID: %@, 房间ID: %@", partitionId, roomId);
|
||||
|
||||
// 进入公共房间
|
||||
[self enterPublicRoomWithRoomId:roomId.stringValue completion:^(NSError * _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"PublicRoomManager: 进入公共房间失败: %@", error);
|
||||
} else {
|
||||
NSLog(@"PublicRoomManager: 进入公共房间成功");
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)enterPublicRoomWithRoomId:(NSString *)roomId completion:(void(^)(NSError * _Nullable error))completion {
|
||||
if (!self.userInfo) {
|
||||
NSError *error = [NSError errorWithDomain:@"PublicRoomManager"
|
||||
code:-1
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"用户信息缺失"}];
|
||||
if (completion) {
|
||||
completion(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建进房请求
|
||||
NIMChatroomEnterRequest *request = [[NIMChatroomEnterRequest alloc] init];
|
||||
request.roomId = roomId;
|
||||
|
||||
// 设置扩展信息
|
||||
XPMessageRemoteExtModel *extModel = [[XPMessageRemoteExtModel alloc] init];
|
||||
extModel.defUser = self.userInfo.defUser;
|
||||
extModel.erbanNo = self.userInfo.erbanNo;
|
||||
extModel.carName = self.userInfo.carName;
|
||||
extModel.inRoomNameplatePic = self.userInfo.nameplatePic;
|
||||
extModel.inRoomNameplateWord = self.userInfo.nameplateWord;
|
||||
extModel.isCustomWord = self.userInfo.isCustomWord;
|
||||
extModel.charmUrl = self.userInfo.userLevelVo.charmUrl;
|
||||
extModel.experLevelSeq = self.userInfo.userLevelVo.experLevelSeq;
|
||||
extModel.experUrl = self.userInfo.userLevelVo.experUrl;
|
||||
extModel.newUser = self.userInfo.newUser;
|
||||
extModel.vipIcon = self.userInfo.userVipInfoVO.nameplateUrl;
|
||||
extModel.iosBubbleUrl = self.userInfo.iosBubbleUrl;
|
||||
extModel.androidBubbleUrl = self.userInfo.androidBubbleUrl;
|
||||
extModel.enterHide = self.userInfo.userVipInfoVO.enterHide;
|
||||
extModel.preventKick = self.userInfo.userVipInfoVO.preventKick;
|
||||
extModel.enterRoomEffects = self.userInfo.userVipInfoVO.enterRoomEffects;
|
||||
extModel.gender = self.userInfo.gender;
|
||||
extModel.fromSayHelloChannel = self.userInfo.fromSayHelloChannel;
|
||||
extModel.platformRole = self.userInfo.platformRole;
|
||||
extModel.nick = self.userInfo.nick;
|
||||
|
||||
NSMutableDictionary *ext = [NSMutableDictionary dictionaryWithObject:extModel.model2dictionary
|
||||
forKey:[NSString stringWithFormat:@"%ld", self.userInfo.uid]];
|
||||
request.roomExt = [ext toJSONString];
|
||||
|
||||
// 进入房间
|
||||
@kWeakify(self);
|
||||
[[NIMSDK sharedSDK].chatroomManager enterChatroom:request completion:^(NSError * _Nullable error, NIMChatroom * _Nullable chatroom, NIMChatroomMember * _Nullable me) {
|
||||
@kStrongify(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (error) {
|
||||
NSLog(@"PublicRoomManager: 进入公共房间失败: %@", error);
|
||||
if (completion) {
|
||||
completion(error);
|
||||
}
|
||||
} else {
|
||||
NSLog(@"PublicRoomManager: 进入公共房间成功,房间ID: %@", roomId);
|
||||
self.isInPublicRoom = YES;
|
||||
self.currentPublicRoomId = roomId;
|
||||
if (completion) {
|
||||
completion(nil);
|
||||
}
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - 手动控制
|
||||
|
||||
- (void)enterPublicRoomWithCompletion:(void(^)(NSError * _Nullable error))completion {
|
||||
if (!self.isInitialized) {
|
||||
NSError *error = [NSError errorWithDomain:@"PublicRoomManager"
|
||||
code:-2
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"管理器未初始化"}];
|
||||
if (completion) {
|
||||
completion(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.isInPublicRoom) {
|
||||
NSLog(@"PublicRoomManager: 已在公共房间中");
|
||||
if (completion) {
|
||||
completion(nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
[self tryEnterPublicRoom];
|
||||
}
|
||||
|
||||
- (void)exitPublicRoomWithCompletion:(void(^)(NSError * _Nullable error))completion {
|
||||
if (!self.isInPublicRoom || !self.currentPublicRoomId) {
|
||||
NSLog(@"PublicRoomManager: 未在公共房间中");
|
||||
if (completion) {
|
||||
completion(nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@kWeakify(self);
|
||||
[[NIMSDK sharedSDK].chatroomManager exitChatroom:self.currentPublicRoomId completion:^(NSError * _Nullable error) {
|
||||
@kStrongify(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (error) {
|
||||
NSLog(@"PublicRoomManager: 退出公共房间失败: %@", error);
|
||||
} else {
|
||||
NSLog(@"PublicRoomManager: 退出公共房间成功");
|
||||
self.isInPublicRoom = NO;
|
||||
self.currentPublicRoomId = nil;
|
||||
}
|
||||
if (completion) {
|
||||
completion(error);
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - 用户信息更新处理
|
||||
|
||||
- (void)updateUserInfo:(UserInfoModel *)userInfo {
|
||||
if (!userInfo || !userInfo.partitionId) {
|
||||
NSLog(@"PublicRoomManager: 用户信息更新失败,信息不完整");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查用户是否切换
|
||||
if (self.currentUserId && ![self.currentUserId isEqualToString:[NSString stringWithFormat:@"%ld", userInfo.uid]]) {
|
||||
NSLog(@"PublicRoomManager: 检测到用户切换,重置管理器");
|
||||
[self reset];
|
||||
}
|
||||
|
||||
self.userInfo = userInfo;
|
||||
self.currentUserId = [NSString stringWithFormat:@"%ld", userInfo.uid];
|
||||
|
||||
// 如果已初始化但未在公共房间,尝试进入
|
||||
if (self.isInitialized && !self.isInPublicRoom) {
|
||||
[self tryEnterPublicRoom];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 配置更新处理
|
||||
|
||||
- (void)updateConfig {
|
||||
if (!self.isInitialized) {
|
||||
NSLog(@"PublicRoomManager: 未初始化,跳过配置更新");
|
||||
return;
|
||||
}
|
||||
|
||||
ClientDataModel *configInfo = [ClientConfig shareConfig].configInfo;
|
||||
if (!configInfo || !configInfo.publicChatRoomIdMap) {
|
||||
NSLog(@"PublicRoomManager: 配置信息不完整,跳过配置更新");
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果未在公共房间,尝试进入
|
||||
if (!self.isInPublicRoom) {
|
||||
[self tryEnterPublicRoom];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - NIMChatManagerDelegate
|
||||
|
||||
- (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]) {
|
||||
NSLog(@"PublicRoomManager: 收到公共房间消息: %@", message.text);
|
||||
// 这里可以添加公共房间消息的处理逻辑
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@@ -95,6 +95,7 @@
|
||||
|
||||
#import "RoomResourceManager.h"
|
||||
#import "SocialShareManager.h"
|
||||
#import "PublicRoomManager.h"
|
||||
|
||||
NSString * const kUserFirstLoginKey = @"kUserFirstLoginKey";
|
||||
NSString * const kHadLaunchApp = @"kHadLaunchApp";
|
||||
@@ -389,6 +390,10 @@ UIKIT_EXTERN NSString *kTabShowAnchorCardKey;
|
||||
[[XPSkillCardPlayerManager shareInstance] setUserInfoModel:userInfo];
|
||||
[[XPSkillCardPlayerManager shareInstance] requestBravoGiftTabInfomation];
|
||||
[[RoomBoomManager sharedManager] saveUserInfo:userInfo];
|
||||
|
||||
// 更新公共房间管理器的用户信息
|
||||
[[PublicRoomManager sharedManager] updateUserInfo:userInfo];
|
||||
|
||||
[self getRoomGameInfo];
|
||||
[AccountInfoStorage instance].name = self.userInfo.nick;
|
||||
|
||||
|
@@ -95,7 +95,7 @@
|
||||
editParam = [MSParamsDecode msDecodeParams:editParam];
|
||||
params = [self configBaseParmars:editParam];
|
||||
|
||||
#if 0
|
||||
#if DEBUG
|
||||
// 构建完整的 URL
|
||||
NSString *baseUrl = [HttpRequestHelper getHostUrl];
|
||||
NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method];
|
||||
@@ -119,7 +119,7 @@
|
||||
@kWeakify(self);
|
||||
[manager GET:method parameters:params headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject];
|
||||
#if 0
|
||||
#if DEBUG
|
||||
NSLog(@"%@ - \n%@\n", method, [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
@@ -246,7 +246,7 @@
|
||||
|
||||
params = [self configBaseParmars:params];
|
||||
|
||||
#if 0
|
||||
#if DEBUG
|
||||
// 构建完整的 URL
|
||||
NSString *baseUrl = [HttpRequestHelper getHostUrl];
|
||||
NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method];
|
||||
@@ -261,7 +261,7 @@
|
||||
|
||||
[manager POST:method parameters:params headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject];
|
||||
#if 0
|
||||
#if DEBUG
|
||||
NSLog(@"\n%@", [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
@@ -287,7 +287,7 @@
|
||||
params = [MSParamsDecode msDecodeParams:[params mutableCopy] ];
|
||||
params = [self configBaseParmars:params];
|
||||
|
||||
#if 0
|
||||
#if DEBUG
|
||||
NSLog(@"\nmethod:\n%@\nparameter:\n%@\n 超時:%@",
|
||||
method,
|
||||
params,
|
||||
@@ -310,13 +310,13 @@ constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
|
||||
progress:nil
|
||||
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject];
|
||||
#if 0
|
||||
#if DEBUG
|
||||
NSLog(@"%@ - \n%@\n", method, [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
success(baseModel);
|
||||
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
|
||||
#if 0
|
||||
#if DEBUG
|
||||
NSLog(@"%@ - \n%@\n", method, error);
|
||||
#else
|
||||
#endif
|
||||
@@ -339,7 +339,7 @@ constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
|
||||
params = [MSParamsDecode msDecodeParams:[params mutableCopy] ];
|
||||
params = [self configBaseParmars:params];
|
||||
AFHTTPSessionManager *manager = [HttpRequestHelper requestManager];
|
||||
#if 0
|
||||
#if DEBUG
|
||||
// 构建完整的 URL
|
||||
NSString *baseUrl = [HttpRequestHelper getHostUrl];
|
||||
NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method];
|
||||
@@ -364,7 +364,7 @@ constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
|
||||
@kWeakify(self);
|
||||
[manager DELETE:method parameters:params headers:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject];
|
||||
#if 0
|
||||
#if DEBUG
|
||||
NSLog(@"\n%@\n", [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
@@ -502,7 +502,7 @@ constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
|
||||
failure(error.code, error.localizedDescription.length > 0 ? error.localizedDescription : YMLocalizedString(@"HttpRequestHelper4"));
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
#if DEBUG
|
||||
NSLog(@"\n%@", error);
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSDictionary *allHeaders = response.allHeaderFields;
|
||||
@@ -537,7 +537,7 @@ constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
|
||||
AFHTTPSessionManager *manager = [HttpRequestHelper requestManager];
|
||||
NSString *url = [self getHostUrl];
|
||||
NSString *urlPath = [NSString stringWithFormat:@"%@/%@", url ,path];
|
||||
#if 0
|
||||
#if DEBUG
|
||||
NSLog(@"\nmethod:\n%@\nparameter:\n%@", path, params);
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
[BSNetListenModel addHttpReq:urlPath header:manager.requestSerializer.HTTPRequestHeaders param:[params copy] time:[NSDate getCurrentTimeWithFormat:@"yyyy-MM-dd HH:mm:ss"]];
|
||||
@@ -582,12 +582,12 @@ constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
|
||||
if (responseObject) {
|
||||
BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject];
|
||||
|
||||
#if 0
|
||||
#if DEBUG
|
||||
NSLog(@"\n%@", [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
if (baseModel.code == 200) {
|
||||
#if 0
|
||||
#if DEBUG
|
||||
NSHTTPURLResponse *urlresponse = (NSHTTPURLResponse *)response;
|
||||
NSDictionary *allHeaders = urlresponse.allHeaderFields;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
@@ -172,6 +172,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
///声音卡
|
||||
@property (nonatomic,strong) XPSoundCardModel *audioCard;
|
||||
|
||||
///用户所在区
|
||||
@property(nonatomic,copy) NSString *partitionId;
|
||||
///
|
||||
///
|
||||
|
@@ -12,6 +12,7 @@
|
||||
#import "LoginViewController.h"
|
||||
#import "BaseNavigationController.h"
|
||||
#import "FirstRechargeManager.h"
|
||||
#import "PublicRoomManager.h"
|
||||
|
||||
@interface BaseMvpPresenter()
|
||||
|
||||
@@ -33,13 +34,16 @@
|
||||
// 1. 停止首充监控
|
||||
[[FirstRechargeManager sharedManager] stopMonitoring];
|
||||
|
||||
// 2. 数据logout
|
||||
// 2. 重置公共房间管理器
|
||||
[[PublicRoomManager sharedManager] reset];
|
||||
|
||||
// 3. 数据logout
|
||||
[[AccountInfoStorage instance] saveAccountInfo:nil];
|
||||
[[AccountInfoStorage instance] saveTicket:nil];
|
||||
if ([NIMSDK sharedSDK].loginManager.isLogined) {
|
||||
[[NIMSDK sharedSDK].loginManager logout:nil];
|
||||
}
|
||||
// 3. 跳登录页面
|
||||
// 4. 跳登录页面
|
||||
[self tokenInvalid];
|
||||
// ///关闭心跳
|
||||
// [[ClientConfig shareConfig] resetHeartBratTimer];
|
||||
|
@@ -1892,6 +1892,7 @@
|
||||
"XPLoginViewController4" = "Login bem-sucedido";
|
||||
"XPLoginViewController5" = "Login com Número de Telefone";
|
||||
"XPLoginViewController6" = "Concorde com o Contrato de Serviço do Usuário e Política de Privacidade";
|
||||
"XPLoginViewController6.1" = "Concorde com o Contrato de Serviço do Usuário\n e Política de Privacidade";
|
||||
"XPLoginViewController7" = "Contrato de Serviço do Usuário";
|
||||
"XPLoginViewController8" = " e ";
|
||||
"XPLoginViewController9" = "Política de Privacidade";
|
||||
|
@@ -2131,7 +2131,8 @@
|
||||
"XPLoginViewController3" = "《Kullanıcı Sözleşmesi》'ni kabul ediyorum ve %@'in bu cihazın numarasını almasına izin veriyorum";
|
||||
"XPLoginViewController4" = "Giriş Başarılı";
|
||||
"XPLoginViewController5" = "Telefon Numarası ile Giriş";
|
||||
"XPLoginViewController6" = "《Kullanıcı Sözleşmesi》 ve 《Gizlilik Politikası》'nı kabul ediyorum";
|
||||
"XPLoginViewController6" = "《Kullanıcı Sözleşmesi》 ve《Gizlilik Politikası》'nı kabul ediyorum";
|
||||
"XPLoginViewController6.1" = "《Kullanıcı Sözleşmesi》 ve \n《Gizlilik Politikası》'nı kabul ediyorum";
|
||||
"XPLoginViewController7" = "《Kullanıcı Sözleşmesi》";
|
||||
"XPLoginViewController8" = "ve";
|
||||
"XPLoginViewController9" = "《Gizlilik Politikası》";
|
||||
|
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