IM 即时通讯实战:环信Web IM极速集成

news2024/12/24 9:54:16

前置技能

  • Node.js 环境已搭建。
  • npm 包管理工具的基本使用。
  • Vue2 或者 Vue3 框架基本掌握或使用。

学习目标

  • 项目中集成 IM 即时通讯实战
  • 利用环信 IM Web SDK 快速实现在 Vue.js 中发送出一条 Hello World!

一、了解环信 IM

  1. 什么是环信 IM?

    环信即时通讯为开发者提供高可靠、低时延、高并发、安全、全球化的通信云服务,支持单聊、群聊、聊天室。提供多平台 SDK 支持,包括:Android、iOS、Web;同时,提供 EaseIM 和 EaseIMKit 以及服务端 REST API,帮助开发者快速构建端到端通信的场景。

  2. 学习完环信 WebIM 之后可以干嘛?

    可以在任意 Web 应用中极速集成搭建即时通讯功能,无论是自己搭建 IM 应用,还是实现产品需求均可以灵活集成进入到自己的项目之中。

二、环信 WebIM 实现通讯的基本流程

前置准备

  1. 有效的开发者 AppKey。 ( 注册环信)(注册参考文档)
  2. 使用 Vue-cli 创建一个空白项目,或已经具备已有待集成项目(此篇文章以 Vue3 为示例,Vue2 同样可以参考此文章)。
  3. 在项目中使用 npm 或者 yarn 安装环信 WebSDK 包,easemob-websdk
  4. 下载环信官方 Vue3-Demo

我们开始

初期配置

在确保已进行 npm install easemob-websdk 安装了环信 SDK 包,并已经下载了 Vue3 官方 Demo,将项目中的 IM 文件拖入自己的项目中。

此文件共两个功能:

  • 引入环信 WebIM-SDK
  • 将引入的 SDK 进行实例化

在这里插入图片描述

DEFAULT_APPKEY修改为自己已注册的 Appkey。

配置监听

<script setup>
import { EaseChatClient } from '@/IM/initwebsdk'
/* SDK连接 相关监听 */
EaseChatClient.addEventHandler('connection', {
    onConnected: () => {}, //与环信服务器建联成功回调。
    onDisconnected: () => {}, //与环信服务器断开成功回调。
    onOnline: () => {}, // 本机网络连接成功。
    onOffline: () => {},// 本机网络掉线。
    onError: (error) => {}, //SDK Error 回调
})
/* 好友关系相关监听 */
EaseChatClient.addEventHandler('friendListen', {
    // 收到好友邀请触发此方法。
    onContactInvited: (data) => {},
    // 联系人被删除时触发此方法。
    onContactDeleted: (data) => {},
    // 新增联系人会触发此方法。
    onContactAdded: (data) => {},
    // 好友请求被拒绝时触发此方法。
    onContactRefuse: (data) => {},
    // 好友请求被同意时触发此方法。
    onContactAgreed: (data) => {}
})
/* message 相关监听 */
EaseChatClient.addEventHandler('messageListen', {
    onTextMessage: function (message) {}, // 收到文本消息。
    onEmojiMessage: function (message) {}, // 收到表情消息。
    onImageMessage: function (message) {}, // 收到图片消息。
    onCmdMessage: function (message) {}, // 收到命令消息。
    onAudioMessage: function (message) {}, // 收到音频消息。
    onLocationMessage: function (message) {}, // 收到位置消息。
    onFileMessage: function (message) {}, // 收到文件消息。
    onCustomMessage: function (message) {}, // 收到自定义消息。
    onVideoMessage: function (message) {}, // 收到视频消息。
    onRecallMessage: function (message) {}, // 收到消息撤回回执。
})
</script>

创建测试 ID

在这里插入图片描述

登录环信

这一步是所有后续操作的第一步

<script setup>
import { EaseChatClient } from '@/IM/initwebsdk'
const loginValue = reactive({
    user: '', //你的测试环信ID
    password: '' //你的测试环信ID密码
})
//登录接口调用
const loginIM = async () => {
    try {
       await EaseChatClient.open({
       user: loginValue.username.toLowerCase(),
       pwd: loginValue.password.toLowerCase()
       }
      );
    } catch (error) {
      console.log('>>>>登录失败', error);
    }
}
</script>

紧接着是开始聊天部分。

好友关系

