赞
踩
1 输入临时名字充当账号使用
2 进入聊天窗口
3 发送消息 (复制一个页面,输入其他名字,方便展示效果)
4 其他窗口效果
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>2.7.12</version> </dependency> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.23</version> </dependency>
package com.dark.wsdemo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * WebSocket配置类。开启WebSocket的支持 */ @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
package com.dark.wsdemo.service; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.dark.wsdemo.vo.MessageVo; 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.Date; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; /** * WebSocket的操作类 */ @Component @Slf4j @ServerEndpoint("/websocket/{name}") public class WebSocketServer { /** * 静态变量,用来记录当前在线连接数,线程安全的类。 */ private static final AtomicInteger onlineSessionClientCount = new AtomicInteger(0); /** * 存放所有在线的客户端 */ private static final Map<String, Session> onlineSessionClientMap = new ConcurrentHashMap<>(); /** * 连接 name 和连接会话 */ private String name; @OnOpen public void onOpen(@PathParam("name") String name, Session session) { /** * session.getId():当前session会话会自动生成一个id,从0开始累加的。 */ Session beforeSession = onlineSessionClientMap.get(name); if (beforeSession != null) { //在线数减1 onlineSessionClientCount.decrementAndGet(); log.info("连接已存在,关闭之前的连接 ==> session_id = {}, name = {}。", beforeSession.getId(), name); //通知之前其他地方连接被挤掉 sendToOne(name, "您的账号在其他地方登录,您被迫下线。"); // 从 Map中移除 onlineSessionClientMap.remove(name); //关闭之前的连接 try { beforeSession.close(); } catch (Exception e) { log.error("关闭之前的连接异常,异常信息为:{}", e.getMessage()); } } log.info("连接建立中 ==> session_id = {}, name = {}", session.getId(), name); onlineSessionClientMap.put(name, session); //在线数加1 onlineSessionClientCount.incrementAndGet(); this.name = name; sendToOne(name, "连接成功"); log.info("连接建立成功,当前在线数为:{} ==> 开始监听新连接:session_id = {}, name = {}。", onlineSessionClientCount, session.getId(), name); } @OnClose public void onClose(@PathParam("name") String name, Session session) { if (name == null || name.equals("")) { name = this.name; } // 从 Map中移除 onlineSessionClientMap.remove(name); //在线数减1 onlineSessionClientCount.decrementAndGet(); log.info("连接关闭成功,当前在线数为:{} ==> 关闭该连接信息:session_id = {}, name = {}。", onlineSessionClientCount, session.getId(), name); } @OnMessage public void onMessage(String message, Session session) { JSONObject jsonObject = JSON.parseObject(message); String toname = jsonObject.getString("name"); String msg = jsonObject.getString("message"); log.info("服务端收到客户端消息 ==> fromname = {}, toname = {}, message = {}", name, toname, message); /** * 模拟约定:如果未指定name信息,则群发,否则就单独发送 */ if (toname == null || toname == "" || "".equalsIgnoreCase(toname)) { sendToAll(msg); } else { sendToOne(toname, msg); } } /** * 发生错误调用的方法 * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("WebSocket发生错误,错误信息为:" + error.getMessage()); error.printStackTrace(); } /** * 群发消息 * * @param message 消息 */ private void sendToAll(String message) { // 遍历在线map集合 onlineSessionClientMap.forEach((onlineName, toSession) -> { // 排除掉自己 if (!name.equalsIgnoreCase(onlineName)) { log.info("服务端给客户端群发消息 ==> name = {}, toname = {}, message = {}", name, onlineName, message); MessageVo messageVo = new MessageVo(); messageVo.setFrom(name); messageVo.setDate(new Date()); messageVo.setMessage(message); toSession.getAsyncRemote().sendText(JSON.toJSONString(messageVo)); } }); } /** * 指定发送消息 * * @param toName * @param message */ private void sendToOne(String toName, String message) { // 通过name查询map中是否存在 Session toSession = onlineSessionClientMap.get(toName); if (toSession == null) { log.error("服务端给客户端发送消息 ==> toname = {} 不存在, message = {}", toName, message); return; } // 异步发送 log.info("服务端给客户端发送消息 ==> toname = {}, message = {}", toName, message); MessageVo messageVo = new MessageVo(); messageVo.setFrom(name); messageVo.setDate(new Date()); messageVo.setMessage(message); toSession.getAsyncRemote().sendText(JSON.toJSONString(messageVo)); } }
package com.dark.wsdemo.vo; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.util.Date; @Data public class MessageVo { private String from; //json时候格式化为时间格式 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date date; private String message; }
<template> <div id="app"> <!-- Modal Dialog --> <div class="modal" v-if="!username"> <div class="modal-content"> <h2>请输入你的名字</h2> <input type="text" v-model="inputUsername" /> <button @click="setUsername">确定</button> </div> </div> <!-- Chat Box --> <div class="chat-box" v-if="username"> <div class="chat-history"> <div v-for="msg in messages" :key="msg.id" :class="[msg.type, 'message']"> <div class="info"> <span class="from">{{ msg.from }}</span> <span class="date">{{ msg.date }}</span> </div> <div class="bubble"> {{ msg.message }} </div> </div> </div> <div class="chat-input"> <input type="text" v-model="inputMessage" @keyup.enter="sendMessage" placeholder="请输入消息..."/> <button @click="sendMessage">发送</button> </div> </div> </div> </template> <script> export default { data() { return { inputMessage: '', inputUsername: '', messages: [], username: '', ws: null, }; }, methods: { setUsername() { if (this.inputUsername.trim() === '') return; this.username = this.inputUsername.trim(); this.ws = new WebSocket(`ws://localhost:8081/websocket/${this.username}`); this.ws.addEventListener('message', (event) => { const data = JSON.parse(event.data); this.messages.push({ ...data, type: 'left', id: this.messages.length }); }); }, sendMessage() { if (this.inputMessage.trim() === '') return; const message = { from: this.username, date: new Date().toLocaleString(), message: this.inputMessage.trim(), }; this.ws.send(JSON.stringify(message)); this.messages.push({ ...message, type: 'right', id: this.messages.length }); this.inputMessage = ''; }, }, }; </script> <style> /* Modal Styles */ .modal { display: flex; justify-content: center; align-items: center; position: fixed; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9999; } .modal-content { background-color: #fff; padding: 20px; width: 300px; text-align: center; border-radius: 10px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } /* Chat Box Styles */ #app { background-color: #f2f2f2; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; font-family: Arial, sans-serif; } .chat-box { box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); width: 300px; height: 400px; border-radius: 8px; overflow: hidden; display: flex; flex-direction: column; } .chat-history { flex: 1; overflow-y: auto; padding: 10px; background-color: #fff; } .message { padding: 5px 0; } .info { font-size: 12px; color: gray; margin-bottom: 4px; } .left .bubble { background-color: #e6e6e6; border-radius: 15px; padding: 12px; display: inline-block; } .right .bubble { background-color: #007bff; color: white; border-radius: 15px; padding: 12px; display: inline-block; margin-left: auto; } .chat-input { display: flex; padding: 10px; background-color: #f7f7f7; border-top: 1px solid #ccc; } input { flex: 1; padding: 8px; border: 1px solid #ccc; border-radius: 4px; margin-right: 10px; } button { padding: 10px 20px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } button:hover { background-color: #0056b3; } </style>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。