WebSocket入门案例——聊天室

WebSocket入门案例——聊天室

1.引入依赖

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

2.添加配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

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

3.编写WebSocket操作类

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@ServerEndpoint("/chat/{username}") 
@Component
@Slf4j
public class WebsocketServer {
    public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();//存储连接对象

    @OnOpen //开启ws连接
    public void onOpen(Session session, @PathParam("username") String username) {
        sessionMap.put(username, session);
        log.info("用户{}登录,当前在线人数:{}", username,sessionMap.size());
        JSONObject result = new JSONObject();
        JSONArray array = new JSONArray();
        result.set("users",array);
        for (String key : sessionMap.keySet()) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.set("username",key);
            array.add(jsonObject);
        }
        sendAllMessage(JSONUtil.toJsonStr(result));
    }



    @OnClose //关闭连接
    public void onclose(Session session, @PathParam("username") String username){
        sessionMap.remove(username);
        log.info("用户{}退出,当前在线人数:{}", username,sessionMap.size());
    }

    @OnMessage //消息的转发
    public void onMessage(String message, Session session, @PathParam("username") String username){
        log.info("用户{}发送消息:{}", username,message);
        JSONObject obj = JSONUtil.parseObj(message);
        String to = obj.getStr("to");
        String text = obj.getStr("text");
        Session toSession = sessionMap.get(to);
        if (toSession != null) {
            JSONObject result = new JSONObject();
            result.set("from",username);
            result.set("text",text);
            this.sendMessage(result.toString(),toSession);
        }else{
            log.error("发送失败,未找到用户username={}",to);
        }
    }

    @OnError
    public void onError(Session session, Throwable throwable){
        log.error("发生错误");
        throwable.printStackTrace();
    }

    private void sendMessage(String message,Session tosession) {
        try{
            log.info("服务端给客户端【{}】发送消息:{}",tosession.getId(),message);
            tosession.getBasicRemote().sendText(message);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void sendAllMessage(String jsonStr) {
        sessionMap.forEach((sessionId, session) -> {
            try {
                log.info("服务端给客户端【{}】发送消息:{}",sessionId,jsonStr);
                session.getBasicRemote().sendText(jsonStr);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}

4.前端页面(Vue)

<template>
  <div style="padding: 10px; margin-bottom: 50px">
    <el-row>
      <el-col :span="8">
        <el-card style="width: 100%; min-height: 300px; color: #333">
          <div style="padding-bottom: 10px; border-bottom: 1px solid #ccc">在线用户<span style="font-size: 12px">(点击聊天气泡开始聊天)</span></div>
          <div v-for="user in users" :key="user.username" style="padding: 10px 0">
            <div @click="chatUser = user.username">
              <span>{{ user.username }}</span>
              <i
                class="el-icon-chat-dot-round"
                style="margin-left: 10px; font-size: 16px; cursor: pointer"
                @click="chatUser = user.username"
              />
              <span v-if="user.username === chatUser" style="font-size: 12px;color: limegreen; margin-left: 5px">chatting...</span>
            </div>
          </div>
        </el-card>
      </el-col>
      <el-col :span="16">
        <div
          style="width: 800px; margin: 0 auto; background-color: white;
                    border-radius: 5px; box-shadow: 0 0 10px #ccc"
        >
          <div style="text-align: center; line-height: 50px;">
            Web聊天室({{ chatUser }})
          </div>
          <div style="height: 350px; overflow:auto; border-top: 1px solid #ccc">
            <ChatBubble
              v-for="(message, index) in messages"
              :key="index"
              :is-remote-user="message.user !== user.username"
              :user-avatar="user.avatarPath"
              :text="message.text"
            />
          </div>
          <div style="height: 200px">
            <textarea
              v-model="text"
              style="height: 160px; width: 100%; padding: 20px; border: none; border-top: 1px solid #ccc;
             border-bottom: 1px solid #ccc; outline: none"
            />
            <div style="text-align: right; padding-right: 10px">
              <el-button type="primary" size="mini" @click="send">发送</el-button>
            </div>
          </div>
        </div>
      </el-col>
    </el-row>
  </div>
</template>
<script>
import ChatBubble from '../compoments/ChatBubble.vue'
import { mapGetters } from 'vuex'
let socket
export default {
  name: 'Im',
  components: { ChatBubble },
  data() {
    return {
      isCollapse: false,
      users: [],
      chatUser: '',
      text: '',
      messages: [],
      content: ''
    }
  },
  computed: {
    ...mapGetters([
      'user',
      'baseApi'
    ])
  },
  created() {
    this.init()
  },
  methods: {
    send() {
      if (!this.chatUser) {
        this.$message({ type: 'warning', message: '请选择聊天对象' })
        return
      }
      if (!this.text) {
        this.$message({ type: 'warning', message: '请输入内容' })
      } else {
        if (typeof (WebSocket) === 'undefined') {
          console.log('您的浏览器不支持WebSocket')
        } else {
          console.log('您的浏览器支持WebSocket')
          // 组装待发送的消息 json
          // {"from": "zhang", "to": "admin", "text": "聊天文本"}
          const message = { from: this.user.username, to: this.chatUser, text: this.text }
          socket.send(JSON.stringify(message)) // 将组装好的json发送给服务端,由服务端进行转发
          this.messages.push({ user: this.user.username, text: this.text })
          this.text = ''
        }
      }
    },
    init() {
      console.log(this.user)
      const username = this.user.username
      const _this = this
      if (typeof (WebSocket) === 'undefined') {
        console.log('您的浏览器不支持WebSocket')
      } else {
        console.log('您的浏览器支持WebSocket')
        const socketUrl = 'ws://localhost:8000/chat/' + username
        if (socket != null) {
          socket.close()
          socket = null
        }
        // 开启一个websocket服务
        socket = new WebSocket(socketUrl)
        // 打开事件
        socket.onopen = function() {
          console.log('websocket已打开')
        }
        //  浏览器端收消息,获得从服务端发送过来的文本消息
        socket.onmessage = function(msg) {
          console.log('收到数据====' + msg.data)
          const data = JSON.parse(msg.data) // 对收到的json数据进行解析, 类似这样的: {"users": [{"username": "zhang"},{ "username": "admin"}]}
          // console.log(data)
          if (data.users) { // 获取在线人员信息
            console.log('在线用户列表====' + data.users)
            _this.users = data.users.filter(user => user.username !== username) // 获取当前连接的所有用户信息,并且排除自身,自己不会出现在自己的聊天列表里
          } else {
            // 如果服务器端发送过来的json数据 不包含 users 这个key,那么发送过来的就是聊天文本json数据
            //  // {"from": "zhang", "text": "hello"}
            if (data.from === _this.chatUser) {
              _this.messages.push(data)
              // 构建消息内容
              // _this.createContent(data.from, null, data.text)
            }
          }
        }
        // 关闭事件
        socket.onclose = function() {
          console.log('websocket已关闭')
        }
        // 发生了错误事件
        socket.onerror = function() {
          console.log('websocket发生了错误')
        }
      }
    }
  }
}
</script>
<style>
.tip {
  color: white;
  text-align: center;
  border-radius: 10px;
  font-family: sans-serif;
  padding: 10px;
  width:auto;
  display:inline-block !important;
  display:inline;
}
.right {
  background-color: deepskyblue;
}
.left {
  background-color: forestgreen;
}
</style>

页面中使用的气泡组件

<template>
  <div class="el-row" :class="bubbleClass" style="padding: 5px 0">
    <div class="el-col el-col-2" v-if="isRemoteUser" style="text-align: right">
      <span class="el-avatar el-avatar--circle" style="height: 40px; width: 40px; line-height: 40px;">
        <img src="@/assets/images/avatar.png" style="object-fit: cover;">
      </span>
    </div>
    <div class="el-col el-col-22" :style="textStyle">
      <div class="tip" :class="bubbleColor">{{ text }}</div>
    </div>
    <div class="el-col el-col-2" v-if="!isRemoteUser">
      <span class="el-avatar el-avatar--circle" style="height: 40px; width: 40px; line-height: 40px;">
        <img src="@/assets/images/avatar.png" style="object-fit: cover;">
      </span>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    isRemoteUser: {
      type: Boolean,
      required: true
    },
    userAvatar: {
      type: String,
      required: true
    },
    text: {
      type: String,
      required: true
    }
  },
  computed: {
    bubbleClass() {
      return this.isRemoteUser ? 'remote-user' : 'current-user'
    },
    textStyle() {
      return this.isRemoteUser ? 'text-align: left; padding-left: 10px' : 'text-align: right; padding-right: 10px'
    },
    bubbleColor() {
      return this.isRemoteUser ? 'right' : 'left'
    }
  },
  mounted() {
    // console.log(this.isRemoteUser)
    console.log(this.userAvatar)
    // console.log(this.text)
  }
}
</script>

<style scoped>
.tip {
  color: white;
  text-align: center;
  border-radius: 10px;
  font-family: sans-serif;
  padding: 10px;
  width:auto;
  display:inline-block !important;
  display:inline;
}
.right {
  background-color: deepskyblue;
}
.left {
  background-color: forestgreen;
}
</style>
  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值