当前位置:   article > 正文

springboot websocket_SpringBoot+Vue+WebSocket 实现在线聊天

spring boot + vue 集成 websocket 在线聊天

ae5a76e4555156ddc467a94e3c6d2bb3.png

一、前言

本文将基于 SpringBoot + Vue + WebSocket 实现一个简单的在线聊天功能

页面如下:

8644076f5426d11da2a92f7be5612633.png

在线体验地址:http://www.zhengqingya.com:8101

二、SpringBoot + Vue + WebSocket 实现在线聊天

1、引入websocket依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-websocket</artifactId>
  4. </dependency>

2、websocket 配置类

  1. @Configuration
  2. public class WebSocketConfig {
  3. @Bean
  4. public ServerEndpointExporter serverEndpointExporter() {
  5. return new ServerEndpointExporter();
  6. }
  7. }

3、websocket 处理类Controller

  1. @Slf4j
  2. @Component
  3. @ServerEndpoint("/groupChat/{sid}/{userId}")
  4. public class WebSocketServerController {
  5. /**
  6. * 房间号 -> 组成员信息
  7. */
  8. private static ConcurrentHashMap<String, List<Session>> groupMemberInfoMap = new ConcurrentHashMap<>();
  9. /**
  10. * 房间号 -> 在线人数
  11. */
  12. private static ConcurrentHashMap<String, Set<Integer>> onlineUserMap = new ConcurrentHashMap<>();
  13. /**
  14. * 收到消息调用的方法,群成员发送消息
  15. *
  16. * @param sid:房间号
  17. * @param userId:用户id
  18. * @param message:发送消息
  19. */
  20. @OnMessage
  21. public void onMessage(@PathParam("sid") String sid, @PathParam("userId") Integer userId, String message) {
  22. List<Session> sessionList = groupMemberInfoMap.get(sid);
  23. Set<Integer> onlineUserList = onlineUserMap.get(sid);
  24. // 先一个群组内的成员发送消息
  25. sessionList.forEach(item -> {
  26. try {
  27. // json字符串转对象
  28. MsgVO msg = JSONObject.parseObject(message, MsgVO.class);
  29. msg.setCount(onlineUserList.size());
  30. // json对象转字符串
  31. String text = JSONObject.toJSONString(msg);
  32. item.getBasicRemote().sendText(text);
  33. } catch (IOException e) {
  34. e.printStackTrace();
  35. }
  36. });
  37. }
  38. /**
  39. * 建立连接调用的方法,群成员加入
  40. *
  41. * @param session
  42. * @param sid
  43. */
  44. @OnOpen
  45. public void onOpen(Session session, @PathParam("sid") String sid, @PathParam("userId") Integer userId) {
  46. List<Session> sessionList = groupMemberInfoMap.computeIfAbsent(sid, k -> new ArrayList<>());
  47. Set<Integer> onlineUserList = onlineUserMap.computeIfAbsent(sid, k -> new HashSet<>());
  48. onlineUserList.add(userId);
  49. sessionList.add(session);
  50. // 发送上线通知
  51. sendInfo(sid, userId, onlineUserList.size(), "上线了~");
  52. }
  53. public void sendInfo(String sid, Integer userId, Integer onlineSum, String info) {
  54. // 获取该连接用户信息
  55. User currentUser = ApplicationContextUtil.getApplicationContext().getBean(UserMapper.class).selectById(userId);
  56. // 发送通知
  57. MsgVO msg = new MsgVO();
  58. msg.setCount(onlineSum);
  59. msg.setUserId(userId);
  60. msg.setAvatar(currentUser.getAvatar());
  61. msg.setMsg(currentUser.getNickName() + info);
  62. // json对象转字符串
  63. String text = JSONObject.toJSONString(msg);
  64. onMessage(sid, userId, text);
  65. }
  66. /**
  67. * 关闭连接调用的方法,群成员退出
  68. *
  69. * @param session
  70. * @param sid
  71. */
  72. @OnClose
  73. public void onClose(Session session, @PathParam("sid") String sid, @PathParam("userId") Integer userId) {
  74. List<Session> sessionList = groupMemberInfoMap.get(sid);
  75. sessionList.remove(session);
  76. Set<Integer> onlineUserList = onlineUserMap.get(sid);
  77. onlineUserList.remove(userId);
  78. // 发送离线通知
  79. sendInfo(sid, userId, onlineUserList.size(), "下线了~");
  80. }
  81. /**
  82. * 传输消息错误调用的方法
  83. *
  84. * @param error
  85. */
  86. @OnError
  87. public void OnError(Throwable error) {
  88. log.info("Connection error");
  89. }
  90. }