完成这个功能 需要将该项目开启两个页面,一个申请,一个接收,这样才能看到效果

两种方式:手动关联一个好友,第二种再创建一个测试 ID 之后,调用 SDK 添加好友。

方式一:测试时最简单的方式,手动关联好友

  1. 在管理后台中手动再创建一个 ID
    image.png

2.并手动将新创建的 ID 关联为好友。
在这里插入图片描述

方式二:开发时调用 SDK 接口添加好友

//申请添加好友
const applyAddFriends = () => {
  EaseChatClient.addContact(targetId, '我想加你为好友!');
};
//接收方登录将会触发
EaseChatClient.addEventHandler('friendListen', {
  // 收到好友邀请触发此方法。
  onContactInvited: (data) => {
    //同意申请
    EaseChatClient.acceptContactInvite(data.from);
    //拒绝申请
    EaseChatClient.declineContactInvite(data.from);
  },
});

进入页面获取好友列表并自行渲染。

<script setup>
//获取好友列表
const friendListData = reactive({})
const { data } = await EaseChatClient.getContacts()
data.length > 0 &&
        data.map(item => (friendListData[item] = { hxId: item }))
</script>

收发消息

完成这个功能 需要将该项目开启两个页面,一个发送,一个接收,这样才能看到效果

发送方发送一条文本消息:

<script setup>
const props = defineProps({
    nowPickInfo: {
        type: Object,
        required: true,
        default: () => ({})
    }
})
const { nowPickInfo } = toRefs(props)
const { ALL_MESSAGE_TYPE, CHAT_TYPE } = messageType
//发送文本内容
const textContent = ref('')
const sendTextMessage = _.debounce(async () => {
    //如果输入框全部为空格同样拒绝发送
    if (textContent.value.match(/^\s*$/)) return
    const msgOptions = {
        id: nowPickInfo.value.id, //要发送的目标ID
        chatType: nowPickInfo.value.chatType,
        msg: textContent.value,
    }
    textContent.value = '' //发送后清空输入框
    try {
        await store.dispatch('sendShowTypeMessage', { msgType: ALL_MESSAGE_TYPE.TEXT, msgOptions })
    } catch (error) {
        console.log('>>>>>>>发送失败+++++++', error)
    }
}, 50)
</script>

接收方接收消息

/* message 相关监听 */
EaseChatClient.addEventHandler('messageListen', {
  onTextMessage: function (message) {
    console.log('>>>>收到文本消息');
    pushNewMessage(message); //在缓存中Push一条新消息。
  }, // 收到文本消息。
});

缓存的消息结构示例

messageList:{
    //以好友的ID为KEY,如果获取则直接messageList[friendId]取到对应的消息。
    friendId:[
       {
        chatType:"singleChat", //聊天类型 单聊或者群聊
        ext:{}, //消息扩展
        from:friendId, //消息来源ID
        id:"1111864344594875684", //消息的唯一ID
        msg:"Hello World!",//消息内容
        time:1676440891009,//消息发送时间
        to:myId,//发送目标ID
        type:"txt" //消息来源
       },
       {
        chatType:"singleChat",
        ext:{},
        from:friendId,
        id:"1111864344594875684",
        msg:"Hello World2!",
        time:1676440891009,
        to:myId,
        type:"txt"
       }
    ],
    friendId2:[
       {
        chatType:"singleChat",
        ext:{},
        from:friendId,
        id:"1111864344594875684",
        msg:"Hello World!",
        time:1676440891009,
        to:myId,
        type:"txt"
       },
    ]
}

渲染消息列表

