基于阿里云的直播聊天(web主播端)


基于阿里云的直播聊天(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判断即可

以上就是基于阿里云聊天的全部代码逻辑与坑点


文章作者: 冷杨威
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 冷杨威 !
  目录
-->