效果圖
1 輸入臨時(shí)名字充當(dāng)賬號使用
2 進(jìn)入聊天窗口
3 發(fā)送消息 (復(fù)制一個(gè)頁面,輸入其他名字,方便展示效果)
4 其他窗口效果
代碼實(shí)現(xiàn)
后端SpringBoot項(xiàng)目,自行創(chuàng)建
pom依賴
<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>
WebSocketConfig.java
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();
}
}
WebSocketServer.java
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 {
/**
* 靜態(tài)變量,用來記錄當(dāng)前在線連接數(shù),線程安全的類。
*/
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():當(dāng)前session會話會自動生成一個(gè)id,從0開始累加的。
*/
Session beforeSession = onlineSessionClientMap.get(name);
if (beforeSession != null) {
//在線數(shù)減1
onlineSessionClientCount.decrementAndGet();
log.info("連接已存在,關(guān)閉之前的連接 ==> session_id = {}, name = {}。", beforeSession.getId(), name);
//通知之前其他地方連接被擠掉
sendToOne(name, "您的賬號在其他地方登錄,您被迫下線。");
// 從 Map中移除
onlineSessionClientMap.remove(name);
//關(guān)閉之前的連接
try {
beforeSession.close();
} catch (Exception e) {
log.error("關(guān)閉之前的連接異常,異常信息為:{}", e.getMessage());
}
}
log.info("連接建立中 ==> session_id = {}, name = {}", session.getId(), name);
onlineSessionClientMap.put(name, session);
//在線數(shù)加1
onlineSessionClientCount.incrementAndGet();
this.name = name;
sendToOne(name, "連接成功");
log.info("連接建立成功,當(dāng)前在線數(shù)為:{} ==> 開始監(jiān)聽新連接: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);
//在線數(shù)減1
onlineSessionClientCount.decrementAndGet();
log.info("連接關(guān)閉成功,當(dāng)前在線數(shù)為:{} ==> 關(guān)閉該連接信息: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("服務(wù)端收到客戶端消息 ==> fromname = {}, toname = {}, message = {}", name, toname, message);
/**
* 模擬約定:如果未指定name信息,則群發(fā),否則就單獨(dú)發(fā)送
*/
if (toname == null || toname == "" || "".equalsIgnoreCase(toname)) {
sendToAll(msg);
} else {
sendToOne(toname, msg);
}
}
/**
* 發(fā)生錯(cuò)誤調(diào)用的方法
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("WebSocket發(fā)生錯(cuò)誤,錯(cuò)誤信息為:" + error.getMessage());
error.printStackTrace();
}
/**
* 群發(fā)消息
*
* @param message 消息
*/
private void sendToAll(String message) {
// 遍歷在線map集合
onlineSessionClientMap.forEach((onlineName, toSession) -> {
// 排除掉自己
if (!name.equalsIgnoreCase(onlineName)) {
log.info("服務(wù)端給客戶端群發(fā)消息 ==> 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));
}
});
}
/**
* 指定發(fā)送消息
*
* @param toName
* @param message
*/
private void sendToOne(String toName, String message) {
// 通過name查詢map中是否存在
Session toSession = onlineSessionClientMap.get(toName);
if (toSession == null) {
log.error("服務(wù)端給客戶端發(fā)送消息 ==> toname = {} 不存在, message = {}", toName, message);
return;
}
// 異步發(fā)送
log.info("服務(wù)端給客戶端發(fā)送消息 ==> toname = {}, message = {}", toName, message);
MessageVo messageVo = new MessageVo();
messageVo.setFrom(name);
messageVo.setDate(new Date());
messageVo.setMessage(message);
toSession.getAsyncRemote().sendText(JSON.toJSONString(messageVo));
}
}
MessageVo.java文章來源:http://www.zghlxwxcb.cn/news/detail-707926.html
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時(shí)候格式化為時(shí)間格式
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date date;
private String message;
}
Vue代碼實(shí)現(xiàn)
App.vue文章來源地址http://www.zghlxwxcb.cn/news/detail-707926.html
<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">發(fā)送</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>
到了這里,關(guān)于SpringBoot+Vue 整合websocket實(shí)現(xiàn)簡單聊天窗口的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!