<script setup>
import { reactive, ref, computed, toRefs } from 'vue'
//获取其id对应的消息内容
const messageData = computed(() => {
    //如果Message.messageList中不存在的话调用拉取漫游取一下历史消息
    return nowPickInfo.value.id && store.state.Message.messageList[nowPickInfo.value.id] || fechHistoryMessage('fistLoad')()
})
<template>
    <div>
        <div class="messageList_box" v-for="(msgBody, index) in messageData" :key="msgBody.id">
            <div v-if="!msgBody.isRecall && msgBody.type !== ALL_MESSAGE_TYPE.INFORM" class="message_box_item"
                :style="{ flexDirection: (isMyself(msgBody) ? 'row-reverse' : 'row') }">
                <div class="message_item_time">{{ handleMsgTimeShow(msgBody.time, index) || '' }}</div>
                <el-avatar class="message_item_avator"
                    :src="isMyself(msgBody) ? loginUserInfo.avatarurl : otherUserInfo(msgBody.from).avatarurl || defaultAvatar">
                </el-avatar>
                <el-dropdown class="message_box_content"
                    :class="[isMyself(msgBody) ? 'message_box_content_mine' : 'message_box_content_other']"
                    trigger="contextmenu" placement="bottom-end">
                    <!-- 文本类型消息 -->
                    <p style="padding: 10px" v-if="msgBody.type === ALL_MESSAGE_TYPE.TEXT">
                        {{ msgBody.msg }}
                    </p>
                    <!-- 图片类型消息 -->
                    <!-- <div> -->
                    <el-image v-if="msgBody.type === ALL_MESSAGE_TYPE.IMAGE" style="border-radius:5px;"
                        :src="msgBody.thumb" :preview-src-list="[msgBody.url]" :initial-index="1" fit="cover" />
                    <!-- </div> -->
                    <!-- 语音类型消息 -->
                    <div :class="['message_box_content_audio', isMyself(msgBody) ? 'message_box_content_audio_mine' : 'message_box_content_audio_other']"
                        v-if="msgBody.type === ALL_MESSAGE_TYPE.AUDIO" @click="startplayAudio(msgBody, index)"
                        :style="`width:${msgBody.length * 10}px`">
                        <span class="audio_length_text">
                            {{ msgBody.length }}′′
                        </span>
                        <div :class="[isMyself(msgBody) ? 'play_audio_icon_mine' : 'play_audio_icon_other', audioPlayStatus.playIndex === index && 'start_play_audio']"
                            style=" background-size: 100% 100%;">
                        </div>
                    </div>
                    <div v-if="msgBody.type === ALL_MESSAGE_TYPE.LOCAL">
                        <p style="padding: 10px">[暂不支持位置消息展示]</p>
                    </div>
                    <!-- 文件类型消息 -->
                    <div v-if="msgBody.type === ALL_MESSAGE_TYPE.FILE" class="message_box_content_file">
                        <div class="file_text_box">
                            <div class="file_name">{{ msgBody.filename }}</div>
                            <div class="file_size">{{ fileSizeFormat(msgBody.file_length) }}</div>
                            <a class="file_download" :href="msgBody.url" download>点击下载</a>
                        </div>
                        <span class="iconfont icon-wenjian"></span>
                    </div>
                    <!-- 自定义类型消息 -->
                    <div v-if="msgBody.type === ALL_MESSAGE_TYPE.CUSTOM" class="message_box_content_custom">
                        <template v-if="msgBody.customEvent && CUSTOM_TYPE[msgBody.customEvent]">
                            <div class="user_card">
                                <div class="user_card_main">
                                    <!-- 头像 -->
                                    <el-avatar shape="circle" :size="50"
                                        :src="msgBody.customExts && msgBody.customExts.avatarurl || msgBody.customExts.avatar || defaultAvatar"
                                        fit="cover" />
                                    <!-- 昵称 -->
                                    <span class="nickname">{{ msgBody.customExts && msgBody.customExts.nickname ||
                                            msgBody.customExts.uid
                                    }}</span>
                                </div>
                                <el-divider style="margin:5px 0;  border-top:1px solid black;" />
                                <p style="font-size: 8px;">个人名片</p>
                            </div>
                        </template>
                    </div>
                    <template #dropdown>
                        <el-dropdown-menu>
                            <el-dropdown-item v-if="msgBody.type === ALL_MESSAGE_TYPE.TEXT && isSupported"
                                @click="copyTextMessages(msgBody.msg)">
                                复制
                            </el-dropdown-item>
                            <el-dropdown-item v-if="isMyself(msgBody)" @click="recallMessage(msgBody)">
                                撤回
                            </el-dropdown-item>
                            <el-dropdown-item @click="deleteMessage(msgBody)">
                                删除
                            </el-dropdown-item>
                            <el-dropdown-item v-if="!isMyself(msgBody)" @click="informOnMessage(msgBody)">
                                举报
                            </el-dropdown-item>
                        </el-dropdown-menu>
                    </template>

                </el-dropdown>
            </div>
            <div v-if="msgBody.isRecall" class="recall_style">{{ isMyself(msgBody) ? "你" : `${msgBody.from}`
            }}撤回了一条消息<span class="reEdit" v-show="isMyself(msgBody) && msgBody.type === ALL_MESSAGE_TYPE.TEXT"
                    @click="reEdit(msgBody.msg)">重新编辑</span></div>
            <div v-if="msgBody.type === ALL_MESSAGE_TYPE.INFORM" class="inform_style">
                <p>
                    {{ msgBody.msg }}
                </p>
            </div>

        </div>
        <ReportMessage ref="reportMessage" />
    </div>