4、websocket 消息显示类

  1. @Data
  2. @ApiModel(description = "websocket消息内容")
  3. public class MsgVO {
  4. @ApiModelProperty(value = "用户id")
  5. private Integer userId;
  6. @ApiModelProperty(value = "用户名")
  7. private String username;
  8. @ApiModelProperty(value = "用户头像")
  9. private String avatar;
  10. @ApiModelProperty(value = "消息")
  11. private String msg;
  12. @ApiModelProperty(value = "在线人数")
  13. private int count;
  14. }

5、前端页面

温馨小提示:当用户登录成功之后,可以发起websocket连接,存在store中...

下面只是单页面的简单实现

  1. <template>
  2. <div class="chat-box">
  3. <header>聊天室 (在线:{{count}}人)</header>
  4. <div class="msg-box" ref="msg-box">
  5. <div
  6. v-for="(i,index) in list"
  7. :key="index"
  8. class="msg"
  9. :style="i.userId == userId?'flex-direction:row-reverse':''"
  10. >
  11. <div class="user-head">
  12. <img :src="i.avatar" height="30" width="30" :title="i.username">
  13. </div>
  14. <div class="user-msg">
  15. <span :style="i.userId == userId?' float: right;':''" :class="i.userId == userId?'right':'left'">{{i.content}}</span>
  16. </div>
  17. </div>
  18. </div>
  19. <div class="input-box">
  20. <input type="text" ref="sendMsg" v-model="contentText" @keyup.enter="sendText()" />
  21. <div class="btn" :class="{['btn-active']:contentText}" @click="sendText()">发送</div>
  22. </div>
  23. </div>
  24. </template>
  25. <script>
  26. export default {
  27. data() {
  28. return {
  29. ws: null,
  30. count: 0,
  31. userId: this.$store.getters.id, // 当前用户ID
  32. username: this.$store.getters.name, // 当前用户昵称
  33. avatar: this.$store.getters.avatar, // 当前用户头像
  34. list: [], // 聊天记录的数组
  35. contentText: "" // input输入的值
  36. };
  37. },
  38. mounted() {
  39. this.initWebSocket();
  40. },
  41. destroyed() {
  42. // 离开页面时关闭websocket连接
  43. this.ws.onclose(undefined);
  44. },
  45. methods: {
  46. // 发送聊天信息
  47. sendText() {
  48. let _this = this;
  49. _this.$refs["sendMsg"].focus();
  50. if (!_this.contentText) {
  51. return;
  52. }
  53. let params = {
  54. userId: _this.userId,
  55. username: _this.username,
  56. avatar: _this.avatar,
  57. msg: _this.contentText,
  58. count: _this.count
  59. };
  60. _this.ws.send(JSON.stringify(params)); //调用WebSocket send()发送信息的方法
  61. _this.contentText = "";
  62. setTimeout(() => {
  63. _this.scrollBottm();
  64. }, 500);
  65. },
  66. // 进入页面创建websocket连接
  67. initWebSocket() {
  68. let _this = this;
  69. // 判断页面有没有存在websocket连接
  70. if (window.WebSocket) {
  71. var serverHot = window.location.hostname;
  72. let sip = '房间号'
  73. // 填写本地IP地址 此处的 :9101端口号 要与后端配置的一致!
  74. var url = 'ws://' + serverHot + ':9101' + '/groupChat/' + sip + '/' + this.userId; // `ws://127.0.0.1/9101/groupChat/10086/聊天室`
  75. let ws = new WebSocket(url);
  76. _this.ws = ws;
  77. ws.onopen = function(e) {
  78. console.log("服务器连接成功: " + url);
  79. };
  80. ws.onclose = function(e) {
  81. console.log("服务器连接关闭: " + url);
  82. };
  83. ws.onerror = function() {
  84. console.log("服务器连接出错: " + url);
  85. };
  86. ws.onmessage = function(e) {
  87. //接收服务器返回的数据
  88. let resData = JSON.parse(e.data)
  89. _this.count = resData.count;
  90. _this.list = [
  91. ..._this.list,
  92. { userId: resData.userId, username: resData.username, avatar: resData.avatar, content: resData.msg }
  93. ];
  94. };
  95. }
  96. },
  97. // 滚动条到底部
  98. scrollBottm() {
  99. let el = this.$refs["msg-box"];
  100. el.scrollTop = el.scrollHeight;
  101. }
  102. }
  103. };
  104. </script>
  105. <style lang="scss" scoped>
  106. .chat-box {
  107. margin: 0 auto;
  108. background: #fafafa;
  109. position: absolute;
  110. height: 100%;
  111. width: 100%;
  112. max-width: 700px;
  113. header {
  114. position: fixed;
  115. width: 100%;
  116. height: 3rem;
  117. background: #409eff;
  118. max-width: 700px;
  119. display: flex;
  120. justify-content: center;
  121. align-items: center;
  122. font-weight: bold;
  123. color: white;
  124. font-size: 1rem;
  125. }
  126. .msg-box {
  127. position: absolute;
  128. height: calc(100% - 6.5rem);
  129. width: 100%;
  130. margin-top: 3rem;
  131. overflow-y: scroll;
  132. .msg {
  133. width: 95%;
  134. min-height: 2.5rem;
  135. margin: 1rem 0.5rem;
  136. position: relative;
  137. display: flex;
  138. justify-content: flex-start !important;
  139. .user-head {
  140. min-width: 2.5rem;
  141. width: 20%;
  142. width: 2.5rem;
  143. height: 2.5rem;
  144. border-radius: 50%;
  145. background: #f1f1f1;
  146. display: flex;
  147. justify-content: center;
  148. align-items: center;
  149. .head {
  150. width: 1.2rem;
  151. height: 1.2rem;
  152. }
  153. // position: absolute;
  154. }
  155. .user-msg {
  156. width: 80%;
  157. // position: absolute;
  158. word-break: break-all;
  159. position: relative;
  160. z-index: 5;
  161. span {
  162. display: inline-block;
  163. padding: 0.5rem 0.7rem;
  164. border-radius: 0.5rem;
  165. margin-top: 0.2rem;
  166. font-size: 0.88rem;
  167. }
  168. .left {
  169. background: white;
  170. animation: toLeft 0.5s ease both 1;
  171. }
  172. .right {
  173. background: #53a8ff;
  174. color: white;
  175. animation: toright 0.5s ease both 1;
  176. }
  177. @keyframes toLeft {
  178. 0% {
  179. opacity: 0;
  180. transform: translateX(-10px);
  181. }
  182. 100% {
  183. opacity: 1;
  184. transform: translateX(0px);
  185. }
  186. }
  187. @keyframes toright {
  188. 0% {
  189. opacity: 0;
  190. transform: translateX(10px);
  191. }
  192. 100% {
  193. opacity: 1;
  194. transform: translateX(0px);
  195. }
  196. }
  197. }
  198. }
  199. }
  200. .input-box {
  201. padding: 0 0.5rem;
  202. position: absolute;
  203. bottom: 0;
  204. width: 100%;
  205. height: 3.5rem;
  206. background: #fafafa;
  207. box-shadow: 0 0 5px #ccc;
  208. display: flex;
  209. justify-content: space-between;
  210. align-items: center;
  211. input {
  212. height: 2.3rem;
  213. display: inline-block;
  214. width: 100%;
  215. padding: 0.5rem;
  216. border: none;
  217. border-radius: 0.2rem;
  218. font-size: 0.88rem;
  219. }
  220. .btn {
  221. height: 2.3rem;
  222. min-width: 4rem;
  223. background: #e0e0e0;
  224. padding: 0.5rem;
  225. font-size: 0.88rem;
  226. color: white;
  227. text-align: center;
  228. border-radius: 0.2rem;
  229. margin-left: 0.5rem;
  230. transition: 0.5s;
  231. }
  232. .btn-active {
  233. background: #409eff;
  234. }
  235. }
  236. }
  237. </style>

本文案例demo源码

https://gitee.com/zhengqingya/xiao-xiao-su

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

闽ICP备14008679号