前端整合腾讯云IM实现即时通讯
最编程
2024-08-11 12:45:17
...
近期需求 : 在项目里面开发IM 即时通讯 之前对接过融云 GoEasy 极光 这次在微信小程序里面集成 本着" 同源策略 " 采用了腾讯云IM 即时通讯 虽然腾讯方面的文档对我来说并不是那么的友好 ☺
即时通信 IM官网地址: cloud.tencent.com/document/pr…
鉴于小程序体积的限制外加本公司对UI还原程度 "高度重视" , 并没有采用含UI集成方案 , 采用了无UI集成方案
半成品
步骤:
集成 SDK
- 通过 npm 和 script 方式将 IM SDK 集成到您的 Web 项目中,推荐使用 npm 集成。
- 通过 npm 方式将 IM SDK 集成到您的小程序或者 uni-app 项目中。
- 通过集成上传插件 tim-upload-plugin,实现更快更安全的富文本消息资源上传。
- 通过集成本地审核插件 tim-profanity-filter-plugin,在客户端本地检测由即时通信 SDK 发送的文本内容,支持对已配置的敏感词进行拦截或者替换处理,为您的产品体验和业务安全保驾护航。本地审核功能的开通和配置方法,详情请参见 控制台指南。
// 从v2.11.2起,SDK 支持了 WebSocket,推荐接入;v2.10.2及以下版本,使用 HTTP
npm install tim-wx-sdk --save
// 发送图片、文件等消息需要腾讯云 即时通信 IM 上传插件
npm install tim-upload-plugin --save
// 拦截或替换敏感词需要本地审核插件
npm install tim-profanity-filter-plugin --save
初始化
初始化 SDK 需要操作以下步骤:
- 准备 SDKAppID。
- 调用
TIM.create
初始化 SDK。 - 添加 SDK 事件监听器。
// 从v2.11.2起,SDK 支持了 WebSocket,推荐接入;v2.10.2及以下版本,使用 HTTP
// v2.24.0起,SDK 支持使用本地审核插件
import TIM from 'tim-js-sdk';
import TIMUploadPlugin from 'tim-upload-plugin';
import TIMProfanityFilterPlugin from 'tim-profanity-filter-plugin';
let options = {
SDKAppID: 0 // 接入时需要将0替换为您的即时通信 IM 应用的 SDKAppID
};
// 创建 SDK 实例,`TIM.create()`方法对于同一个 `SDKAppID` 只会返回同一份实例
let tim = TIM.create(options); // SDK 实例通常用 tim 表示
// 设置 SDK 日志输出级别,详细分级请参见 <a href="https://web.sdk.qcloud.com/im/doc/zh-cn/SDK.html#setLogLevel">setLogLevel 接口的说明</a>
tim.setLogLevel(0); // 普通级别,日志量较多,接入时建议使用
// tim.setLogLevel(1); // release 级别,SDK 输出关键信息,生产环境时建议使用
// 注册腾讯云即时通信 IM 上传插件
tim.registerPlugin({'tim-upload-plugin': TIMUploadPlugin});
// 注册腾讯云即时通信 IM 本地审核插件
tim.registerPlugin({'tim-profanity-filter-plugin': TIMProfanityFilterPlugin});
登录
let promise = tim.login({userID: 'your userID', userSig: 'your userSig'});
promise.then(function(imResponse) {
console.log(imResponse.data); // 登录成功
if (imResponse.data.repeatLogin === true) {
// 标识帐号已登录,本次登录操作为重复登录。v2.5.1 起支持
console.log(imResponse.data.errorInfo);
}
}).catch(function(imError) {
console.warn('login error:', imError); // 登录失败的相关信息
});
创建消息
接收消息 事件监听
let onMessageReceived = function(event) {
// event.data - 存储 Message 对象的数组 - [Message]
const messageList = event.data;
messageList.forEach((message) => {
if (message.type === TIM.TYPES.MSG_TEXT) {
// 文本消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.TextPayload
} else if (message.type === TIM.TYPES.MSG_IMAGE) {
// 图片消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.ImagePayload
} else if (message.type === TIM.TYPES.MSG_SOUND) {
// 音频消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.AudioPayload
} else if (message.type === TIM.TYPES.MSG_VIDEO) {
// 视频消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.VideoPayload
} else if (message.type === TIM.TYPES.MSG_FILE) {
// 文件消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.FilePayload
} else if (message.type === TIM.TYPES.MSG_CUSTOM) {
// 自定义消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.CustomPayload
} else if (message.type === TIM.TYPES.MSG_MERGER) {
// 合并消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.MergerPayload
} else if (message.type === TIM.TYPES.MSG_LOCATION) {
// 地理位置消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.LocationPayload
} else if (message.type === TIM.TYPES.MSG_GRP_TIP) {
// 群提示消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.GroupTipPayload
} else if (message.type === TIM.TYPES.MSG_GRP_SYS_NOTICE) {
// 群系统通知 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.GroupSystemNoticePayload
}
});
};
tim.on(TIM.EVENT.MESSAGE_RECEIVED, onMessageReceived);
历史消息
// 打开某个会话时,第一次拉取消息列表
let promise = tim.getMessageList({conversationID: 'C2Ctest'});
promise.then(function(imResponse) {
const messageList = imResponse.data.messageList; // 消息列表。
const nextReqMessageID = imResponse.data.nextReqMessageID; // 用于续拉,分页续拉时需传入该字段。
const isCompleted = imResponse.data.isCompleted; // 表示是否已经拉完所有消息。
});
// 下拉查看更多消息
let promise = tim.getMessageList({conversationID: 'C2Ctest', nextReqMessageID});
promise.then(function(imResponse) {
const messageList = imResponse.data.messageList; // 消息列表。
const nextReqMessageID = imResponse.data.nextReqMessageID; // 用于续拉,分页续拉时需传入该字段。
const isCompleted = imResponse.data.isCompleted; // 表示是否已经拉完所有消息。
});
获取会话列表
接入侧可通过调用 getConversationList 接口主动获取会话列表。
获取全量的会话列表
// 获取全量的会话列表
let promise = tim.getConversationList();
promise.then(function(imResponse) {
const conversationList = imResponse.data.conversationList; // 全量的会话列表,用该列表覆盖原有的会话列表
const isSyncCompleted = imResponse.data.isSyncCompleted; // 从云端同步会话列表是否完成
}).catch(function(imError) {
console.warn('getConversationList error:', imError); // 获取会话列表失败的相关信息
});
获取指定的会话列表
// 获取指定的会话列表
let promise = tim.getConversationList([conversationID1, conversationID2]);
promise.then(function(imResponse) {
const conversationList = imResponse.data.conversationList; // 缓存中已存在的指定的会话列表
}).catch(function(imError) {
console.warn('getConversationList error:', imError); // 获取会话列表失败的相关信息
});
获取所有的群会话
// 获取所有的群会话
let promise = tim.getConversationList({ type: TIM.TYPES.CONV_GROUP });
promise.then(function(imResponse) {
const conversationList = imResponse.data.conversationList; // 会话列表
});
获取所有的“标星”会话
// 获取所有的“标星”会话
let promise = tim.getConversationList({ markType: TIM.TYPES.CONV_MARK_TYPE_STAR });
promise.then(function(imResponse) {
const conversationList = imResponse.data.conversationList; // 会话列表
});
获取指定会话分组下的所有会话
// 获取指定会话分组下的所有会话
let promise = tim.getConversationList({ groupName: 'Suppliers' });
promise.then(function(imResponse) {
const conversationList = imResponse.data.conversationList; // 会话列表
});
监听会话列表更新事件
接入侧监听 TIM.EVENT.CONVERSATION_LIST_UPDATED 事件,获取会话列表更新的通知。
示例
let onConversationListUpdated = function(event) {
console.log(event.data); // 包含 Conversation 实例的数组
};
tim.on(TIM.EVENT.CONVERSATION_LIST_UPDATED, onConversationListUpdated);
置顶会话
功能描述
会话置顶,指的是把好友或者群会话固定在会话列表的最前面,方便用户查找。置顶状态会存储在服务器,切换终端设备后,置顶状态会同步到新设备上。
调用接口成功后会话列表重新排序,SDK 会派发事件 TIM.EVENT.CONVERSATION_LIST_UPDATED。
// 置顶会话,v2.14.0起支持
let promise = tim.pinConversation({ conversationID: 'C2CExample', isPinned: true });
promise.then(function(imResponse) {
// 置顶会话成功
const { conversationID } = imResponse.data; // 被置顶的会话 ID
}).catch(function(imError) {
console.warn('pinConversation error:', imError); // 置顶会话失败的相关信息
});
// 取消置顶会话,v2.14.0起支持
let promise = tim.pinConversation({ conversationID: 'C2CExample', isPinned: false });
promise.then(function(imResponse) {
// 取消置顶会话成功
const { conversationID } = imResponse.data; // 被取消置顶的会话 ID
}).catch(function(imError) {
console.warn('pinConversation error:', imError); // 取消置顶会话失败的相关信息
});
删除会话
功能描述
在删除好友或退出群组后,如果不需要查看好友或群会话的历史消息,可以选择删除会话。会话删除默认关闭多端同步,可在 即时通信 IM 控制台 开启多端同步。
let promise = tim.deleteConversation('C2CExample');
promise.then(function(imResponse) {
// 删除会话成功
const { conversationID } = imResponse.data; // 被删除的会话 ID
}).catch(function(imError) {
console.warn('deleteConversation error:', imError); // 删除会话失败的相关信息
});
具体建议还是打开官网进行开发 功能不难 本次记录只是方便后期相同需求开发快速迭代
chitchat.wxml
<!--pages/contact/contact.wxml-->
<custom-nav-bar backgroundColor="#000" color="#fff" isBottomBorder="{{false}}">
<view class="nav-box">
{{ navbarTitle }}
</view>
</custom-nav-bar>
<view class="msg-list-box">
<scroll-view scroll-y scroll-into-view='{{toView}}' scroll-with-animation bindscrolltoupper="bindscrolltoupperHandler" style='height: 80vh;' enable-passive bindtap="clickScrollView">
<!-- <view class='scrollMsg'> -->
<block wx:key="{{ index }}" wx:for='{{messageList}}'>
<view wx:if="{{ item.showMessageTime }}" class="message-time">
<text>{{ item.messageTime }}</text>
</view>
<!--(右) -->
<view wx:if="{{ item.flow == 'out' }}" id='msg-{{index}}' class="right-msg-list-box">
<view class="remoteAudioUrl" bindtap="innerAudioHandler" data-audio="{{ item.payload.remoteAudioUrl }}" style="justify-content: flex-end;padding-right: 30rpx;width: {{ item.payload.second*20 }}rpx;max-width: 650rpx;min-width: 132rpx;" wx:if="{{ item.type == 'TIMSoundElem' }}">
<text style="color: #fff;margin-right: 20rpx;">{{ item.payload.second }}''</text>
<image src="https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-202302281147%E8%AF%AD%E9%9F%B3%20%E6%8B%B7%E8%B4%9D%402x.png" mode="widthFix" style="width: 22rpx;" />
</view>
<view class='rightMsg' wx:if="{{ item.type == 'TIMTextElem' }}">{{item.payload.text}}</view>
<image style="width: 300rpx;border-radius: 10rpx;" bindtap="getLookBigImage" data-imgurl="{{item.payload.imageInfoArray[0].imageUrl}}" mode="widthFix" wx:if="{{ item.type == 'TIMImageElem' }}" src="{{ item.payload.imageInfoArray[0].imageUrl }}"></image>
<!-- <view wx:if="{{ item.type == 'TIMTextElem' || item.type == 'TIMSoundElem' }}" style='width: 4vw; height: 11vw; margin-right: 16rpx; display: flex; align-items: center; z-index: 9;'>
<image style='width: 4vw;' src='https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im---%E4%B8%89%E8%A7%92%E5%BD%A2%201%20%E6%8B%B7%E8%B4%9D%207%402x.png' mode='widthFix'></image>
</view> -->
<view class="right-msg-list-avatar-box">
<image class="right-msg-list-avatar" src='https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F4582899c-cff4-4363-a04f-908be3513443%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1679795943&t=da989d03216a8a1f6ad8dc989399f1c0'></image>
</view>
</view>
<!-- (左) -->
<view wx:else id='msg-{{index}}' class="left-msg-list">
<view style='width: 70rpx; height: 70rpx;margin-right: 30rpx;'>
<image style='width: 70rpx; height: 70rpx; border-radius: 50%;' src='https://img1.baidu.com/it/u=4131860888,2773799558&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800'></image>
</view>
<!-- <view wx:if="{{ item.type == 'TIMTextElem' || item.type == 'TIMSoundElem' }}" style='width: 4vw; height: 11vw; margin-left: 16rpx; display: flex; align-items: center; z-index: 9;'>
<image style='width: 4vw;' src='https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im---%E4%B8%89%E8%A7%92%E5%BD%A2%201%20%E6%8B%B7%E8%B4%9D%206%402x.png' mode='widthFix'></image>
</view> -->
<view class='leftMsg' wx:if="{{ item.type == 'TIMTextElem' }}">{{item.payload.text}}</view>
<image bindtap="getLookBigImage" data-imgurl="{{ item.payload.imageInfoArray[0].imageUrl }}" style="width: 300rpx;border-radius: 10rpx;" mode="widthFix" wx:if="{{ item.type == 'TIMImageElem' }}" src="{{ item.payload.imageInfoArray[0].imageUrl }}"></image>
<view class="remoteAudioUrl" bindtap="innerAudioHandler" data-audio="{{ item.payload.remoteAudioUrl }}" style="justify-content: flex-start;padding-left: 30rpx;width: {{ item.payload.second*20 }}rpx;max-width: 650rpx;min-width: 132rpx;" wx:if="{{ item.type == 'TIMSoundElem' }}">
<image src="https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230228%E8%AF%AD%E9%9F%B3%402x.png" mode="widthFix" style="width: 22rpx;" />
<text style="color: #fff;margin-right: 20rpx;">{{ item.payload.second }}''</text>
</view>
</view>
</block>
<!-- </view> -->
<!-- 占位 -->
<view style='width: 100%; height: 18vw;'></view>
</scroll-view>
<!-- {{inputBottom}} -->
<view class="inputRoom" style='bottom: {{ inputBottom }};height: {{ inputRoomHeight }};'>
<view style="display: flex;align-items: center;box-sizing: border-box;padding: 20rpx;width: 100%;background-color: #000000;">
<image style='width: 7vw;' bindtap="changeAudioHandler" data-flag="{{ true }}" wx:if="{{ !audioFlag }}" src='https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230224%E8%AF%AD%E9%9F%B3%20%281%29%402x.png' mode='widthFix'></image>
<image style='width: 7vw;' bindtap="changeAudioHandler" data-flag="{{ false }}" wx:else src='https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230227%E9%94%AE%E7%9B%98%402x.png' mode='widthFix'></image>
<input bindconfirm='sendClick' wx:if="{{ !audioFlag }}" adjust-position='{{false}}' model:value='{{inputVal}}' confirm-type='send' bindfocus='focus' bindblur='blur'></input>
<view wx:else class="audio" bind:longpress="handleRecordStart" bind:touchmove="handleTouchMove" bind:touchend="handleRecordStop">
<text>按住</text>
<text>说话</text>
</view>
<image style='width: 7vw; margin-left: 3.2vw;' bindtap="changeEmoji" src='https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230224%E8%A1%A8%E6%83%85%402x.png' mode='widthFix'></image>
<image style='width: 7vw; margin-left: 3.2vw;' bindtap="multimediaHandler" src='https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230224icon_%E6%B7%BB%E5%8A%A0%402x.png' mode='widthFix'></image>
</view>
<!-- 表情 -->
<view wx:if="{{displayFlag === 'emoji'}}" class="TUI-Emoji-area">
<Emoji bind:enterEmoji="appendMessage" />
</view>
<!-- 图片 -->
<view wx:if="{{displayFlag === 'extension'}}" class="TUI-Extensions">
<view class="TUI-Extension-slot" style="margin-left: 40rpx;" bindtap="handleSendImage">
<view class="extension-box">
<image class="TUI-Extension-icon" mode="widthFix" src="https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230227%E6%8B%8D%E6%91%84-%E9%80%89%E4%B8%AD%402x%20%282%29.png" />
</view>
<view class="TUI-Extension-slot-name">照片</view>
</view>
<view class="TUI-Extension-slot" style="margin-left: 90rpx;" bindtap="handleSendPicture">
<view class="extension-box">
<image class="TUI-Extension-icon" mode="widthFix" src="https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230227%E6%8B%8D%E6%91%84-%E9%80%89%E4%B8%AD%402x%20%281%29.png" />
</view>
<view class="TUI-Extension-slot-name">拍摄</view>
</view>
</view>
</view>
<!-- 正在录音动效 -->
<view class="record-modal" wx:if="{{recordFlag}}" bind:longpress="handleRecordStart" bind:touchmove="handleTouchMove" bind:touchend="handleRecordStop">
<view class="wrapper">
<view class="modal-loading">
</view>
</view>
<view class="modal-title">
正在录音
</view>
</view>
</view>
chitchat.wxss
/* pages/contact/contact.wxss */
page {
background-color: #080808;
}
.msg-list-box {
margin-top: 150rpx;
background-color: #080808;
}
.nav-box {
display: flex;
justify-content: flex-start;
align-items: center;
font-size: 30rpx;
font-family: PingFang SC;
font-weight: 500;
color: #F5F5F5;
}
.inputRoom {
box-sizing: border-box;
width: 100vw;
background-color: #000000;
position: fixed;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
z-index: 20;
}
input {
/* width: 76vw; */
/* height: 9.33vw; */
width: 518rpx;
height: 64rpx;
background-color: #333333;
border-radius: 40rpx;
margin-left: 2vw;
padding: 0 3vw;
font-size: 28rpx;
color: #e6e6e6;
}
.right-msg-list-box {
display: flex;
justify-content: flex-end;
padding: 3vw 3vw 3vw 11vw;
}
.right-msg-list-avatar-box {
width: 70rpx;
height: 70rpx;
margin-left: 30rpx;
}
.right-msg-list-avatar {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
}
.left-msg-list {
display: flex;
padding: 3vw 11vw 3vw 3vw;
}
.leftMsg {
font-size: 35rpx;
color: #E6E6E6;
line-height: 7vw;
padding: 2vw 2.5vw;
background: #1F1F1F;
/* margin-left: -1.6vw; */
border-radius: 10rpx;
z-index: 10;
}
.rightMsg {
font-size: 35rpx;
color: #E6E6E6;
line-height: 7vw;
padding: 2vw 2.5vw;
background: #1F1F1F;
/* margin-right: -1.6vw; */
border-radius: 10rpx;
z-index: 10;
}
/* 表情 */
.TUI-Emoji-area {
width: 100vw;
height: 200px;
background-color: #000000;
padding-bottom: 100rpx;
}
/* 多媒体 */
.TUI-Extensions {
display: flex;
flex-wrap: wrap;
width: 100vw;
height: 400rpx;
background-color: #000000;
}
.TUI-Extension-slot {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 48rpx;
}
.extension-box {
width: 100rpx;
height: 100rpx;
background: #333333;
border-radius: 28rpx;
display: flex;
justify-content: center;
align-items: center;
}
.TUI-Extension-icon {
width: 51rpx;
border-radius: 0;
}
.TUI-Extension-slot-name {
font-size: 26rpx;
font-family: PingFang SC;
font-weight: 500;
color: #B3B3B3;
margin-top: 12rpx;
text-align: center;
}
.message-time {
font-size: 22rpx;
font-family: PingFang SC;
font-weight: 500;
color: #CCCCCC;
display: flex;
justify-content: center;
align-items: center;
padding: 30rpx;
}
.audio {
width: 518rpx;
height: 64rpx;
background-color: #333333;
border-radius: 40rpx;
margin-left: 2vw;
padding: 0 3vw;
font-size: 28rpx;
color: #e6e6e6;
display: flex;
justify-content: center;
align-items: center;
}
.audio:active {
background-color: #5e5c5c;
}
/* 正在录音 */
.record-modal {
height: 300rpx;
width: 60vw;
background-color: #333;
opacity: 0.8;
position: fixed;
top: 670rpx;
z-index: 9999;
left: 20vw;
border-radius: 24rpx;
display: flex;
flex-direction: column;
}
.record-modal .wrapper {
display: flex;
height: 200rpx;
box-sizing: border-box;
padding: 10vw;
}
.record-modal .wrapper .modal-loading {
opacity: 1;
width: 40rpx;
height: 16rpx;
border-radius: 4rpx;
background-color: #006fff;
animation: loading 2s cubic-bezier(0.17, 0.37, 0.43, 0.67) infinite;
}
.modal-title {
text-align: center;
color: #fff;
}
@keyframes loading {
0% {
transform: translate(0, 0)
}
50% {
transform: translate(30vw, 0);
background-color: #f5634a;
width: 40px;
}
100% {
transform: translate(0, 0);
}
}
.remoteAudioUrl {
box-sizing: border-box;
height: 75rpx;
background: #1F1F1F;
border-radius: 10rpx;
display: flex;
align-items: center;
font-size: 30rpx;
color: #e6e6e6;
}
chitchat.js
import dayjs from './../../../../utils/dayjs'
const app = getApp();
var windowHeight = wx.getSystemInfoSync().windowHeight;
var keyHeight = 0;
/**
* 计算msg总高度
*/
function calScrollHeight(that, keyHeight) {
var query = wx.createSelectorQuery();
query.select('.scrollMsg').boundingClientRect(function (rect) {}).exec();
}
const recorderManager = wx.getRecorderManager();
const innerAudioContext = wx.createInnerAudioContext({
useWebAudioImplement: true
})
Page({
/**
* 页面的初始数据
*/
data: {
navbarTitle: "",
audioFlag: false,
messageTime: "",
showMessageTime: false,
inputRoomHeight: '16vw',
scrollHeight: '',
inputBottom: 0,
toUserId: null, //被发送人的用户id
messageList: [], //历史记录的消息列表
displayFlag: '',
inputVal: '',
nextReqMessageID: null,
isCompleted: false,
recordFlag: false,
recordOptions: {
duration: 60000,
sampleRate: 44100,
numberOfChannels: 1,
encodeBitRate: 192000,
format: 'aac'
},
},
changeEmoji() {
this.setData({
scrollHeight: '',
inputBottom: 0,
下一篇: 如何建立内网穿透并搭建Pritunl