</template>
</script>

了解即时通讯IM及应用场景请访问:环信官网
更多集成IM教程请访问:IMGeek社区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/358227.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习神经网络基础知识(一) 模型选择、欠拟合和过拟合

专栏&#xff1a;神经网络复现目录 深度学习神经网络基础知识(一) 本文讲述神经网络基础知识&#xff0c;具体细节讲述前向传播&#xff0c;反向传播和计算图&#xff0c;同时讲解神经网络优化方法&#xff1a;权重衰减&#xff0c;Dropout等方法&#xff0c;最后进行Kaggle实…

机器学习算法原理之k近邻 / KNN

文章目录k近邻 / KNN主要思想模型要素距离度量分类决策规则kd树主要思想kd树的构建kd树的搜索总结归纳k近邻 / KNN 主要思想 假定给定一个训练数据集&#xff0c;其中实例标签已定&#xff0c;当输入新的实例时&#xff0c;可以根据其最近的 kkk 个训练实例的标签&#xff0c…

5.5 配置路由反射器

5.3.2配置路由反射器 1. 实验目的 熟悉路由反射器的应用场景掌握路由反射器的配置方法2. 实验拓扑 实验拓扑如图5-5所示: 图5-5:配置路由反射器 3. 实验步骤 (1) 配置IP地址 R1的配置 <Huawei>sy…

JVM学习笔记三:运行时数据区之程序计数器

目录 概述 字节码取指令举例 CPU时间片 经典问题 使用PC寄存器存储字节码指令地址有什么用呢&#xff1f; 为什么使用PC寄存器记录当前线程的执行地址呢&#xff1f; 概述 运行时数据区中运行速度最快的存储区域&#xff0c;并且是线程私有的&#xff0c;每一个线程都具…

在线教育有什么优势?

AI 1、便捷性&#xff1a;在线教育可以让学生在家里或者其他任何地方学习&#xff0c;不受时间和地点的限制&#xff0c;可以随时随地学习&#xff0c;极大的方便了学习者。 2、节约成本&#xff1a;在线教育可以节约学习者的时间和金钱&#xff0c;学习者可以节省出去上学的…

【MFC】数据库操作——ODBC(20)

ODBC:开放式数据库连接&#xff0c;是为解决异构数据库&#xff08;不同数据库采用的数据存储方法不同&#xff09;共享而产生的。ODBC API相对来说非常复杂&#xff0c;这里介绍MFC的ODBC类。 添加ODBC用户DSN 首先&#xff0c;在计算机中添加用户DSN&#xff1a;(WIN10下&a…

详解js在事件中,如何传递复杂数据类型(数组,对象,函数)

文章目录 前言一、何谓预编译&#xff0c;变量提升&#xff1f;二、复杂数据类型的传递 1.数组2.对象3.函数总结前言 在JavaScript这门编程语言学习中&#xff0c;如何传参&#xff0c;什么是变量提升&#xff0c;js代码预编译等等。要想成为一名优秀的js高手&#xff0c;这些内…

一个页面分成几块展示

每一项占用一个div,里面展示的是具体的图文内容,页面底部展示的是当前页码和总数,实现效果如下: 代码如下: <div class"header"></div><div class"main-content"><divclass"equipment-item"v-for"item in equipmentL…

OpenCV-PyQT项目实战(8)项目案例03:鼠标定位

欢迎关注『OpenCV-PyQT项目实战 Youcans』系列&#xff0c;持续更新中 OpenCV-PyQT项目实战&#xff08;1&#xff09;安装与环境配置 OpenCV-PyQT项目实战&#xff08;2&#xff09;QtDesigner 和 PyUIC 快速入门 OpenCV-PyQT项目实战&#xff08;3&#xff09;信号与槽机制 …

信创引领丨呼叫中心加速适配国产化

