赞
踩
前端刚入职小白,记录websocket在vue前端的实际应用。
首先,我们需要简单了解一下websocket:
HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理,HTTP 协议无法实现服务器主动向客户端发起消息。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 JavaScript 和 XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源。
Websocket应运而生,WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。
其实说到底就是将服务器发送过来的数据在通过websocket接收函数时,通过字段判断和操作
1:是正在和我聊天的人发的还是别人?
2:收到的未读信息数量?
3:我发送的人的标识ID。
4:我要通过一个方法来让后端知道我已经读取了信息。
5:也要注意发送和接收数据的时间。
话不多说,上代码:
- <script>
- export default {
- data() {
- return {
- selfId: 0,
- // 在线状态
- state: 1,
- goodsID:0,
- recenttext:'',
- selfListData: {},
- //搜索用户
- search: "",
- //用户列表渲染数据
- userListData: [],
- //用户点击选中变色
- act: 0,
- // 加号弹框
- dialogVisible: false,
- //模拟花间一壶酒用户的历史信息
- userInfoList2: [],
- //历史信息
- userInfoList: [],
- //输入框
- textarea: "",
- //滚动条距离顶部距离
- scrollTop: 0,
- //发送和输入显隐
- isshow: 0,
- websock: null,
- page: 1,
- page1: 1,
- urlhh: '',
- abcd: 0,
- index: -1,
- dialogInfo:{},
- firstRunFlag: true,
- other_id:0,
- category:0,
- isUpdate:true,
- };
- },
- created() {
- this.initWebSocket()
- },
- destroyed() {
- this.websock.close() //离开路由之后断开websocket连接
- },
-
- methods: {
- initWebSocket() {
- // token为访问令牌,一般登录成功,就会返回token
- const token = window.sessionStorage.getItem('token')
- // 其中线上的链接为wss://,在本地上的链接为ws://
- const wsuri = "wss://www.xxxxxxxx.com:8002/chat_ws/"
- // const wsuri = `ws://192.168.0.0:9000/chat_ws/`
- // 建立websocket连接
- this.websock = new WebSocket(wsuri, [token])
- // websocket接收信息
- this.websock.onmessage = this.websocketonmessage
- // 打开websocket
- this.websock.onopen = this.websocketonopen
- // websocket连接错误时重连
- this.websock.onerror = this.websocketonerror
- },
- websocketonopen() {
- window.console.log('连接成功')
- },
- websocketonerror() {
- //连接建立失败重连
- this.initWebSocket()
- },
- websocketonmessage(e) {
- //数据接收,首先要解析服务器传过来的数据
- const redata = JSON.parse(e.data)
- if (redata.msg_type==3) {
- // msg_type字段为3时,代表服务器给我发了文本信息了,注意是明确文本信息了
- // 例如对方发送 “你好” 给我时,msg_type就为3,text:'你好'
- // 下面这个if判断条件
- // this.dialogInfo.other_userid的意思是我跟这个人的对话框的ID
- // 比如我跟小明正在聊天,那我跟小明的对话框ID就假设为1
- // 此时我收到websocket服务器发来的文本信息text:在吗?,sender:1
- // 此时就意味着是小明给我发的信息,如果sender不是1,就代表着不是小明回我的
- if(this.dialogInfo.other_userid==redata.sender){
- // 如果跟我正在聊天的人给我回消息,下面这个代码,在此页面不影响
- // 这个传值的意思就是,将跟我聊天的对话框ID传递给其他组件或页面,可以用来判断未读信息条数,如果收到的信息的sender和我传递过去的ID相等,意味着未读信息就不需要加一
- this.$bus.$emit("hi",redata.sender);
- }
- // 当接收到数据时,就会更新消息列表,为防止没有对话框的用户不在信息列表,
- // 同时将该用户的ID记录下来,并记录已经更新过消息列表
- if(this.firstRunFlag==true){
- // 如果不是正在聊天的人给我回消息,就更新消息列表,为防止没有对话框的用户不在信息列表,
- if(this.dialogInfo.other_userid!=redata.sender){
- // getChatmessage()该函数为获取用户的列表,比如小明,小红等
- this.getChatmessage()
- }
- this.other_id=redata.sender
- this.firstRunFlag=false
- }
- // 当继续接受到数据时,由于已经记录过更新消息列表了,所以上一步不会执行
- // 当接收的数据如果和上次接收的数据发送ID相同时,也不会更新消息列表,因为该用户一定会有对话框在消息列表
- // 当接收的数据如果和上次接收的数据发送ID不相同时,即另外一个用户给我发消息时,
- // 又要更新消息列表,同样是防止没有对话框的用户不在信息列表
- if(this.other_id!=redata.sender){
- if(this.dialogInfo.other_userid!=redata.sender){
- this.getChatmessage()
- }
- this.firstRunFlag=true
- }
- //聊天框的人给你发消息时
- if(this.dialogInfo.other_userid==redata.sender){
- // userInfoList为信息列表
- this.userInfoList.push({
- url: this.urlhh, // url为用户的头像,
- info: redata.text, // 返回的文本信息
- timer: this.dealDate(), // 收到信息的时间
- position: "left", // 是对方给我发送信息,所以该信息应该是展示在左边
- });
- }
- // 这个for循环的意思是,遍历整个用户列表,找到发送的人的对话列表,然后将最近的信息和时间进行更新
- for(let i=0;i<this.userListData.length;i++){
- if(this.userListData[i].other_userid==redata.sender){
- this.userListData[i].last_message.text=redata.text,
- this.userListData[i].last_message.created=this.dealDate(new Date())
- break
- }
- }
- // 收到信息就把信息列表的滚动条置底
- this.$nextTick(() => { // 一定要用nextTick
- this.setPageScrollTo();
- //页面滚动条距离顶部高度等于这个盒子的高度
- this.$refs.scrollBox.scrollTop = this.$refs.scrollBox.scrollHeight;
- })
- } else if (redata.msg_type==2) {
- window.console.log('下线')
- } else if (redata.msg_type==1) {
- window.console.log('上线')
- }else if (redata.msg_type==9) {
- // unread_count为未读消息个数,msg_type为9,返回未读信息个数
- if(this.dialogInfo.other_userid==redata.sender&&redata.unread_count!=0){
- // 如果我正在跟小明聊天,他又给我发个信息,我这个方法不能自动已读,只能手动发送信息,来告诉服务器我已读了
- let actions = {msg_type:6,user_pk:this.dialogInfo.other_userid,random_id:-5,dialog_id:this.dialogInfo.id}
- this.websocketsend(JSON.stringify(actions))
- // 上述就是手动发送已读,msg_type为6向服务器发送已读信息
- // for循环遍历用户列表,如果发送者的ID是和正在跟我聊天人的ID相等,就强制将用户列表上的跟我聊天的未读信息为0
- for(let i=0;i<this.userListData.length;i++){
- if(this.userListData[i].other_userid==redata.sender){
- // this.userListData[i].unread_count=0
- let user = this.userListData[i]
- user.unread_count=0
- this.$set(this.userListData, i, user);
- break
- }
- }
- }
- for(let i=0;i<this.userListData.length;i++){
- if(this.userListData[i].other_userid==redata.sender){
- if(this.userListData[i].other_userid==this.dialogInfo.other_userid){
- // for循环遍历用户列表,如果发送者的ID是和正在跟我聊天人的ID相等,就强制将用户列表上的跟我聊天的未读信息为0
- // this.userListData[i].unread_count=0
- let user = this.userListData[i]
- user.unread_count=0
- this.$set(this.userListData, i, user);
- }else{
- // 如果发送者的ID和正在跟我聊天人的ID不相等,将用户列表上的那个人的未读信息重新渲染成最新的
- this.userListData[i].unread_count=redata.unread_count
- break
- }
- }
- }
- }
- },
- websocketsend(Data) {
- //数据发送
- this.websock.send(Data)
- },
- websocketclose() {
- //关闭
- },
- //获取用户自己的信息
- getSelf() {
- //getUserDetail接口为自己写的获取自己的用户信息的接口,如头像,昵称,ID等
- getUserDetail().then((response) => {
- this.selfListData = response.data
- }).catch(function () {
- });
- },
- load(){
- this.page1++
- //获取下一页聊天列表
- this.getChatnextmessage()
- },
- // 滚动条置底时触发
- scrollEvent (e) {
- if (e.srcElement.scrollTop + e.srcElement.clientHeight + 1 > e.srcElement.scrollHeight) {
- //当isUpdate为true时,表示用户列表信息还存在,就要继续获取下一页,当isUpdate为false时,用户信息列表没有更多了
- if(this.isUpdate) {
- this.load();
- } else {
- this.$message("消息列表已加载完毕");
- }
- }
- },
- //获取下一页列表聊天数据
- getChatnextmessage() {
- return new Promise(resolve=>{
- // chatexport为自己写的获取用户信息列表的接口,我这里是一次传20条数据
- chatexport({
- page:this.page1
- }).then((response)=> {
- if(response.data.results.length<20){
- // 如果返回的信息列表条数小于20,就代表着不会有更多的信息了
- this.isUpdate=false
- }
- let userListDatanext = response.data.results
- userListDatanext.forEach(item=>{
- if(item.other_userimage==null){
- item.other_userimage='这里放上匿名用户的头像'
- }
- // 处理时间的格式
- item.last_message.created = this.dealDate(item.last_message.created)
- })
- // 将获取的下一页数据合并到用户列表数组中
- this.userListData = this.userListData.concat(userListDatanext);
- resolve()
- }).catch(function (error) {
- window.console.log(error);
- resolve()
- });
- })
- },
- //获取全部渲染到列表聊天数据
- getChatmessage() {
- return new Promise(resolve=>{
- // 滚动条置顶
- this.$nextTick(() => {
- let scrollEl = this.$refs.mianscroll;
- scrollEl.scrollTo({ top: 0, behavior: 'smooth' });
- });
- this.isUpdate=true
- this.page1=1
- //原理和上个函数类似
- chatexport({
- page:this.page1
- }).then((response)=> {
- if(response.data.results.length<20){
- this.isUpdate=false
- }
- this.userListData = response.data.results
- this.userListData.forEach(item=>{
- if(item.other_userimage==null){
- item.other_userimage='这里放上匿名用户的头像'
- }
- item.last_message.created = this.dealDate(item.last_message.created)
- })
- resolve()
- }).catch(function (error) {
- window.console.log(error);
- resolve()
- });
- })
- },
- //获取个人聊天数据
- getPriChatmessage() {
- return new Promise((resolve)=>{
- //chatPriMessage为自己写的获取自己和别人的聊天记录,一次传20条数据
- chatPriMessage({
- dialog_with_id:this.act,//传值dialog_with_id为聊天对象的ID,我这里就是传对话框的ID,其实就是唯一识别就行
- page:this.page
- }).then((response)=> {
- if(response.data.results.length==0){
- //比如有40条聊天数据,获取第三页就为0了,就不能在继续获取了
- this.abcd=1
- //abcd为标识,为1时,不能再继续获取聊天数据了
- resolve()
- // 长度为0时,没数据了就return
- return
- }
- response.data.results.forEach(item=>{
- //is_out为true时为我发的信息,放右边
- if(item.is_out){
- //username字段可有可无(全文都是)
- this.userInfoList.unshift({
- url: this.selfListData.wechat_headimgurl,
- username: this.selfListData.name,
- info: item.text,
- timer: this.dealDate(item.created),//created字段为时间
- position: "right",
- });
- resolve()
- }else{
- //is_out为false时为聊天对象发的信息,放左边
- if(item.recipient_url){
- this.urlhh=item.recipient_url
- }else{
- this.urlhh='这里放匿名图片链接'
- }
- this.userInfoList.unshift({
- url: this.urlhh,
- username: item.recipient_username,
- info: item.text,
- timer: this.dealDate(item.created),
- position: "left",
- });
- resolve()
- }
- })
- }).catch(function (error) {
- window.console.log(error);
- });
- })
- },
- //获取个人最近感兴趣聊天数据(可有可无)
- getRecentmessage() {
- return new Promise((resolve)=>{
- // 这个接口可有可无
- getRecentgoods({
- dialog_with_id:this.act
- }).then((response)=> {
- if(response.data.results.length){
- this.recenttext=response.data.results[0].text
- this.recenttext=this.recenttext.substr(0,50)
- this.goodsID=response.data.results[0].object_id
- this.category=response.data.results[0].category
- }else{
- this.recenttext=''
- }
- resolve()
- }).catch(function (error) {
- window.console.log(error);
- });
- })
- },
- //此功能可有可无
- toDetail(){
- if(this.category==1){
- window.open('要去的链接', "_blank");
- }else if(this.category==2){
- window.open('要去的链接', "_blank");
- }else if(this.category==3){
- window.open('要去的链接', "_blank");
- }
- },
- //点击用户
- async getAct(val, index) {
- this.dialogInfo = val //点击用户列表上的详细信息
- this.page=1
- this.isshow = 1 //更改样式
- this.index=index
- this.act=val.other_userid
- this.getRecentmessage() //可有可无
- this.$bus.$emit("hello", this.userListData[index].unread_count);//传值给其他组件
- this.userListData[index].unread_count=0 //未读改为0
- let actions = {msg_type:6,user_pk:val.other_userid,random_id:-7,dialog_id:val.id}
- this.websocketsend(JSON.stringify(actions))//发送已读信息
- // 点击用户切换数据时先清除监听滚动事件,防止出现没有历史数据的用户,滚动条为0,会触发滚动事件
- this.$refs.scrollBox.removeEventListener("scroll", this.srTop);
- //点击变色
- this.userInfoList = [];
- await this.getPriChatmessage()
- //滚动条置底
- this.$nextTick(() => {
- this.setPageScrollTo();
- this.$refs.scrollBox.scrollTop = this.$refs.scrollBox.scrollHeight;
- })
- },
- //发送
- setUp() {
- if(this.textarea == "") {
- alert("发送信息不能为空!");
- return;
- }
- let actions = {text:this.textarea,msg_type:3,user_pk:this.act,random_id:-7}
- this.websocketsend(JSON.stringify(actions)) //发送至服务器
- this.userInfoList.push({
- url: this.selfListData.wechat_headimgurl,
- username: "超人",
- info: this.textarea,
- timer: this.dealDate(),
- position: "right",
- });//渲染到信息列表
- this.dialogInfo.last_message.text=this.textarea,//更新用户列表最近信息
- this.dialogInfo.last_message.created=this.dealDate(new Date()), //更新用户列表最近时间
- this.textarea = "";
- // 页面滚动到底部
- this.$nextTick(() => { // 一定要用nextTick
- this.setPageScrollTo();
- //页面滚动条距离顶部高度等于这个盒子的高度
- this.$refs.scrollBox.scrollTop = this.$refs.scrollBox.scrollHeight;
- })
- },
- // 监听键盘回车阻止换行并发送
- handlePushKeyword(event) {
- if (event.keyCode === 13) {
- event.preventDefault(); // 阻止浏览器默认换行操作
- this.setUp(); //发送文本
- return false;
- }
- },
- // 监听按的是ctrl + 回车,就换行
- lineFeed() {
- this.textarea = this.textarea + "\n";
- },
- //点击icon
- extend(val) {
- alert("你点击了:" + val);
- },
- //滚动条默认滚动到最底部
- setPageScrollTo() {
- //获取中间内容盒子的可见区域高度
- this.scrollTop = document.querySelector("#box").offsetHeight;
- setTimeout(() => {
- //加个定时器,防止上面高度没获取到,再获取一遍。
- if (this.scrollTop != this.$refs.scrollBox.offsetHeight) {
- this.scrollTop = document.querySelector("#box").offsetHeight;
- }
- }, 100);
- //scrollTop:滚动条距离顶部的距离。
- //把上面获取到的高度座位距离,把滚动条顶到最底部
- this.$refs.scrollBox.scrollTop = this.scrollTop;
- //判断是否有滚动条,有滚动条就创建一个监听滚动事件,滚动到顶部触发srTop方法
- this.$refs.scrollBox.addEventListener("scroll", this.srTop);
- },
- //滚动条到达顶部
- srTop() {
- //判断:当滚动条距离顶部为0时代表滚动到顶部了
- if (this.$refs.scrollBox.scrollTop == 0 && this.abcd == 0) {
- this.page = this.page + 1
- //获取下一页聊天数据
- this.getPriChatmessage()
- }
- },
- //处理时间函数
- dealDate(time) {
- let d = time ? new Date(time) : new Date();
- let year = d.getFullYear();
- let month = d.getMonth() + 1;
- let day = d.getDate();
- let hours = d.getHours();
- let min = d.getMinutes();
- let seconds = d.getSeconds();
- if (month < 10) month = '0' + month;
- if (day < 10) day = '0' + day;
- if (hours < 0) hours = '0' + hours;
- if (min < 10) min = '0' + min;
- if (seconds < 10) seconds = '0' + seconds;
-
- return (year + '/' + month + '/' + day + ' ' + hours + ':' + min);
- }
- },
- };
- </script>
上述代码中注释部分已经将前端的代码逻辑讲的大概了,其中后端接口和后端字段数据,我这是从后端同事取得。应当选择适合自己的方法和逻辑。
2023/10/23,记录
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。