部分代码
首先创建springboot项目并引入依赖:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
服务端核心代码
@Component
@ServerEndpoint("/chat/lobby")
public class LobbyChat {@OnOpenpublic void onOpen(Session session) throws IOException {/** @description: TODO 链接建立时调用该方法* @params: [session] //连接发起者的会话对象* @return: void* @author: QGQ* @dateTime: 2022/3/1 18:59*/
// Info.increaseOnlineCount();//从querystring中取出用户输入的用户名并解码然后和session对象一起保存到user对象中User user = new User(URLDecoder.decode(session.getQueryString().split("=")[1],"UTF-8"),session);Info.addUser(user);//向所有用户推送最新的在线者信息for (User u: Info.getUserList()) {send(u.getSession(),MsgHelper.generateUserInfo(Info.getUserList()));}System.out.println("一名用户上线,"+user.toString()+"当前在线人数为:"+ Info.getOnlineCount());}@OnClosepublic void onClose(Session session) throws IOException {/** @description: TODO 某用户下线,更新在线人数和在线者列表* @params: [session]* @return: void* @author: QGQ* @dateTime: 2022/3/1 19:01*/System.out.println("用户"+Info.getUser(session)+"已下线,当前在线人数:"+(Info.getOnlineCount()-1));Info.removeUser(session);//向所有用户推送最新的在线者信息for (User u: Info.getUserList()) {send(u.getSession(),MsgHelper.generateUserInfo(Info.getUserList()));}}@OnErrorpublic void onError(Session session, Throwable error){System.out.println("发生错误!");error.printStackTrace();}@OnMessagepublic void onMessage(String msg,Session session) throws IOException {/** @description: TODO 根据收到消息的类型将收到的消息发给所有在线者或个人* @params: [session]* @return: void* @author: QGQ* @dateTime: 2022/3/1 19:51*///使用jackson的API将前端发来的封装好的消息对象转成mapObjectMapper objectMapper = new ObjectMapper();Map<String,String> received = objectMapper.readValue(msg, new TypeReference<Map<String,String>>(){});
// System.out.println(received);String msgObj = null; //用于封装规定格式的消息对象User sender = Info.getUser(session); //获取发送者对象if (received.get("type").equals("lobby")){//该type值表示这是发往大厅的消息System.out.println("用户"+Info.getUser(session)+"发出的消息:"+received.get("content"));msgObj = MsgHelper.generateUserMsg(sender.getUsername(),sender.getSession().getId(),"lobby",received.get("content"));for (User u: Info.getUserList()) {send(u.getSession(),msgObj);}}if (received.get("type").equals("friend")){//该type值表示这条消息是发给某人的System.out.println(received);msgObj = MsgHelper.generateUserMsg(sender.getUsername(),sender.getSession().getId(),"friend",received.get("content"));
// send(Info.getUser(session).getSession(),msgObj);send(Info.getUser(received.get("sessionID")).getSession(),msgObj);}}public static void send(Session receiver,String msg) throws IOException {/** @description: TODO 向用户发送消息* @params: [receiver, msg] //前者为接受者,后者为发送的内容* @return: void* @author: QGQ* @dateTime: 2022/3/3 20:20*/receiver.getBasicRemote().sendText(msg);}}
前端核心代码:
let app = Vue.createApp({data(){return {username:"", //当前用户的昵称lobby_input:"hello", //大厅输入框的内容friend_input:"", //私聊输入框的内容msg_records_f: [], //私聊框消息记录,格式为{type:"message",sender:xxx,senderID:xxx,to:xxx,time:xxx,content:xxx}msg_records_l:[], //大厅消息记录,格式同上received_msg_f: "", //私聊框收到的最新消息online_count: 0, //在线人数onlineUsers:null, //在线者列表pri_chat_obj: {username:"未开始私聊",sessionId:-1}, //当前私聊对象,格式为{username:xxx,sessionID:xxx}socket: null, //websocket对象btnChatMovedActiveIndex: -1, //决定CSS类.btn-chat-moved是否生效}},methods:{sendMsgToF(){// 向私聊对象发送消息if(this.pri_chat_obj.sessionId != -1){if(this.friend_input!=""){console.log(this.msgObjF);this.socket?.send(this.msgObjF);let d= new Date();let timeStr = d.getHours() + "时" + d.getMinutes() +"分" + d.getSeconds() + "秒";this.msg_records_f.push({sender:this.username,time:timeStr,content:this.friend_input});this.friend_input = "";}}else {alert("请先选择私聊对象");}},sendMsgToL(){//发送消息到大厅,没输入任何文字时不发送if (this.lobby_input !== ""){this.socket?.send(this.msgObjL);this.lobby_input = "";}},chatWith(user){//切换私聊对象为userif(user!==this.pri_chat_obj){this.pri_chat_obj = user;this.msg_records_f = [];}},getUserBySessionID(id){//通过sessionID获取发送者对象for (let i = 0; i < this.onlineUsers.length; i++) {if (this.onlineUsers[i].sessionId === id){return this.onlineUsers[i];}}}},computed:{msgObjL(){//要发送给后端的大厅消息对象,格式为{type:lobby,content:xxx}return JSON.stringify({type:"lobby",content:this.lobby_input});},msgObjF(){//要发送给后端的私聊消息对象,格式为:{type:friend,sessionID:xxx,content:xxx}return JSON.stringify({type:"friend",sessionID:this.pri_chat_obj.sessionId,content:this.friend_input});}},watch:{received_msg_f(val){console.log(val)}},mounted(){let name = window.prompt("请输入你在本次聊天中要使用的昵称");while (name === ""||name == null){name = window.prompt("请输入一个昵称!");}this.username = name;let webSocket = new WebSocket("ws://127.0.0.1:8080/chat/lobby?username="+this.username);webSocket.onopen = function (){console.log("链接建立成功");app.$data.socket = webSocket;// app.$data.online_count++;}webSocket.onclose = function (){console.log("连接已关闭");}webSocket.onerror = function (err){alert("连接服务器失败!");console.log(err);}//收到消息时调用webSocket.onmessage = function (event){console.log(event.data)let msgObj = JSON.parse(event.data); //获取后端封装的消息对象// app.$data.received_msg_f = msgObj.content; //修改最新收到的消息console.log(msgObj)if(msgObj.type==="message"){//该type值表示这是某个用户发送的消息if(msgObj.to==="lobby"){app.$data.msg_records_l.push(msgObj); //将收到的消息加入消息记录}if (msgObj.to==="friend"){app.$data.msg_records_f.push(msgObj);app.$data.pri_chat_obj = app.getUserBySessionID(msgObj.senderID);}}if (msgObj.type=="info"){//该type值表示这是服务器推送的在线用户信息// console.log(msgObj);app.$data.online_count = msgObj.onlineCount;app.$data.onlineUsers = msgObj.userList;}}//当窗口被关闭时,先关闭ws连接window.onbeforeunload = function (){webSocket.close();app.$data.socket = null;app.$data.online_count--;}}
}).mount("#app");
完整代码可在Gitee查看.
演示
先输入要使用的昵称
同样方法打开三个标签页
在大厅大消息所有人都会收到
鼠标在在线者列表上悬浮会出现chat按钮,点击可开始私聊
向私聊对象发送消息,只有他一个人会收到,Tony收到消息
Thor没有收到
Peter收到Tony的回复