随着信创产业的高速发展&#xff0c;企业服务软件高度适配国产操作系统成为大势所趋。早在2020年&#xff0c;佳信客服平台就正式通过“华为云鲲鹏云服务”平台系统的兼容性测试与认证&#xff0c;成为鲲鹏凌云伙伴&#xff0c;也意味着佳信客服平台正式与国产硬件平台及操作系…

【Kubernetes 企业项目实战】07、最新一代微服务网格 Istio 入门到企业实战(下)

目录 一、istio 核心资源解读 1.1 Gateway 总结&#xff1a; 1.2 VirtualService 1.2.1 VirtualService 主要由以下部分组成 1.2.2 virtualservice 配置路由规则 1.2.3 路由规则优先级 1.2.4 多路由规则解读 1.3 DestinationRule 二、istio 核心功能演示 2.1 断路器…

【离散数学】1. 数理逻辑

1.数理逻辑 2. 集合论 3. 代数系统 4. 图论 离散数学&#xff1a;研究离散量结构及相互关系的学科 数理逻辑集合论代数系统图论 逻辑&#xff1a;研究推理的科学 数学方法&#xff1a;引进一套符号系统的方法 数理逻辑是用数学方法研究形式逻辑的科学&#xff0c;即使用符号化…

vue环境总结

因将node.js升级后&#xff0c;打包运行出错了。后来加班重新改好。 一、用nvm管理可以用命令将node和npm对应安装 1.首先在控制台输入where node查看之前本地安装的node的路径 2.将node目录删除或者卸载 二、1.安装nvm 从官网下载安装包 https://github.com/coreybutler/nvm…

JDK定时器Timer原理

前言 前些时间想到利用redis实现延时队列&#xff0c;但是底层的定时器不止如何实现好些&#xff0c;故此研究了一下jdk的Timer。 Timer是一个用于执行定时任务的类&#xff0c;可以单次执行或按指定时间间隔循环执行&#xff08;直到主动cancel或线程被杀掉&#xff09;。Ti…

大数据之---Nifi-Nifi模板_具体使用方法---大数据之Nifi工作笔记0009

然后我们来看看,如果好不容易设计了一个流程,那么是可以通过 使用模板来让流程复用的 可以看到可以创建模板,一会说怎么用具体,上面已经 写清楚了,如何创建模板 我们看一下左侧的operate,这里可以看到这里就可以创建模板 可以看到小手那个地方,点击就可以创建模板了 可以看到…

python k8s库,read_namespaced_config_map:maximum recursion depth exceeded

使用背景 在python中&#xff0c;调用了gevent库&#xff0c;同时引用了官方的k8s库接口&#xff1a; GitHub - kubernetes-client/python: Official Python client library for kubernetesOfficial Python client library for kubernetes. Contribute to kubernetes-client/…

Node.js安装配置及Angular CLI的安装

NodeJS的安装node.js官网下载地址: https://nodejs.org/en/download/在node.js的官网上面下载适合自己机型的&#xff0c;如果是Windows系统的话&#xff0c;建议下载对应的 Windows Installer (.msi) 。下载完成后&#xff0c;双击打开安装&#xff0c;安装路径最好自定义&…

Melis4.0[D1s]:1.启动流程(与adc按键初始化相关部分)跟踪笔记

文章目录1.启动流程1.1 最先进入的文件&#xff1a;head_s.S1.2 start_kernel()函数所在的文件&#xff1a;init.c1.3 input_init()函数所在文件&#xff1a;sys_input.c1.4 INPUT_LKeyDevInit()所在文件&#xff1a;keyboarddev.c1.5 esINPUT_RegLdev()所在文件&#xff1a;in…

LVS负载均衡

文章目录前言一、LVS模式-DR二、ipvsadm配置参数三、DR模式的部署server1:调度器&#xff08;VS&#xff09;server2:真实服务器&#xff08;RS&#xff09;server3:真实服务器&#xff08;RS&#xff09;真实服务器(server2和server3)屏蔽客户端测试&#xff1a;纯代码步骤演示…

SpringCloud保姆级搭建教程六---ElasticSearch

es下载地址&#xff1a;https://www.elastic.co/cn/downloads/elasticsearch 最新版本或者 https://github.com/elastic/elasticsearch 7.17.9kibana下载地址&#xff1a;https://github.com/elastic/kibana 各个版本jdk8 对应的es应该是7.*版本&#xff0c;最新的es应该对应的…