基于阿里云的直播聊天(web主播端)
这篇是基于阿里云的聊天,某个用户进入直播间,指定某个用户的禁言和解除禁言,全体禁言和全体解除禁言,弥补网上缺乏此类文章,以此记录
HTML
<!--这里使用的tab组件进行聊天与用户列表的切换-->
<div class="aside">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="消息" name="first">
<div class="info">
<!--消息列表模块-->
<div class="chat">
<p class="infoTitle">童鞋,欢迎来到直播间!为营造良好的学习交流氛围和健康的网络环境,请您在发言时文明用语。</p>
<div class="list-wrap" style="overflow:auto" ref="listWrap">
<!-- 虚拟列表所有项的高度 -->
<div class="scroll-bar" ref="scrollBar" style="top: 20px;position: relative;"></div>
<!-- 虚拟列表项 -->
<ul class="list" ref="list" v-if="titleInfo?.live_status===1">
<li v-for="item,index in showList" :key="index">
<template v-if="item?.userName">
<span class="who" style="overflow:auto">{[item?.userName]} :
<span class="what" v-text="item.Data"></span>
</span>
</template>
<template v-else style="text-align: center;">
<p id="userJoin">{[item.content]}</p>
</template>
</li>
</ul>
</div>
</div>
<!--发送消息模块-->
<div class="sendInfo">
<el-input v-model="teacherMessage" class="srInfo" type="textarea" :autosize="{ minRows: 6, maxRows: 6}" placeholder="请输入..."> </el-input>
<div style="position: absolute;left: 13px;bottom: 5px;cursor: pointer;display: flex;" v-if="!allJy" @click="doChangeJy1">
<img src="/static/img/index/noChat.png" alt="" style="width: 18px;">
<span style="color:#fff;font-size:12px;">解除禁言</span>
</div>
<div style="position: absolute;left: 13px;bottom: 5px;cursor: pointer;display: flex;" v-if="allJy" @click="doChangeJy">
<img src="/static/img/index/Chat.png" alt="" style="width: 18px;">
<span style="color:#fff;font-size:12px;">全体禁言</span>
</div>
<el-button v-if="teacherMessage?.trim().length" id="sendInfoOk" type="primary" :loading="btnFlagz" @click="teacherSendInfo" v-text="btnFlagz ? '发送中' : '发送'"></el-button>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="学生" name="second">
<div class="infinite-list-wrapper" style="overflow:auto">
<div class="nowSum" v-text="'当前在线人数:'+userSum"></div>
<ul v-infinite-scroll="load" style="overflow:auto">
<li v-for="item,index in userList" style="position: relative;" :key="index">
<span class="who" style="overflow:auto" v-text="item?.userId"></span>
<span @click="doHidden1(item,index)" style="position: absolute;right: 10px;top: 5px;cursor: pointer;" v-if="!item?.jy"><img src="/static/img/index/noChat.png" style="width: 18px;"></span>
<span @click="doHidden2(item,index)" style="position: absolute;right: 10px;top: 5px;cursor: pointer;" v-else><img src="/static/img/index/chat.png" style="width: 18px;"></span>
</li>
</ul>
</div>
</el-tab-pane>
</el-tabs>
</div>
CSS
// 用户进入,离开直播间样式
#userJoin {
color:pink;
margin: 0;
width: 310px;
height: 28px;
background: #333B47;
border-radius: 17px 17px 17px 17px;
color: rgba(255,255,255,0.6);
text-align: center;
line-height: 27px;
}
.aside {
box-sizing: border-box;
padding: 0 0 0 16px;
width: 360px;
height: 600px;
background-color: #1B2128;
}
.info {
position: relative;
width: 336px;
height: 546px;
background: #262D37;
}
.... 以下的样式就不写了,主要是看Vue代码
以上两点,结构与样式不要过于关注,主要是看下面的Vue代码
VUE
1.引入SDK
<script src='https://g.alicdn.com/video-cloud-fe/aliyun-interaction-sdk/1.0.3/aliyun-interaction-sdk.web.min.js'></script>
2.开始代码
new Vue({
data() {
// 阿里云长连接初始化
InteractionEngine: null,
InteractionInstance: null,
deviceId: null,
// 推流所有参数对象
pushObj: null,
// 所有列表消息
showList: [],
// 消息传输
InteractionEventNames: null,
InteractionMessageTypes: null,
// 人员列表
userSum: 0, // 在线人数
userList: [], //人员列表
// 禁言指定人员
muteUserList: [],
// 解除禁言
noMuteUserList: [],
// id消息组
groupId: null
},
mounted() {
// 长连接初始化
this.InteractionEngine = window.AliyunInteraction.InteractionEngine
this.InteractionInstance = this.InteractionEngine.create();
},
methods:{
// 判断是否为JSON
isJSON(str) {
if (typeof str == 'string') {
try {
JSON.parse(str);
return true;
} catch (e) {
console.log(e);
return false;
}
}
},
// 消息滑到底部
scrollToBottom() {
this.$nextTick(() => {
this.$refs['listWrap'].scrollTop = this.$refs['listWrap'].scrollHeight
})
},
// 可以在进入时调用,或者在点击某个按钮时调用,但是,一旦调用,即聊天,禁言,用户进入与离开开始
aliyunSdk() {
/* 1.获取sdk 所需 token
#1 踩坑1 阿里云官方说是调用 const tokenObj = await Appservices.getToken()去获取SDK所需的token,
不是,他根本没给你提供Appservices这个实例,很容易在这里让你的代码就夭折,这里是去掉后端的接口拿所需的token。
这里需要后端去看阿里云指定的代码
*/
const resToken = await syhtAjaxGet('/api/live/getMessageToken', {
kid: JSON.parse(this.openObj.get('kid')), //这个是我们后端需要知道在哪里聊天,这里看你们后端需要什么,每个都不一样
deviceId: this.InteractionEngine.getDeviceId, // 这里的deviceId,官方提供了,必传
deviceType: 'web' // 我们是web端,传入web,必传
})
// 2.根据token建立长连接
this.InteractionInstance.auth(resToken.data.AccessToken).then(() => {
//这里不用管
const params2 = {
extension: {
title: '自定义',
},
};
/* 3.创建消息组
必须我们这边发起人创建消息组,不能让后端创建,后端创建的无法进行用户禁言,
且安卓,IOS都需要我们传入给后端,用我们的groupId
*/
this.InteractionInstance.createGroup(params2).then(async (res) => {
// 创建消息组成功
console.log(res.groupId); // 新创建的 groupId
// 将新创建的 groupId发送给后端,让后端记录给安卓与IOS使用
await syhtAjaxPost('/api/live/addLiveGroup', {
kid: JSON.parse(this.openObj.get('kid')),
group_id: res.groupId
})
this.groupId = res.groupId // 存入data中,后续使用
// 获取用户列表函数
this.getUserList()
const params = {
groupId: this.groupId, // 消息组id
userNick: localStorage.getItem('teacher_name'), // 用户昵称
userAvatar: '', // 非必填,传入头像图片地址
userExtension: '', // 非必填,可传入额外的 JSONString 文本
broadCastType: 2, // 系统消息扩散类型,0: 不扩散;1:扩散到指定人; 2:扩散到群组
broadCastStatistics: true, // 是否扩散统计类消息
};
// 这里加入的人员与离开的,在第四步的监听中都能监听到
this.InteractionInstance.joinGroup(params).then(() => {
console.log("加入消息组")
}).catch((err) => {
// 加入消息组失败
})
const params1 = {
groupId: this.groupId, // 消息组id,传入我们开始存的
};
this.InteractionInstance.getGroup(params1).then((res) => {
// 获取消息组信息成功, res 为消息组信息对象
console.log(res, "消息组信息对象")
})
.catch((err) => {
// 获取消息组信息失败
});
// 4.监听新来的消息,进入的人员,离开的人员
this.InteractionEventNames = window.AliyunInteraction.InteractionEventNames
this.InteractionMessageTypes = window.AliyunInteraction.InteractionMessageTypes
this.InteractionInstance.on(this.InteractionEventNames.Message, (eventData) => {
console.log('收到信息啦', eventData);
const {
type,
data,
messageId,
senderId,
senderInfo
} = eventData;
// 这里由于安卓和IOS给我的数据结构不同做些调整,你们根据自己的调整,this.isJSON()是否为JSON的函数封装
if (!(Object.prototype.toString.call(data) === '[object Object]') && this.isJSON(senderInfo)) {
post = JSON.parse(senderInfo)
this.showList.push({
userName: post.userNick,
Data: data
})
this.teacherMessage = '' // 清空发言
this.scrollToBottom() // 有新消息自动滑到底部
} else if (!(Object.prototype.toString.call(data) === '[object Object]') && !this.isJSON(senderInfo)) {
console.log(this.isJSON(senderInfo))
this.showList.push({
userName: senderInfo.userNick,
Data: data
})
this.teacherMessage = ''
this.scrollToBottom()
console.log(this.showList, "接受信息2")
}
switch (type) {
// 由于聊天没有点赞,这里不用管
case this.InteractionMessageTypes.PaaSLikeInfo:
// 点赞事件 1001,data 为点赞数据
break;
case this.InteractionMessageTypes.PaaSUserJoin:
// 用户加入事件 1002,data 为直播间统计数据
// 如果说是老师本人则是您,如果是其他人进入这是用户名 + 进入直播间
if (senderInfo.userNick === localStorage.getItem('teacher_name')) {
this.showList.push({
content: "您已进入直播间"
})
} else {
this.showList.push({
content: `${senderInfo.userNick}已进入直播间`
})
}
break;
case this.InteractionMessageTypes.PaaSUserLeave:
// 用户离开事件 1003,与用户进入一样
if (senderInfo.userNick === localStorage.getItem('teacher_name')) {
this.showList.push({
content: "您已离开直播间"
})
} else {
this.showList.push({
content: `${senderInfo.userNick}已离开直播间`
})
}
break;
// 以下的禁言可以在监听这里处理,也可以在禁言的同时处理
case this.InteractionMessageTypes.PaaSMuteGroup:
// 互动消息组被禁言 1004
break;
case this.InteractionMessageTypes.PaaSCancelMuteGroup:
// 互动消息组取消禁言 1005
break;
case this.InteractionMessageTypes.PaaSMuteUser:
// 某个用户被禁言 1006,data 为用户信息
break;
case this.InteractionMessageTypes.PaaSCancelMuteUser:
// 某个用户被取消禁言 1007,data 为用户信息
break;
default:
break;
}
});
}).catch((err) => {
// 创建消息组失败
});
});
},
// 禁言全体
async doChangeJy() {
const params = {
groupId: this.groupId, // 消息组id
broadCastType: 2, // 系统消息扩散类型,0: 不扩散;1:扩散到指定人; 2:扩散到群组
};
this.InteractionInstance.muteAll(params).then(() => { // 注意:muteAll
// 消息组禁言成功
this.allJy = !this.allJy
this.$message.success("全体禁言成功")
}).catch((err) => {
// 消息组禁言失败
this.$message.success("全体禁言失败")
});
},
// 解除全体禁言
async doChangeJy1() {
const params = {
groupId: this.groupId, // 消息组id
broadCastType: 2, // 系统消息扩散类型,0: 不扩散;1:扩散到指定人; 2:扩散到群组
};
this.InteractionInstance.cancelMuteAll(params).then(() => { // 注意:cancelMuteAll
// 消息组禁言成功
this.allJy = !this.allJy
this.$message.success("全体解除禁言成功")
}).catch((err) => {
// 消息组禁言失败
this.$message.success("全体解除禁言失败")
});
},
// 指定单个禁言
async doHidden1(item, ind) {
this.muteUserList = []
this.muteUserList.push(item.userId)
const params = {
groupId: this.groupId, // 消息组id
muteUserList: this.muteUserList, // 需要禁言的用户列表,最大200个
// 取消禁言的用户列表字段名为 cancelMuteUserList
muteTime: 0, // 非必填,禁言的时间,单位为s,如果不传或者传0则采用默认禁言时间
broadCastType: 2, // 系统消息扩散类型,0: 不扩散;1:扩散到指定人; 2:扩散到群组
};
this.InteractionInstance.muteUser(params).then((res) => {
this.$nextTick(() => {
item.jy = !item.jy
})
this.$forceUpdate()
console.log(item, "禁言")
this.$message.success("禁言成功")
}).catch((err) => {
// 禁言用户失败
console.log(err)
});
},
// 解除单个禁言,特别注意,调用的是cancelMuteUser这个,文档写的是cancelMuteAll,妈的,害我一致没搞清楚,网上找了也没有遇到过这种问题的
// 我当时在想是不是他单词写错了,结果他妈的还真是
doHidden2(item, ind) {
this.noMuteUserList = []
this.noMuteUserList.push(item.userId)
const params = {
groupId: this.groupId, // 消息组id
cancelMuteUserList: this.noMuteUserList, // 需要禁言的用户列表,最大200个
// 取消禁言的用户列表字段名为 cancelMuteUserList
muteTime: 0, // 非必填,禁言的时间,单位为s,如果不传或者传0则采用默认禁言时间
broadCastType: 2, // 系统消息扩散类型,0: 不扩散;1:扩散到指定人; 2:扩散到群组
};
this.InteractionInstance.cancelMuteUser(params)
.then((res) => {
this.$nextTick(() => {
item.jy = !item.jy
})
this.$forceUpdate()
this.$message.success("解除禁言成功")
})
.catch((err) => {
// 禁言用户失败
console.log(err)
});
},
// 获取用户列表
async getUserList() {
const params = {
groupId: this.groupId, // 消息组id
sortType: 1, // 排序方式,0-时间递增顺序,1-时间递减顺序
pageNum: 1, // 分页拉取的索引下标,第一次调用传1,后续调用+1
pageSize: 50, // 分页拉取的大小,默认20条,最大50条
};
this.InteractionInstance.listGroupUser(params).then((res) => {
// 获取消息组成员列表成功
const {
total, // 总数
userList, // 返回的消息组的在线成员列表
hasMore, // 是否还剩数据
} = res;
console.log(res, "人员列表")
// 这里不用管,是处理数据,你们按照自己的来就行
if (this.userList.length === 0) {
this.userList.push(...JSON.parse(JSON.stringify(userList)))
this.userList.forEach(item => {
if (!item.jy) {
item.jy = false
}
})
} else {
if (this.userList.length === userList.length) {
console.log("此时数据不改变")
} else if (this.userList.length < userList.length) {
let arr = []
this.userList.forEach(eve => {
arr.push(eve.userId)
})
userList.forEach(item => {
if (arr.indexOf(item.userId) == -1) {
this.userList.push(item)
}
})
this.userList.forEach(item => {
if (!item.jy) {
item.jy = false
}
})
}else if(this.userList.length > userList.length) {
let arr = []
userList.forEach(eve => {
arr.push(eve.userId)
})
this.userList.forEach((item,index) => {
if (!(arr.indexOf(item.userId) != -1)) {
this.userList.splice(index,1)
}
})
this.userList.forEach(item => {
if (!item.jy) {
item.jy = false
}
})
}
}
this.userSum = total
})
.catch((err) => {
// 获取消息组成员列表失败
});
},
// 断开长链接,在你退出时触发
stopAliYunSdk() {
this.InteractionInstance.logout().then(res => {
console.log(res, "长连接断开成功")
}).catch(err => {
console.log(err, "长链接断开失败")
})
}
}
})
注意
坑点一:就是传入SDK所需要的token,文档写的是const tokenObj = await Appservices.getToken();通过Appservices去获取,其实不是,而是通过后端接口去获取的
坑点二:取消指定用户的禁言,文档写的是与之对应的是取消禁言消息组内指定成员接口:cancelMuteAll。,不是他这么写的,他这里文档写错了,你如果按照他这样写,你拿到的永远是1005的状态码,而不是1007的状态码。正确的是cancelMuteUser
坑点三:要与安卓和IOS规定消息格式,不然会多出很多判断
坑点四:groupId要由我们自己创建,而不是拿后端的,安卓与IOS的也是需要我们把groupId传给后端后,再去获取。不然禁言的时候会显示你没有权限禁言
坑点五:你可能在发送消息以后无法拿到当前的用户名称,第一种情况是在你发送时,传递的参数用户名没传。第二种情况就是为了进入同一个消息组,在刷新之后无法获取用户名,这种情况你可以通过消息体带给安卓和ios,不然他们无法获取发送消息的名字
坑点六:当你消息组创建完毕,将消息组Id传递给后端后,这里是对坑点五的补充,在刷新后,又会创建一次消息组,那么安卓与IOS的学生们则与你不会在同一个消息组,需要你通过后端,把你传递过去的消息组Id拿过来,加入,这里则不需要创建了。这里需要写一个判断,如果有消息Id,则不创建,没有则创建消息组。以上没有没写这段代码,就加一个if判断即可
以上就是基于阿里云聊天的全部代码逻辑与坑点