当前位置:   article > 正文

SpringBoot+WebSocket+Vue整合实现在线聊天_java+vue实现客服在线聊天功能

java+vue实现客服在线聊天功能

一、WebSocket简介

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

 二、程序介绍

最近在做类似于电商网站的项目,其中需要用到消息推送、在线聊天的功能,本程序讲解怎么实现利用两个客户端完成在线聊天的功能,适用场景可以用于“客服聊天、多人群聊、消息推送”等。利用Vue做了一个比较简易的聊天界面,如下所示:

 三、后端实现

 1.引入此次项目的目的依赖包

  1. <!--websocket依赖-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-websocket</artifactId>
  5. </dependency>
  6. <!-- Gson依赖 -->
  7. <dependency>
  8. <groupId>com.squareup.retrofit2</groupId>
  9. <artifactId>converter-gson</artifactId>
  10. <version>2.6.2</version>
  11. </dependency>

注:WebSocket依赖是所必须的,毕竟要站在巨人的肩膀上进行开发,但其实也可以写成原生的,不需要依靠任何依赖,直接自己写底层,而Gson就不必多做介绍了,我这里主要是想将发送给前端的有效数据转成以json的形式。

2.编写WebSocket的配置类

  1. package com.chen.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.socket.server.standard.ServerEndpointExporter;
  5. /**
  6. * @author chen
  7. * @date 2019/10/26
  8. * @email 15218979950@163.com
  9. * @description WebSocketConfig配置类,注入对象ServerEndpointExporter,
  10. * * 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
  11. */
  12. @Configuration
  13. public class WebSocketConfig {
  14. @Bean
  15. public ServerEndpointExporter serverEndpointExporter() {
  16. return new ServerEndpointExporter();
  17. }
  18. }

3.封装消息实体

  1. package com.chen.entity;
  2. /**
  3. * @author chen
  4. * @date 2019/10/26
  5. * @email 15218979950@163.com
  6. * @description 消息实体
  7. */
  8. public class SocketMsg {
  9. private int type; //聊天类型0:群聊,1:单聊.
  10. private String fromUser;//发送者.
  11. private String toUser;//接受者.
  12. private String msg;//消息
  13. public int getType() {
  14. return type;
  15. }
  16. public void setType(int type) {
  17. this.type = type;
  18. }
  19. public String getFromUser() {
  20. return fromUser;
  21. }
  22. public void setFromUser(String fromUser) {
  23. this.fromUser = fromUser;
  24. }
  25. public String getToUser() {
  26. return toUser;
  27. }
  28. public void setToUser(String toUser) {
  29. this.toUser = toUser;
  30. }
  31. public String getMsg() {
  32. return msg;
  33. }
  34. public void setMsg(String msg) {
  35. this.msg = msg;
  36. }
  37. }

