zoukankan      html  css  js  c++  java
  • SpringBoot+Vue+WebSocket 实现在线聊天

    一、前言

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

    页面如下:
    在这里插入图片描述

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

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

    1、引入websocket依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    

    2、websocket 配置类

    @Configuration
    public class WebSocketConfig {
        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
    }
    

    3、websocket 处理类Controller

    @Slf4j
    @Component
    @ServerEndpoint("/groupChat/{sid}/{userId}")
    public class WebSocketServerController {
    
        /**
         * 房间号 -> 组成员信息
         */
        private static ConcurrentHashMap<String, List<Session>> groupMemberInfoMap = new ConcurrentHashMap<>();
        /**
         * 房间号 -> 在线人数
         */
        private static ConcurrentHashMap<String, Set<Integer>> onlineUserMap = new ConcurrentHashMap<>();
    
        /**
         * 收到消息调用的方法,群成员发送消息
         *
         * @param sid:房间号
         * @param userId:用户id
         * @param message:发送消息
         */
        @OnMessage
        public void onMessage(@PathParam("sid") String sid, @PathParam("userId") Integer userId, String message) {
            List<Session> sessionList = groupMemberInfoMap.get(sid);
            Set<Integer> onlineUserList = onlineUserMap.get(sid);
            // 先一个群组内的成员发送消息
            sessionList.forEach(item -> {
                try {
                    // json字符串转对象
                    MsgVO msg = JSONObject.parseObject(message, MsgVO.class);
                    msg.setCount(onlineUserList.size());
                    // json对象转字符串
                    String text = JSONObject.toJSONString(msg);
                    item.getBasicRemote().sendText(text);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
    
        /**
         * 建立连接调用的方法,群成员加入
         *
         * @param session
         * @param sid
         */
        @OnOpen
        public void onOpen(Session session, @PathParam("sid") String sid, @PathParam("userId") Integer userId) {
            List<Session> sessionList = groupMemberInfoMap.computeIfAbsent(sid, k -> new ArrayList<>());
            Set<Integer> onlineUserList = onlineUserMap.computeIfAbsent(sid, k -> new HashSet<>());
            onlineUserList.add(userId);
            sessionList.add(session);
            // 发送上线通知
            sendInfo(sid, userId, onlineUserList.size(), "上线了~");
        }
    
    
        public void sendInfo(String sid, Integer userId, Integer onlineSum, String info) {
            // 获取该连接用户信息
            User currentUser = ApplicationContextUtil.getApplicationContext().getBean(UserMapper.class).selectById(userId);
            // 发送通知
            MsgVO msg = new MsgVO();
            msg.setCount(onlineSum);
            msg.setUserId(userId);
            msg.setAvatar(currentUser.getAvatar());
            msg.setMsg(currentUser.getNickName() + info);
            // json对象转字符串
            String text = JSONObject.toJSONString(msg);
            onMessage(sid, userId, text);
        }
    
        /**
         * 关闭连接调用的方法,群成员退出
         *
         * @param session
         * @param sid
         */
        @OnClose
        public void onClose(Session session, @PathParam("sid") String sid, @PathParam("userId") Integer userId) {
            List<Session> sessionList = groupMemberInfoMap.get(sid);
            sessionList.remove(session);
            Set<Integer> onlineUserList = onlineUserMap.get(sid);
            onlineUserList.remove(userId);
            // 发送离线通知
            sendInfo(sid, userId, onlineUserList.size(), "下线了~");
        }
    
        /**
         * 传输消息错误调用的方法
         *
         * @param error
         */
        @OnError
        public void OnError(Throwable error) {
            log.info("Connection error");
        }
    }
    

    4、websocket 消息显示类

    @Data
    @ApiModel(description = "websocket消息内容")
    public class MsgVO {
    
        @ApiModelProperty(value = "用户id")
        private Integer userId;
    
        @ApiModelProperty(value = "用户名")
        private String username;
    
        @ApiModelProperty(value = "用户头像")
        private String avatar;
    
        @ApiModelProperty(value = "消息")
        private String msg;
    
        @ApiModelProperty(value = "在线人数")
        private int count;
    
    }
    

    5、前端页面

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

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

    <template>
      <div class="chat-box">
        <header>聊天室 (在线:{{count}}人)</header>
        <div class="msg-box" ref="msg-box">
          <div
            v-for="(i,index) in list"
            :key="index"
            class="msg"
            :style="i.userId == userId?'flex-direction:row-reverse':''"
          >
            <div class="user-head">
              <img :src="i.avatar" height="30" width="30" :title="i.username">
            </div>
            <div class="user-msg">
              <span :style="i.userId == userId?' float: right;':''" :class="i.userId == userId?'right':'left'">{{i.content}}</span>
            </div>
          </div>
        </div>
        <div class="input-box">
          <input type="text" ref="sendMsg" v-model="contentText" @keyup.enter="sendText()" />
          <div class="btn" :class="{['btn-active']:contentText}" @click="sendText()">发送</div>
        </div>
      </div>
    </template>
    
    <script>
      export default {
        data() {
          return {
            ws: null,
            count: 0,
            userId: this.$store.getters.id, // 当前用户ID
            username: this.$store.getters.name, // 当前用户昵称
            avatar: this.$store.getters.avatar, // 当前用户头像
            list: [], // 聊天记录的数组
            contentText: "" // input输入的值
          };
        },
        mounted() {
          this.initWebSocket();
        },
        destroyed() {
          // 离开页面时关闭websocket连接
          this.ws.onclose(undefined);
        },
        methods: {
          // 发送聊天信息
          sendText() {
            let _this = this;
            _this.$refs["sendMsg"].focus();
            if (!_this.contentText) {
              return;
            }
            let params = {
              userId: _this.userId,
              username: _this.username,
              avatar: _this.avatar,
              msg: _this.contentText,
              count: _this.count
            };
            _this.ws.send(JSON.stringify(params)); //调用WebSocket send()发送信息的方法
            _this.contentText = "";
            setTimeout(() => {
              _this.scrollBottm();
            }, 500);
          },
          // 进入页面创建websocket连接
          initWebSocket() {
            let _this = this;
            // 判断页面有没有存在websocket连接
            if (window.WebSocket) {
              var serverHot =  window.location.hostname;
              let sip = '房间号'
              // 填写本地IP地址 此处的 :9101端口号 要与后端配置的一致!
              var url = 'ws://' + serverHot + ':9101' + '/groupChat/' + sip + '/' + this.userId; // `ws://127.0.0.1/9101/groupChat/10086/聊天室`
              let ws = new WebSocket(url);
              _this.ws = ws;
              ws.onopen = function(e) {
                console.log("服务器连接成功: " + url);
              };
              ws.onclose = function(e) {
                console.log("服务器连接关闭: " + url);
              };
              ws.onerror = function() {
                console.log("服务器连接出错: " + url);
              };
              ws.onmessage = function(e) {
                //接收服务器返回的数据
                let resData = JSON.parse(e.data)
                _this.count = resData.count;
                _this.list = [
                  ..._this.list,
                  { userId: resData.userId, username: resData.username, avatar: resData.avatar, content: resData.msg }
                ];
              };
            }
          },
          // 滚动条到底部
          scrollBottm() {
              let el = this.$refs["msg-box"];
              el.scrollTop = el.scrollHeight;
          }
        }
      };
    </script>
    
    <style lang="scss" scoped>
      .chat-box {
        margin: 0 auto;
        background: #fafafa;
        position: absolute;
        height: 100%;
         100%;
        max- 700px;
      header {
        position: fixed;
         100%;
        height: 3rem;
        background: #409eff;
        max- 700px;
        display: flex;
        justify-content: center;
        align-items: center;
        font-weight: bold;
        color: white;
        font-size: 1rem;
      }
      .msg-box {
        position: absolute;
        height: calc(100% - 6.5rem);
         100%;
        margin-top: 3rem;
        overflow-y: scroll;
      .msg {
         95%;
        min-height: 2.5rem;
        margin: 1rem 0.5rem;
        position: relative;
        display: flex;
        justify-content: flex-start !important;
      .user-head {
        min- 2.5rem;
         20%;
         2.5rem;
        height: 2.5rem;
        border-radius: 50%;
        background: #f1f1f1;
        display: flex;
        justify-content: center;
        align-items: center;
      .head {
         1.2rem;
        height: 1.2rem;
      }
      // position: absolute;
      }
      .user-msg {
         80%;
      // position: absolute;
        word-break: break-all;
        position: relative;
        z-index: 5;
      span {
        display: inline-block;
        padding: 0.5rem 0.7rem;
        border-radius: 0.5rem;
        margin-top: 0.2rem;
        font-size: 0.88rem;
      }
      .left {
        background: white;
        animation: toLeft 0.5s ease both 1;
      }
      .right {
        background: #53a8ff;
        color: white;
        animation: toright 0.5s ease both 1;
      }
      @keyframes toLeft {
        0% {
          opacity: 0;
          transform: translateX(-10px);
        }
        100% {
          opacity: 1;
          transform: translateX(0px);
        }
      }
      @keyframes toright {
        0% {
          opacity: 0;
          transform: translateX(10px);
        }
        100% {
          opacity: 1;
          transform: translateX(0px);
        }
      }
      }
      }
      }
      .input-box {
        padding: 0 0.5rem;
        position: absolute;
        bottom: 0;
         100%;
        height: 3.5rem;
        background: #fafafa;
        box-shadow: 0 0 5px #ccc;
        display: flex;
        justify-content: space-between;
        align-items: center;
      input {
        height: 2.3rem;
        display: inline-block;
         100%;
        padding: 0.5rem;
        border: none;
        border-radius: 0.2rem;
        font-size: 0.88rem;
      }
      .btn {
        height: 2.3rem;
        min- 4rem;
        background: #e0e0e0;
        padding: 0.5rem;
        font-size: 0.88rem;
        color: white;
        text-align: center;
        border-radius: 0.2rem;
        margin-left: 0.5rem;
        transition: 0.5s;
      }
      .btn-active {
        background: #409eff;
      }
      }
      }
    </style>
    

    本文案例demo源码

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

  • 相关阅读:
    JS和C# 里的闭包及闭包在事件中的使用
    ***项目开发记录
    七牛云存储之应用视频上传系统开心得
    二维码及二维码接合短URL的应用
    EF批量添加,删除,修改的扩展
    ngTemplateOutlet递归的问题
    每日新知2019-06-03
    Spring boot初始
    纯前端播放本地音乐
    macbook 安装任意来源
  • 原文地址:https://www.cnblogs.com/zhengqing/p/11988541.html
Copyright © 2011-2022 走看看