腾讯云IM实现短信注册登录好友添加单聊会话
以后项目会用到即时通信,写了一个demo,这里整理下集成腾讯云IM过程。
云通信架构
提供单聊、群聊、资料托管、关系链托管、帐号托管全方位解决方案,并提供完善的 App 接入、后台管理接口。
imagedemo使用 SDK v2.x版本,ImSDK开发包内含:ImSDK.framework、IMCore.framework、TLSSDK.framework、QALSDK.framework。
image使用说明
1.直接拖入
image2.选中IMDemo的Target,在General面板中的Linked Frameworks and Libraries添加依赖库。
image3.在Build Setting中Other Linker Flags添加-ObjC。
image4.工程Info.plist文件增加
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
5.添加工程pch文件
#import <ImSDK/ImSDK.h>
#import <QALSDK/QalSDKProxy.h>
#import <QALSDK/QalSDKCallbackProtocol.h>
#import "TLSSDK/TLSAccountHelper.h"
#import "TLSSDK/TLSLoginHelper.h"
#import "TLSSDK/TLSRefreshTicketListener.h"
#import "TLSSDK/TLSOpenLoginListener.h"
#import "TLSSDK/TLSHelper.h"
6.编译成功,运行以下代码,打印"hello world"表示ImSDK集成成功
// 初始化TIM
[[TIMManager sharedInstance]initSdk:kSdkAppId accountType:kAccountType];
[[TIMManager sharedInstance]log:TIM_LOG_DEBUG tag:@"imsdk" msg:@"hello world"];
image.png
帐号登录集成
这里采用托管模式,集成(Tencent Login Service,TLS)SDK实现短信登录验证服务。
托管模式
托管模式是指,由 TLS 为开发者提供 App 帐号的密码注册、存储和密码验证。帐号验证成功后,派发私钥加密生成的签名到客户端,App 业务服务器可以通过 下载的公钥 解密签名进行验证。
使用说明
直接使用 TLS SDK 即可快速完成注册和登录能力集成。
image搭建登录界面
注册登录逻辑
注册功能开发
SDK接口采用了异步的方式来实现,所有需要异步返回结果的接口都提供了listener回调参数,回调命名为Listener.
1.TLS初始化
# AppDelegate.m
// kSdkAppId和kAccountType是在腾讯云平台申请的业务id和帐号类型
[[QalSDKProxy sharedInstance]initWithAppid:kSdkAppId andSDKAppid:kSdkAppId andAccType:kAccountType];
TLSLoginHelper *helper = [[TLSLoginHelper getInstance]init:kSdkAppId andAccountType:kAccountType andAppVer:@"1.0"];
[[TLSAccountHelper getInstance]init:kSdkAppId andAccountType:kAccountType andAppVer:@"1.0"];
#强烈注意:TLSSDK依赖QALSDK,因此需要先初始化QALSDK。
2.输入手机号码,请求短信验证码
# UIMRegisterViewController.m 遵守<TLSSmsLoginListener>协议
#pragma mark - 获取验证码按钮的点击
- (void)getCodeBtnClick
{
// phoneNumStr 用户输入的手机号码,格式是 国家码-手机号码,比如 86-186xxx
// regListener 注册回调,需要实现TLSSmsRegListener协议,不能为nil
NSString *phoneNoStr = [NSString stringWithFormat:@"86-%@",self.phoneTextField.text];
[[TLSHelper getInstance]TLSSmsRegAskCode:phoneNoStr andTLSSmsRegListener:self];
}
#pragma mark - 注册按钮的点击
- (void)registerBtnClick
{
[[TLSHelper getInstance]TLSSmsRegVerifyCode:self.pwdTextField.text andTLSSmsRegListener:self];
}
/**
* 刷新短信验证码请求成功
*
* @param reaskDuration 下次请求间隔
* @param expireDuration 验证码有效期
*/
-(void) OnSmsRegReaskCodeSuccess:(int)reaskDuration andExpireDuration:(int)expireDuration{
// 重新请求短信验证码的回调
//[[TLSHelper getInstance]TLSSmsRegReaskCode:nil];//等倒计时结束,用户手动点击重新发送之后,触发此逻辑
}
/**
* 验证短信验证码成功
*/
-(void) OnSmsRegVerifyCodeSuccess{
NSLog(@"验证短信验证码成功");
[SVProgressHUD setMinimumDismissTimeInterval:1];
[SVProgressHUD showSuccessWithStatus:@"验证短信验证码成功"];
// 完成注册
[[TLSHelper getInstance]TLSSmsRegCommit:self];
}
/**
* 提交注册成功
*
* @param userInfo 用户信息
*/
-(void) OnSmsRegCommitSuccess:(TLSUserInfo *)userInfo{
NSLog(@"注册成功");
[SVProgressHUD setMinimumDismissTimeInterval:1];
[SVProgressHUD showSuccessWithStatus:@"注册成功"];
[self dismissViewControllerAnimated:YES completion:^{
NSLog(@"移除"); // 注册页是modal出来的
}];
}
/**
* 短信注册失败
*
* @param errInfo 错误信息
*/
-(void) OnSmsRegFail:(TLSErrInfo *) errInfo{
NSString *errMsg = errInfo.sErrorMsg;
NSLog(@"验证码注册失败%@",errMsg);
//时间
[SVProgressHUD setMinimumDismissTimeInterval:1];
[SVProgressHUD showSuccessWithStatus:errMsg];
[self timeFailBeginFrom:1];
}
/**
* 短信注册超时
*
* @param errInfo 错误信息
*/
-(void) OnSmsRegTimeout:(TLSErrInfo *) errInfo{
NSLog(@"验证码注册失败%@",errInfo);
[self timeFailBeginFrom:1];
}
/**
* 请求短信验证码成功
*
* @param reaskDuration 下次请求间隔
* @param expireDuration 验证码有效期
*/
-(void) OnSmsRegAskCodeSuccess:(int)reaskDuration andExpireDuration:(int) expireDuration{
NSLog(@"提交手机号成功");
}
短信登录功能开发
用户通过短信验证码的方式进行登录。
1.输入手机号码,请求短信验证码
#pragma mark - 登录按钮的点击
- (void)loginBtnClick
{
// smsCodeText.text 用户输入的短信验证码
// listener可以传nil,此时listener使用上一步设置的值
NSString *phoneNoStr = [NSString stringWithFormat:@"86-%@", self.phoneTextField.text];
[[TLSHelper getInstance] TLSSmsVerifyCode: phoneNoStr andCode: self.smsCodeText.text andTLSSmsLoginListener:self];
}
#pragma mark - 获取验证码的点击
- (void)getLoginCodeBtnClick
{
// 调用短信验证码接口
//phoneNumStr 用户输入的手机号,格式是 国家码-手机号码,比如 86-186xxx
//self短消登录回调listener,需要实现TLSSmsLoginListener协议,不能为nil
NSString *phoneNoStr = [NSString stringWithFormat:@"86-%@",self.phoneTextField.text];
[[TLSHelper getInstance] TLSSmsAskCode:phoneNoStr andTLSSmsLoginListener:self];
}
2.在回调中提交登录腾讯IM
/**
* 请求短信验证码成功
*
* @param reaskDuration 下次请求间隔
* @param expireDuration 验证码有效期
*/
- (void)OnSmsLoginAskCodeSuccess:(int)reaskDuration andExpireDuration:(int)expireDuration{
NSLog(@"提交手机号成功");
}
/**
* 验证短信验证码成功
*/
- (void)OnSmsLoginVerifyCodeSuccess{
NSLog(@"验证短信验证码成功");
//self短消登录回调listener,不能为nil
NSString *phoneNoStr = [NSString stringWithFormat:@"86-%@",self.phoneTextField.text];
[[TLSHelper getInstance] TLSSmsLogin:phoneNoStr andTLSSmsLoginListener:self];
}
/**
* 提交登录请求成功
*
* @param userInfo 用户信息
*/
- (void)OnSmsLoginSuccess:(TLSUserInfo *)userInfo{
/* 登录成功了,在这里可以获取用户票据*/
NSString *userSig = [[TLSHelper getInstance] getTLSUserSig:userInfo.identifier];
// 登录腾讯云IM
[[IMDataManager shareManager]loginTIMWithUserInfo:[IMUserInfo sharedIMUserInfo] withToken:userSig];
}
- (void)OnSmsLoginFail:(TLSErrInfo *)errInfo{
/* 短信登录过程中任意一步都可以到达这里,可以根据tlsErrInfo 中ErrCode, Title, Msg 给用户弹提示语,引导相关操作*/
NSString *errMsg = errInfo.sErrorMsg;
NSLog(@"登录失败%@",errMsg);
//时间
[SVProgressHUD setMinimumDismissTimeInterval:1];
[SVProgressHUD showSuccessWithStatus:errMsg];
}
- (void)OnSmsLoginTimeout:(TLSErrInfo *)errInfo{
/* 短信登录过程中任意一步都可以到达这里,顾名思义,网络超时,可能是用户网络环境不稳定,一般让用户重试即可*/
NSString *errMsg = errInfo.sErrorMsg;
NSLog(@"登录超时%@",errMsg);
//时间
[SVProgressHUD setMinimumDismissTimeInterval:1];
[SVProgressHUD showSuccessWithStatus:errMsg];
}
# IMDataManager.m
/**
* 登录TIM服务器(connect,用token去连接)
*
* @param userInfo 用户信息
* @param token token令牌
*/
-(void)loginTIMWithUserInfo:(IMUserInfo *)userInfo withToken:(NSString *)token{
TIMLoginParam *param = [[TIMLoginParam alloc]init];
[param setIdentifier:userInfo.userId];
[param setSdkAppId:kSdkAppId];
[param setAccountType:kAccountType];
[param setAppidAt3rd:kAppIdAt3rd];
[param setUserSig:token];
[[TIMManager sharedInstance] login:param succ:^{
NSLog(@"tom2登录成功啦啦啦啦啦啦啦啦啦啦了");
// 成功后跳转主界面
UITabBarController *tabBarVC = [[UIMTabBarController alloc] init];
[UIApplication sharedApplication] .keyWindow.rootViewController = tabBarVC;
} fail:^(int code, NSString *msg) {
NSLog(@"登录失败,呼叫总部,登录失败┏(- 0 -)┛");
}];
}
简易搭建会话列表界面
- 初始化会话刷新监听
通过会话刷新监听器 TIMRefreshListener 中的 onRefresh 回调通知更新界面,用户得到这个消息时,可以刷新界面,比如会话列表的未读等。
# UIMConversationListViewController.m
@property(nonatomic, strong)NSArray<TIMConversation *> *conversationList;
[[TIMManager sharedInstance] setRefreshListener:self];
通过TIMManager获取会话列表list后,遍历会话列表,通过getType,判断你会话类型,单聊TIM_C2C,系统消息TIM_SYSTEM
image.png
#pragma 刷新用户会话列表
- (void)onRefresh
{
// 获取所有会话
NSArray *list = [[TIMManager sharedInstance] getConversationList];
_conversationList = list;
for (TIMConversation *sess in list) {
// 加好友消息
if ([sess getType] == TIM_SYSTEM) {
NSString *rec = [sess getReceiver];
/* 获取会话消息
*
* @param count 获取数量
* @param last 上次最后一条消息
* @param succ 成功时回调
* @param fail 失败时回调
*
* @return 0 本次操作成功
*/
__block TIMMessage *msg;
[sess getMessage:1 last:msg succ:^(NSArray *msgs) {
msg = msgs[0];
for (int i = 0; i < [msg elemCount]; i++) {
TIMElem * elem = [msg getElem:i];
if ([elem isKindOfClass:[TIMSNSSystemElem class]]) {
TIMSNSSystemElem * system_elem = (TIMSNSSystemElem * )elem;
switch ([system_elem type]) {
case TIM_SNS_SYSTEM_ADD_FRIEND:
for (TIMSNSChangeInfo * info in [system_elem users]) {
NSLog(@"user %@ become friends", [info identifier]);
}
break;
case TIM_SNS_SYSTEM_DEL_FRIEND:
for (TIMSNSChangeInfo * info in [system_elem users]) {
NSLog(@"user %@ delete friends", [info identifier]);
}
break;
case TIM_SNS_SYSTEM_ADD_FRIEND_REQ:
for (TIMSNSChangeInfo * info in [system_elem users]) {
NSLog(@"user %@ request friends: reason=%@", [info identifier], [info wording]);
}
break;
default:
NSLog(@"ignore type");
break;
}
}
}
} fail:^(int code, NSString *msg) {
NSLog(@"出错了");
}];
}
// 1对1会话消息
if (sess.getType == TIM_C2C) {
NSString *rec = [sess getReceiver];
}
}
[self.conversationListTabelView reloadData];
}
2.设置cell数据源为 _conversationList
#pragma mark - UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
..........省略..........
TIMConversation *sess = _conversationList[indexPath.row];
cell.nicknameLabel.text = [sess getReceiver];
..........省略..........
return cell;
}
** 简易搭建单聊会话界面**
Simulator Screen Shot - iPhone 7 - 2018-05-10 at 15.39.25.png
1.注册新消息通知回调 TIMMessageListener
// 监听新消息
[[TIMManager sharedInstance] setMessageListener:self];
2.保存聊天数据的数组
// 保存所有聊天数据
@property (nonatomic, strong) NSMutableArray *chatsData;
3.消息模型
#import <Foundation/Foundation.h>
typedef enum : NSUInteger {
UIMChatModelMessageTypeOther,
UIMChatModelMessageTypeMe,
} UIMChatModelMessageType; // 消息类型
@interface UIMChatModel : NSObject
/// 消息内容
@property (nonatomic, copy) NSString *text;
/// 消息时间
@property (nonatomic, copy) NSString *time;
/// 消息类型
@property (nonatomic, assign) UIMChatModelMessageType type;
@end
4.发送消息/接收消息
回调消息内容通过参数TIMMessage传递,通过TIMMessage可以获取消息和相关会话的详细信息,如消息文本,语音数据,图片等等(这里只做了文字对话)
注意: 需要在登录之前注册新消息通知,ImSDK 会拉取离线消息,通过 onNewMessage 抛出
/**
* 新消息通知
*
* @param msgs 新消息列表,TIMMessage 类型数组
*/
- (void)onNewMessage:(NSArray*) msgs{
for (TIMMessage *msg in msgs)
{
int cnt = [msg elemCount];
for (int i = 0; i < cnt; I++)
{
TIMElem * elem = [msg getElem:i];
if ([elem isKindOfClass:[TIMTextElem class]])
{
TIMTextElem * text_elem = (TIMTextElem * )elem;
if ([msg.sender isEqualToString: _friendInfo.userId])
{
NSString *text = text_elem.text;
[self sendMessageWithText:text andMessageType:UIMChatModelMessageTypeOther];
[_tableView reloadData];
// 滚动到最后一行
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:_chatsData.count - 1 inSection:0];
[_tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];
}
}
}
}
}
/**
发送消息/接收消息
@param text 消息内容
@param type 消息类型
UIMChatModelMessageTypeMe 自己
UIMChatModelMessageTypeOther 好友
*/
- (void)sendMessageWithText:(NSString *)text andMessageType:(UIMChatModelMessageType)type {
if (type == UIMChatModelMessageTypeMe) {
[self sendMessageToTIM:text];
}
// 1.创建一个新模型
UIMChatModel *newChatModel = [[UIMChatModel alloc] init];
NSDate *date = [NSDate date]; // 获取当前系统时间
// 把时间转换成字符串
NSDateFormatter *df = [[NSDateFormatter alloc] init];
// 指定时间格式
// df.dateFormat = @"yyyy-MM-dd hh:mm:ss";
df.dateFormat = @"HH:mm";
newChatModel.time = [df stringFromDate:date];
if ([newChatModel.time isEqualToString:_previousTime]) {
newChatModel.time = nil;
} else {
_previousTime = newChatModel.time;
}
newChatModel.text = text;
newChatModel.type = type;
// 2.添加模型数组中
[_chatsData addObject:newChatModel];
}
/**
Me发送消息
@param textMsg 消息内容
*/
- (void)sendMessageToTIM: (NSString *)textMsg{
TIMMessage *msg = [[TIMMessage alloc] init];
TIMTextElem *elem = [[TIMTextElem alloc] init];
[elem setText:textMsg];
[msg addElem:elem];
TIMConversation *sess = [[TIMManager sharedInstance] getConversation:TIM_C2C receiver:_friendInfo.userId];
[sess sendMessage:msg succ:^{
NSLog(@"发送消息成功");
} fail:^(int code, NSString *msg) {
NSLog(@"发送消息失败");
}];
}
/**
接收Other消息
*/
- (NSString *)receiveMessageFromTIM{
__block NSString *otherMsgText;
TIMConversation *sess = [[TIMManager sharedInstance] getConversation:TIM_C2C receiver:_friendInfo.userId];
[sess getMessage:2 last:nil succ:^(NSArray *msgs) {
NSLog(@"接收消息成功 %@",msgs);
for (TIMMessage *msg in msgs)
{
int cnt = [msg elemCount];
for (int i = 0; i < cnt; I++)
{
TIMElem * elem = [msg getElem:i];
if ([elem isKindOfClass:[TIMTextElem class]])
{
TIMTextElem * text_elem = (TIMTextElem * )elem;
otherMsgText = text_elem.text;
}
}
}
} fail:^(int code, NSString *msg) {
NSLog(@"接收消息失败");
}];
return otherMsgText;
}
// 当点击键盘的发送时会调用此方法
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
// 自己发一条消息
[self sendMessageWithText:textField.text andMessageType:UIMChatModelMessageTypeMe];
[_tableView reloadData];
// 滚动到最后一行
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:_chatsData.count - 1 inSection:0];
[_tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];
// 清空文本输入框中的文字
textField.text = nil;
return YES;
}
3.显示对话
UIMChatMeCell.m和UIMChatOtherCell.m
// 重写模型属性的set方法给子控件设置数据
- (void)setChatModel:(UIMChatModel *)chatModel {
_chatModel = chatModel;
_timeLabel.text = chatModel.time;
_textContentLabel.text = chatModel.text;
}
资料关系链托管
实现好友列表,使用用户 id查找好友,添加好友功能(https://cloud.tencent.com/doc/product/269/%E8%B5%84%E6%96%99%E7%B3%BB%E7%BB%9F)、关系链系统 文档。
设置好友验证方式
可通过 TIMFriendshipManager 的 SetAllowType 方法设置好友验证方式,用户可根据需要设置其中一种,目前没有方法设置默认的好友验证方式,默认都是任何人可加好友。(demo也是默认,没改)
有以下几种验证方式:
同意任何用户加好友
拒绝任何人加好友
需要验证
添加好友界面
/**
* 添加好友点击事件
*
* users 要添加的用户列表 TIMAddFriendRequest* 列表
*/
- (void)actionAddFriend:(id)sender {
NSMutableArray * users = [[NSMutableArray alloc] init];
TIMAddFriendRequest* req = [[TIMAddFriendRequest alloc] init];
// 添加好友
req.identifier = self.friendUserInfo.userId;
// 添加备注 002Remark
req.remark = [NSString stringWithUTF8String:"002Remark"];
// 添加理由
req.addWording = [NSString stringWithUTF8String:"i am X"];
[users addObject:req];
[[TIMFriendshipManager sharedInstance] AddFriend:users succ:^(NSArray * arr) {
for (TIMFriendResult * res in arr) {
if (res.status != TIM_FRIEND_STATUS_SUCC) {
NSLog(@"AddFriend failed: user=%@ status=%d", res.identifier, res.status);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil
message:@"已发送好友邀请"
delegate:nil
cancelButtonTitle:@"确定"
otherButtonTitles:nil, nil];
[alertView show];
}
else {
NSLog(@"AddFriend succ: user=%@ status=%d", res.identifier, res.status);
}
}
} fail:^(int code, NSString * err) {
NSLog(@"add friend fail: code=%d err=%@", code, err);
}];
}
}
好友列表
#pragma mark - 加载数据
- (void)loadTIMFriendData {
[[TIMFriendshipManager sharedInstance] GetFriendList:^(NSArray * arr) {
NSMutableArray *ArrM = [[NSMutableArray alloc]initWithCapacity:arr.count];
for (TIMUserProfile *friend in arr) {
[ArrM addObject:friend];
}
_friendListTIMData = ArrM.copy;
[self.friendsTabelView reloadData];
}fail:^(int code, NSString * err) {
NSLog(@"GetFriendList fail: code=%d err=%@", code, err);;
}];
}