4.WebSocket实现类

  1. package com.chen.service;
  2. import com.chen.entity.SocketMsg;
  3. import com.fasterxml.jackson.core.JsonParseException;
  4. import com.fasterxml.jackson.databind.JsonMappingException;
  5. import com.fasterxml.jackson.databind.ObjectMapper;
  6. import com.google.gson.Gson;
  7. import org.springframework.stereotype.Component;
  8. import javax.websocket.*;
  9. import javax.websocket.server.PathParam;
  10. import javax.websocket.server.ServerEndpoint;
  11. import java.io.IOException;
  12. import java.util.HashMap;
  13. import java.util.Map;
  14. import java.util.concurrent.CopyOnWriteArraySet;
  15. /**
  16. * @author chen
  17. * @date 2019/10/26
  18. * @email 15218979950@163.com
  19. * @description websocket的具体实现类
  20. *  使用springboot的唯一区别是要@Component声明,而使用独立容器是由容器自己管理websocket的,
  21. *  但在springboot中连容器都是spring管理的。
  22. *  虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,
  23. *  所以可以用一个静态set保存起来
  24. */
  25. @ServerEndpoint(value = "/websocket/{nickname}")
  26. @Component
  27. public class WebSocketService {
  28. private String nickname;
  29. private Session session;
  30. //用来存放每个客户端对应的MyWebSocket对象。
  31. private static CopyOnWriteArraySet<WebSocketService> webSocketSet = new CopyOnWriteArraySet<WebSocketService>();
  32. //与某个客户端的连接会话,需要通过它来给客户端发送数据
  33. //用来记录sessionId和该session进行绑定
  34. private static Map<String, Session> map = new HashMap<String, Session>();
  35. /**
  36. * 连接建立成功调用的方法
  37. */
  38. @OnOpen
  39. public void onOpen(Session session, @PathParam("nickname") String nickname) {
  40. Map<String,Object> message=new HashMap<String, Object>();
  41. this.session = session;
  42. this.nickname = nickname;
  43. map.put(session.getId(), session);
  44. webSocketSet.add(this);//加入set中
  45. System.out.println("有新连接加入:" + nickname + ",当前在线人数为" + webSocketSet.size());
  46. //this.session.getAsyncRemote().sendText("恭喜" + nickname + "成功连接上WebSocket(其频道号:" + session.getId() + ")-->当前在线人数为:" + webSocketSet.size());
  47. message.put("type",0); //消息类型,0-连接成功,1-用户消息
  48. message.put("people",webSocketSet.size()); //在线人数
  49. message.put("name",nickname); //昵称
  50. message.put("aisle",session.getId()); //频道号
  51. this.session.getAsyncRemote().sendText(new Gson().toJson(message));
  52. }
  53. /**
  54. * 连接关闭调用的方法    
  55. */
  56. @OnClose
  57. public void onClose() {
  58. webSocketSet.remove(this); //从set中删除
  59. System.out.println("有一连接关闭!当前在线人数为" + webSocketSet.size());
  60. }
  61. /**
  62. * 收到客户端消息后调用的方法
  63. * @param message 客户端发送过来的消息
  64. */
  65. @OnMessage
  66. public void onMessage(String message, Session session, @PathParam("nickname") String nickname) {
  67. System.out.println("来自客户端的消息-->" + nickname + ": " + message);
  68. //从客户端传过来的数据是json数据,所以这里使用jackson进行转换为SocketMsg对象,
  69. // 然后通过socketMsg的type进行判断是单聊还是群聊,进行相应的处理:
  70. ObjectMapper objectMapper = new ObjectMapper();
  71. SocketMsg socketMsg;
  72. try {
  73. socketMsg = objectMapper.readValue(message, SocketMsg.class);
  74. if (socketMsg.getType() == 1) {
  75. //单聊.需要找到发送者和接受者.
  76. socketMsg.setFromUser(session.getId());//发送者.
  77. Session fromSession = map.get(socketMsg.getFromUser());
  78. Session toSession = map.get(socketMsg.getToUser());
  79. //发送给接受者.
  80. if (toSession != null) {
  81. //发送给发送者.
  82. Map<String,Object> m=new HashMap<String, Object>();
  83. m.put("type",1);
  84. m.put("name",nickname);
  85. m.put("msg",socketMsg.getMsg());
  86. fromSession.getAsyncRemote().sendText(new Gson().toJson(m));
  87. toSession.getAsyncRemote().sendText(new Gson().toJson(m));
  88. } else {
  89. //发送给发送者.
  90. fromSession.getAsyncRemote().sendText("系统消息:对方不在线或者您输入的频道号不对");
  91. }
  92. } else {
  93. //群发消息
  94. broadcast(nickname + ": " + socketMsg.getMsg());
  95. }
  96. } catch (JsonParseException e) {
  97. e.printStackTrace();
  98. } catch (JsonMappingException e) {
  99. e.printStackTrace();
  100. } catch (IOException e) {
  101. e.printStackTrace();
  102. }
  103. }
  104. /**
  105. * 发生错误时调用   
  106. */
  107. @OnError
  108. public void onError(Session session, Throwable error) {
  109. System.out.println("发生错误");
  110. error.printStackTrace();
  111. }
  112. /**
  113. * 群发自定义消息
  114. */
  115. public void broadcast(String message) {
  116. for (WebSocketService item : webSocketSet) {
  117. item.session.getAsyncRemote().sendText(message);//异步发送消息.
  118. }
  119. }
  120. }

四、前端实现

