当前位置:   article > 正文

vue2实现websocket的聊天应用_vue2 websocket

vue2 websocket

前端刚入职小白,记录websocket在vue前端的实际应用。

1:websocket简单介绍

        首先,我们需要简单了解一下websocket:

HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理,HTTP 协议无法实现服务器主动向客户端发起消息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 JavaScript 和 XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源。

Websocket应运而生,WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。

2:websocket的实现

其实说到底就是将服务器发送过来的数据在通过websocket接收函数时,通过字段判断和操作

1:是正在和我聊天的人发的还是别人?

2:收到的未读信息数量?

3:我发送的人的标识ID。

4:我要通过一个方法来让后端知道我已经读取了信息。

5:也要注意发送和接收数据的时间。

话不多说,上代码:

  1. <script>
  2. export default {
  3. data() {
  4. return {
  5. selfId: 0,
  6. // 在线状态
  7. state: 1,
  8. goodsID:0,
  9. recenttext:'',
  10. selfListData: {},
  11. //搜索用户
  12. search: "",
  13. //用户列表渲染数据
  14. userListData: [],
  15. //用户点击选中变色
  16. act: 0,
  17. // 加号弹框
  18. dialogVisible: false,
  19. //模拟花间一壶酒用户的历史信息
  20. userInfoList2: [],
  21. //历史信息
  22. userInfoList: [],
  23. //输入框
  24. textarea: "",
  25. //滚动条距离顶部距离
  26. scrollTop: 0,
  27. //发送和输入显隐
  28. isshow: 0,
  29. websock: null,
  30. page: 1,
  31. page1: 1,
  32. urlhh: '',
  33. abcd: 0,
  34. index: -1,
  35. dialogInfo:{},
  36. firstRunFlag: true,
  37. other_id:0,
  38. category:0,
  39. isUpdate:true,
  40. };
  41. },
  42. created() {
  43. this.initWebSocket()
  44. },
  45. destroyed() {
  46. this.websock.close() //离开路由之后断开websocket连接
  47. },
  48. methods: {
  49. initWebSocket() {
  50. // token为访问令牌,一般登录成功,就会返回token
  51. const token = window.sessionStorage.getItem('token')
  52. // 其中线上的链接为wss://,在本地上的链接为ws://
  53. const wsuri = "wss://www.xxxxxxxx.com:8002/chat_ws/"
  54. // const wsuri = `ws://192.168.0.0:9000/chat_ws/`
  55. // 建立websocket连接
  56. this.websock = new WebSocket(wsuri, [token])
  57. // websocket接收信息
  58. this.websock.onmessage = this.websocketonmessage
  59. // 打开websocket
  60. this.websock.onopen = this.websocketonopen
  61. // websocket连接错误时重连
  62. this.websock.onerror = this.websocketonerror
  63. },
  64. websocketonopen() {
  65. window.console.log('连接成功')
  66. },
  67. websocketonerror() {
  68. //连接建立失败重连
  69. this.initWebSocket()
  70. },
  71. websocketonmessage(e) {
  72. //数据接收,首先要解析服务器传过来的数据
  73. const redata = JSON.parse(e.data)
  74. if (redata.msg_type==3) {
  75. // msg_type字段为3时,代表服务器给我发了文本信息了,注意是明确文本信息了
  76. // 例如对方发送 “你好” 给我时,msg_type就为3,text:'你好'
  77. // 下面这个if判断条件
  78. // this.dialogInfo.other_userid的意思是我跟这个人的对话框的ID
  79. // 比如我跟小明正在聊天,那我跟小明的对话框ID就假设为1
  80. // 此时我收到websocket服务器发来的文本信息text:在吗?,sender:1
  81. // 此时就意味着是小明给我发的信息,如果sender不是1,就代表着不是小明回我的
  82. if(this.dialogInfo.other_userid==redata.sender){
  83. // 如果跟我正在聊天的人给我回消息,下面这个代码,在此页面不影响
  84. // 这个传值的意思就是,将跟我聊天的对话框ID传递给其他组件或页面,可以用来判断未读信息条数,如果收到的信息的sender和我传递过去的ID相等,意味着未读信息就不需要加一
  85. this.$bus.$emit("hi",redata.sender);
  86. }
  87. // 当接收到数据时,就会更新消息列表,为防止没有对话框的用户不在信息列表,
  88. // 同时将该用户的ID记录下来,并记录已经更新过消息列表
  89. if(this.firstRunFlag==true){
  90. // 如果不是正在聊天的人给我回消息,就更新消息列表,为防止没有对话框的用户不在信息列表,
  91. if(this.dialogInfo.other_userid!=redata.sender){
  92. // getChatmessage()该函数为获取用户的列表,比如小明,小红等
  93. this.getChatmessage()
  94. }
  95. this.other_id=redata.sender
  96. this.firstRunFlag=false
  97. }
  98. // 当继续接受到数据时,由于已经记录过更新消息列表了,所以上一步不会执行
  99. // 当接收的数据如果和上次接收的数据发送ID相同时,也不会更新消息列表,因为该用户一定会有对话框在消息列表
  100. // 当接收的数据如果和上次接收的数据发送ID不相同时,即另外一个用户给我发消息时,
  101. // 又要更新消息列表,同样是防止没有对话框的用户不在信息列表
  102. if(this.other_id!=redata.sender){
  103. if(this.dialogInfo.other_userid!=redata.sender){
  104. this.getChatmessage()
  105. }
  106. this.firstRunFlag=true
  107. }
  108. //聊天框的人给你发消息时
  109. if(this.dialogInfo.other_userid==redata.sender){
  110. // userInfoList为信息列表
  111. this.userInfoList.push({
  112. url: this.urlhh, // url为用户的头像,
  113. info: redata.text, // 返回的文本信息
  114. timer: this.dealDate(), // 收到信息的时间
  115. position: "left", // 是对方给我发送信息,所以该信息应该是展示在左边
  116. });
  117. }
  118. // 这个for循环的意思是,遍历整个用户列表,找到发送的人的对话列表,然后将最近的信息和时间进行更新
  119. for(let i=0;i<this.userListData.length;i++){
  120. if(this.userListData[i].other_userid==redata.sender){
  121. this.userListData[i].last_message.text=redata.text,
  122. this.userListData[i].last_message.created=this.dealDate(new Date())
  123. break
  124. }
  125. }
  126. // 收到信息就把信息列表的滚动条置底
  127. this.$nextTick(() => { // 一定要用nextTick
  128. this.setPageScrollTo();
  129. //页面滚动条距离顶部高度等于这个盒子的高度
  130. this.$refs.scrollBox.scrollTop = this.$refs.scrollBox.scrollHeight;
  131. })
  132. } else if (redata.msg_type==2) {
  133. window.console.log('下线')
  134. } else if (redata.msg_type==1) {
  135. window.console.log('上线')
  136. }else if (redata.msg_type==9) {
  137. // unread_count为未读消息个数,msg_type为9,返回未读信息个数
  138. if(this.dialogInfo.other_userid==redata.sender&&redata.unread_count!=0){
  139. // 如果我正在跟小明聊天,他又给我发个信息,我这个方法不能自动已读,只能手动发送信息,来告诉服务器我已读了
  140. let actions = {msg_type:6,user_pk:this.dialogInfo.other_userid,random_id:-5,dialog_id:this.dialogInfo.id}
  141. this.websocketsend(JSON.stringify(actions))
  142. // 上述就是手动发送已读,msg_type为6向服务器发送已读信息
  143. // for循环遍历用户列表,如果发送者的ID是和正在跟我聊天人的ID相等,就强制将用户列表上的跟我聊天的未读信息为0
  144. for(let i=0;i<this.userListData.length;i++){
  145. if(this.userListData[i].other_userid==redata.sender){
  146. // this.userListData[i].unread_count=0
  147. let user = this.userListData[i]
  148. user.unread_count=0
  149. this.$set(this.userListData, i, user);
  150. break
  151. }
  152. }
  153. }
  154. for(let i=0;i<this.userListData.length;i++){
  155. if(this.userListData[i].other_userid==redata.sender){
  156. if(this.userListData[i].other_userid==this.dialogInfo.other_userid){
  157. // for循环遍历用户列表,如果发送者的ID是和正在跟我聊天人的ID相等,就强制将用户列表上的跟我聊天的未读信息为0
  158. // this.userListData[i].unread_count=0
  159. let user = this.userListData[i]
  160. user.unread_count=0
  161. this.$set(this.userListData, i, user);
  162. }else{
  163. // 如果发送者的ID和正在跟我聊天人的ID不相等,将用户列表上的那个人的未读信息重新渲染成最新的
  164. this.userListData[i].unread_count=redata.unread_count
  165. break
  166. }
  167. }
  168. }
  169. }
  170. },
  171. websocketsend(Data) {
  172. //数据发送
  173. this.websock.send(Data)
  174. },
  175. websocketclose() {
  176. //关闭
  177. },
  178. //获取用户自己的信息
  179. getSelf() {
  180. //getUserDetail接口为自己写的获取自己的用户信息的接口,如头像,昵称,ID等
  181. getUserDetail().then((response) => {
  182. this.selfListData = response.data
  183. }).catch(function () {
  184. });
  185. },
  186. load(){
  187. this.page1++
  188. //获取下一页聊天列表
  189. this.getChatnextmessage()
  190. },
  191. // 滚动条置底时触发
  192. scrollEvent (e) {
  193. if (e.srcElement.scrollTop + e.srcElement.clientHeight + 1 > e.srcElement.scrollHeight) {
  194. //当isUpdate为true时,表示用户列表信息还存在,就要继续获取下一页,当isUpdate为false时,用户信息列表没有更多了
  195. if(this.isUpdate) {
  196. this.load();
  197. } else {
  198. this.$message("消息列表已加载完毕");
  199. }
  200. }
  201. },
  202. //获取下一页列表聊天数据
  203. getChatnextmessage() {
  204. return new Promise(resolve=>{
  205. // chatexport为自己写的获取用户信息列表的接口,我这里是一次传20条数据
  206. chatexport({
  207. page:this.page1
  208. }).then((response)=> {
  209. if(response.data.results.length<20){
  210. // 如果返回的信息列表条数小于20,就代表着不会有更多的信息了
  211. this.isUpdate=false
  212. }
  213. let userListDatanext = response.data.results
  214. userListDatanext.forEach(item=>{
  215. if(item.other_userimage==null){
  216. item.other_userimage='这里放上匿名用户的头像'
  217. }
  218. // 处理时间的格式
  219. item.last_message.created = this.dealDate(item.last_message.created)
  220. })
  221. // 将获取的下一页数据合并到用户列表数组中
  222. this.userListData = this.userListData.concat(userListDatanext);
  223. resolve()
  224. }).catch(function (error) {
  225. window.console.log(error);
  226. resolve()
  227. });
  228. })
  229. },
  230. //获取全部渲染到列表聊天数据
  231. getChatmessage() {
  232. return new Promise(resolve=>{
  233. // 滚动条置顶
  234. this.$nextTick(() => {
  235. let scrollEl = this.$refs.mianscroll;
  236. scrollEl.scrollTo({ top: 0, behavior: 'smooth' });
  237. });
  238. this.isUpdate=true
  239. this.page1=1
  240. //原理和上个函数类似
  241. chatexport({
  242. page:this.page1
  243. }).then((response)=> {
  244. if(response.data.results.length<20){
  245. this.isUpdate=false
  246. }
  247. this.userListData = response.data.results
  248. this.userListData.forEach(item=>{
  249. if(item.other_userimage==null){
  250. item.other_userimage='这里放上匿名用户的头像'
  251. }
  252. item.last_message.created = this.dealDate(item.last_message.created)
  253. })
  254. resolve()
  255. }).catch(function (error) {
  256. window.console.log(error);
  257. resolve()
  258. });
  259. })
  260. },
  261. //获取个人聊天数据
  262. getPriChatmessage() {
  263. return new Promise((resolve)=>{
  264. //chatPriMessage为自己写的获取自己和别人的聊天记录,一次传20条数据
  265. chatPriMessage({
  266. dialog_with_id:this.act,//传值dialog_with_id为聊天对象的ID,我这里就是传对话框的ID,其实就是唯一识别就行
  267. page:this.page
  268. }).then((response)=> {
  269. if(response.data.results.length==0){
  270. //比如有40条聊天数据,获取第三页就为0了,就不能在继续获取了
  271. this.abcd=1
  272. //abcd为标识,为1时,不能再继续获取聊天数据了
  273. resolve()
  274. // 长度为0时,没数据了就return
  275. return
  276. }
  277. response.data.results.forEach(item=>{
  278. //is_out为true时为我发的信息,放右边
  279. if(item.is_out){
  280. //username字段可有可无(全文都是)
  281. this.userInfoList.unshift({
  282. url: this.selfListData.wechat_headimgurl,
  283. username: this.selfListData.name,
  284. info: item.text,
  285. timer: this.dealDate(item.created),//created字段为时间
  286. position: "right",
  287. });
  288. resolve()
  289. }else{
  290. //is_out为false时为聊天对象发的信息,放左边
  291. if(item.recipient_url){
  292. this.urlhh=item.recipient_url
  293. }else{
  294. this.urlhh='这里放匿名图片链接'
  295. }
  296. this.userInfoList.unshift({
  297. url: this.urlhh,
  298. username: item.recipient_username,
  299. info: item.text,
  300. timer: this.dealDate(item.created),
  301. position: "left",
  302. });
  303. resolve()
  304. }
  305. })
  306. }).catch(function (error) {
  307. window.console.log(error);
  308. });
  309. })
  310. },
  311. //获取个人最近感兴趣聊天数据(可有可无)
  312. getRecentmessage() {
  313. return new Promise((resolve)=>{
  314. // 这个接口可有可无
  315. getRecentgoods({
  316. dialog_with_id:this.act
  317. }).then((response)=> {
  318. if(response.data.results.length){
  319. this.recenttext=response.data.results[0].text
  320. this.recenttext=this.recenttext.substr(0,50)
  321. this.goodsID=response.data.results[0].object_id
  322. this.category=response.data.results[0].category
  323. }else{
  324. this.recenttext=''
  325. }
  326. resolve()
  327. }).catch(function (error) {
  328. window.console.log(error);
  329. });
  330. })
  331. },
  332. //此功能可有可无
  333. toDetail(){
  334. if(this.category==1){
  335. window.open('要去的链接', "_blank");
  336. }else if(this.category==2){
  337. window.open('要去的链接', "_blank");
  338. }else if(this.category==3){
  339. window.open('要去的链接', "_blank");
  340. }
  341. },
  342. //点击用户
  343. async getAct(val, index) {
  344. this.dialogInfo = val //点击用户列表上的详细信息
  345. this.page=1
  346. this.isshow = 1 //更改样式
  347. this.index=index
  348. this.act=val.other_userid
  349. this.getRecentmessage() //可有可无
  350. this.$bus.$emit("hello", this.userListData[index].unread_count);//传值给其他组件
  351. this.userListData[index].unread_count=0 //未读改为0
  352. let actions = {msg_type:6,user_pk:val.other_userid,random_id:-7,dialog_id:val.id}
  353. this.websocketsend(JSON.stringify(actions))//发送已读信息
  354. // 点击用户切换数据时先清除监听滚动事件,防止出现没有历史数据的用户,滚动条为0,会触发滚动事件
  355. this.$refs.scrollBox.removeEventListener("scroll", this.srTop);
  356. //点击变色
  357. this.userInfoList = [];
  358. await this.getPriChatmessage()
  359. //滚动条置底
  360. this.$nextTick(() => {
  361. this.setPageScrollTo();
  362. this.$refs.scrollBox.scrollTop = this.$refs.scrollBox.scrollHeight;
  363. })
  364. },
  365. //发送
  366. setUp() {
  367. if(this.textarea == "") {
  368. alert("发送信息不能为空!");
  369. return;
  370. }
  371. let actions = {text:this.textarea,msg_type:3,user_pk:this.act,random_id:-7}
  372. this.websocketsend(JSON.stringify(actions)) //发送至服务器
  373. this.userInfoList.push({
  374. url: this.selfListData.wechat_headimgurl,
  375. username: "超人",
  376. info: this.textarea,
  377. timer: this.dealDate(),
  378. position: "right",
  379. });//渲染到信息列表
  380. this.dialogInfo.last_message.text=this.textarea,//更新用户列表最近信息
  381. this.dialogInfo.last_message.created=this.dealDate(new Date()), //更新用户列表最近时间
  382. this.textarea = "";
  383. // 页面滚动到底部
  384. this.$nextTick(() => { // 一定要用nextTick
  385. this.setPageScrollTo();
  386. //页面滚动条距离顶部高度等于这个盒子的高度
  387. this.$refs.scrollBox.scrollTop = this.$refs.scrollBox.scrollHeight;
  388. })
  389. },
  390. // 监听键盘回车阻止换行并发送
  391. handlePushKeyword(event) {
  392. if (event.keyCode === 13) {
  393. event.preventDefault(); // 阻止浏览器默认换行操作
  394. this.setUp(); //发送文本
  395. return false;
  396. }
  397. },
  398. // 监听按的是ctrl + 回车,就换行
  399. lineFeed() {
  400. this.textarea = this.textarea + "\n";
  401. },
  402. //点击icon
  403. extend(val) {
  404. alert("你点击了:" + val);
  405. },
  406. //滚动条默认滚动到最底部
  407. setPageScrollTo() {
  408. //获取中间内容盒子的可见区域高度
  409. this.scrollTop = document.querySelector("#box").offsetHeight;
  410. setTimeout(() => {
  411. //加个定时器,防止上面高度没获取到,再获取一遍。
  412. if (this.scrollTop != this.$refs.scrollBox.offsetHeight) {
  413. this.scrollTop = document.querySelector("#box").offsetHeight;
  414. }
  415. }, 100);
  416. //scrollTop:滚动条距离顶部的距离。
  417. //把上面获取到的高度座位距离,把滚动条顶到最底部
  418. this.$refs.scrollBox.scrollTop = this.scrollTop;
  419. //判断是否有滚动条,有滚动条就创建一个监听滚动事件,滚动到顶部触发srTop方法
  420. this.$refs.scrollBox.addEventListener("scroll", this.srTop);
  421. },
  422. //滚动条到达顶部
  423. srTop() {
  424. //判断:当滚动条距离顶部为0时代表滚动到顶部了
  425. if (this.$refs.scrollBox.scrollTop == 0 && this.abcd == 0) {
  426. this.page = this.page + 1
  427. //获取下一页聊天数据
  428. this.getPriChatmessage()
  429. }
  430. },
  431. //处理时间函数
  432. dealDate(time) {
  433. let d = time ? new Date(time) : new Date();
  434. let year = d.getFullYear();
  435. let month = d.getMonth() + 1;
  436. let day = d.getDate();
  437. let hours = d.getHours();
  438. let min = d.getMinutes();
  439. let seconds = d.getSeconds();
  440. if (month < 10) month = '0' + month;
  441. if (day < 10) day = '0' + day;
  442. if (hours < 0) hours = '0' + hours;
  443. if (min < 10) min = '0' + min;
  444. if (seconds < 10) seconds = '0' + seconds;
  445. return (year + '/' + month + '/' + day + ' ' + hours + ':' + min);
  446. }
  447. },
  448. };
  449. </script>

3:总结

上述代码中注释部分已经将前端的代码逻辑讲的大概了,其中后端接口和后端字段数据,我这是从后端同事取得。应当选择适合自己的方法和逻辑。

2023/10/23,记录

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小惠珠哦/article/detail/873502
推荐阅读
相关标签
  

闽ICP备14008679号