这里为了方便开发,合理地显示出效果,我使用了Element-ui框架。

  1. <template>
  2. <div>
  3. <el-row :gutter="20">
  4. <el-col :span="12" :offset="6">
  5. <div class="main">
  6. <el-row>
  7. <el-input
  8. placeholder="请输入自己的昵称"
  9. prefix-icon="el-icon-user-solid"
  10. v-model="name"
  11. style="width:50%"
  12. ></el-input>
  13. <el-button type="primary" @click="conectWebSocket()">建立连接</el-button>
  14. <el-button type="danger">断开连接</el-button>
  15. </el-row>
  16. <el-row>
  17. <el-input
  18. placeholder="请输入对方频道号"
  19. prefix-icon="el-icon-phone"
  20. v-model="aisle"
  21. style="width:40%"
  22. ></el-input>
  23. </el-row>
  24. <el-row>
  25. <el-input
  26. placeholder="请输入要发送的消息"
  27. prefix-icon="el-icon-s-promotion"
  28. v-model="messageValue"
  29. style="width:50%"
  30. ></el-input>
  31. <el-button type="primary" @click="sendMessage()">发送</el-button>
  32. </el-row>
  33. <div class="message">
  34. <div v-for="(value,key,index) in messageList" :key="index">
  35. <el-tag v-if="value.name==name" type="success" style="float:right">我:{{value.msg}}</el-tag>
  36. <br />
  37. <el-tag v-if="value.name!=name" style="float:left">{{value.name}}:{{value.msg}}</el-tag>
  38. <br />
  39. </div>
  40. </div>
  41. </div>
  42. </el-col>
  43. </el-row>
  44. </div>
  45. </template>
  46. <script>
  47. export default {
  48. data() {
  49. return {
  50. name: "", // 昵称
  51. websocket: null, // WebSocket对象
  52. aisle: "", // 对方频道号
  53. messageList: [], // 消息列表
  54. messageValue: "" // 消息内容
  55. };
  56. },
  57. methods: {
  58. conectWebSocket: function() {
  59. console.log("建立连接");
  60. if (this.name === "") {
  61. this.$alert("请输入自己的昵称", "提示", {
  62. confirmButtonText: "确定",
  63. callback: action => {}
  64. });
  65. } else {
  66. //判断当前浏览器是否支持WebSocket
  67. if ("WebSocket" in window) {
  68. this.websocket = new WebSocket(
  69. "ws://localhost:8080/websocket/" + this.name
  70. );
  71. } else {
  72. alert("不支持建立socket连接");
  73. }
  74. //连接发生错误的回调方法
  75. this.websocket.onerror = function() {
  76. };
  77. //连接成功建立的回调方法
  78. this.websocket.onopen = function(event) {
  79. };
  80. //接收到消息的回调方法
  81. var that = this;
  82. this.websocket.onmessage = function(event) {
  83. var object = eval("(" + event.data + ")");
  84. console.log(object);
  85. if (object.type == 0) {
  86. // 提示连接成功
  87. console.log("连接成功");
  88. that.showInfo(object.people, object.aisle);
  89. }
  90. if (object.type == 1) {
  91. //显示消息
  92. console.log("接受消息");
  93. that.messageList.push(object);
  94. }
  95. };
  96. //连接关闭的回调方法
  97. this.websocket.onclose = function() {};
  98. //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
  99. window.onbeforeunload = function() {
  100. this.websocket.close();
  101. };
  102. }
  103. },
  104. // 发送消息
  105. sendMessage: function() {
  106. var socketMsg = { msg: this.messageValue, toUser: this.aisle };
  107. if (this.aisle == "") {
  108. //群聊.
  109. socketMsg.type = 0;
  110. } else {
  111. //单聊.
  112. socketMsg.type = 1;
  113. }
  114. this.websocket.send(JSON.stringify(socketMsg));
  115. },
  116. showInfo: function(people, aisle) {
  117. this.$notify({
  118. title: "当前在线人数:" + people,
  119. message: "您的频道号:" + aisle,
  120. duration: 0
  121. });
  122. }
  123. }
  124. };
  125. </script>
  126. <style scoped>
  127. .main {
  128. position: relative;
  129. top: 20px;
  130. }
  131. .message {
  132. position: relative;
  133. overflow:auto;
  134. top: 20px;
  135. width: 100%;
  136. height: 40%;
  137. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
  138. padding: 5px;
  139. }
  140. </style>

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

闽ICP备14008679号