項(xiàng)目流程圖
1. 前端搭建:
? ? ? ? 前端用Vue+Element-Plus 來搭建,由登錄頁面和聊天頁面組成
1.1 登錄頁面
? ? ? ? 由一個昵稱輸入框組成,用戶輸入自己的昵稱若昵稱和別的用戶不重復(fù),則可進(jìn)入聊天室,否則提示錯誤并請重新輸入。
<template>
<div class="name">
<el-form
class="name-form"
ref="nameFormRef"
:model="nameForm"
:rules="nameRules"
@submit.prevent
>
<el-form-item>
<h1>EZ-Chat</h1>
</el-form-item>
<el-form-item class="name-input" prop="nickname">
<el-input
class="nickname"
v-model="nameForm.nickname"
@keydown.enter="submitButton(nameFormRef)"
placeholder="輸入一個昵稱"
></el-input>
<el-button
class="submit"
type="primary"
@click="submitButton(nameFormRef)"
>進(jìn)入</el-button
>
</el-form-item>
</el-form>
</div>
</template>
????????這段代碼是一個Vue.js組件的模板部分,用于實(shí)現(xiàn)聊天應(yīng)用的用戶昵稱輸入和登錄功能
<script lang="ts" setup>
import { reactive, ref } from "vue";
import type { FormInstance, FormRules } from "element-plus";
import { ElMessage } from "element-plus";
import router from "@/router";
import axios from "axios";
// 昵稱表單
const nameFormRef = ref<FormInstance>();
// 昵稱表單
const nameForm = reactive({
nickname: "",
});
// 表單校驗(yàn)
const nameRules = reactive<FormRules>({
nickname: [{ required: true, message: "請輸入一個昵稱", trigger: "blur" }],
});
// 登錄操作
const submitButton = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate((valid) => {
if (valid) {
axios
.get("http://localhost:8888/list/" + nameForm.nickname)
.then((resp) => {
const data = resp.data;
// 判斷用戶名是否存在
if (!data.isExist) {
sessionStorage.setItem("name", nameForm.nickname);
router.push("/chat");
} else {
ElMessage({
message: "該用戶名已存在,請更換",
grouping: true,
type: "error",
});
}
});
} else {
ElMessage({
message: "請輸入一個昵稱",
grouping: true,
type: "error",
});
return false;
}
});
};
</script>
<style lang="scss">
@import "../assets/css/name";
</style>
????????這段代碼使用了Element Plus庫中的el-form
、el-input
和el-button
組件來構(gòu)建表單界面。其中,el-form
用于創(chuàng)建表單容器,el-input
用于輸入昵稱,el-button
用于提交表單。
????????在表單中,用戶需要輸入一個昵稱,并點(diǎn)擊"進(jìn)入"按鈕進(jìn)行登錄操作。當(dāng)用戶按下回車鍵時,會觸發(fā)submitButton
函數(shù),該函數(shù)會驗(yàn)證表單是否通過校驗(yàn)。如果校驗(yàn)通過,則發(fā)送一個HTTP請求到服務(wù)器,獲取與該昵稱對應(yīng)的用戶信息。
上圖是登錄界面的展示。
1.2聊天頁面
<template>
<div class="chat">
<div class="list-pane">
<div class="user-pane">
<div class="user-count">
<h2>當(dāng)前在線人數(shù):{{ userCount }}</h2>
</div>
<div class="user-list">
<div class="user" v-for="user in userList" :key="user">
<el-image
class="user-img"
:src="require('@/assets/images/user.png')"
></el-image>
<p class="username">{{ user }}</p>
</div>
</div>
</div>
</div>
<div class="chat-pane">
<div class="chat-header">
<h2>EZ-Chat - {{ nickname }}</h2>
</div>
<div class="chat-message" ref="chatHistory">
<div class="user-message" v-for="message in messages" :key="message">
<div class="img">
<el-image
class="user-img"
:src="require('@/assets/images/user.png')"
></el-image>
</div>
<div class="message">
<div class="username">
{{ message.name }} <span class="time">{{ message.time }}</span>
</div>
<div class="text user-text" v-if="nickname === message.name">
{{ message.msg }}
</div>
<div class="text" v-if="nickname !== message.name">
{{ message.msg }}
</div>
</div>
</div>
</div>
<div class="chat-textarea">
<el-input
v-model="text"
class="user-textarea"
type="textarea"
resize="none"
@keydown.enter="sendButton"
></el-input>
<el-button type="primary" class="send-button" @click="sendButton"
>發(fā)送</el-button
>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onActivated } from "vue";
import router from "@/router";
let nickname = ref();
let socket: WebSocket;
onActivated(() => {
nickname.value = sessionStorage.getItem("name");
// 查詢是否設(shè)置了昵稱
if (nickname.value == null) {
router.push("/");
return;
}
// 查詢?yōu)g覽器是否支持 WebSocket
if (typeof WebSocket == "undefined") {
alert("您的瀏覽器不支持 WebSocket");
router.push("/");
return;
}
// 開啟 WebSocket 服務(wù)
let socketHost = "localhost";
let socketPort = "8888";
let socketUrl =
"ws://" + socketHost + ":" + socketPort + "/socket/" + nickname.value;
socket = new WebSocket(socketUrl);
// 連接服務(wù)器
socket.onopen = () => {
console.log("已連接至服務(wù)器");
};
// 瀏覽器接收服務(wù)端發(fā)送的消息
socket.onmessage = (msg) => {
let data = JSON.parse(msg.data);
if (data.userlist) {
// 接收用戶列表消息
userList.value = data.userlist;
userCount.value = data.userlist.length;
} else {
// 接收消息
messages.value.push(data);
// 獲取節(jié)點(diǎn)
let chatHistory = document.getElementsByClassName("chat-message")[0];
if (chatHistory.scrollHeight >= chatHistory.clientHeight) {
setTimeout(function () {
//設(shè)置滾動條到最底部
chatHistory.scrollTop = chatHistory.scrollHeight;
}, 0);
}
}
};
// 關(guān)閉服務(wù)
socket.onclose = () => {
console.log("WebSocket 服務(wù)已關(guān)閉");
};
// 錯誤事件
socket.onerror = () => {
console.log("WebSocket 服務(wù)發(fā)生錯誤");
};
});
// 日期轉(zhuǎn)換
const formatTime = (date: Date) => {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
return (
[year, month, day].map(formatNumber).join("-") +
" " +
[hour, minute, second].map(formatNumber).join(":")
);
};
const formatNumber = (n: number) => {
const s = n.toString();
return s[1] ? s : "0" + s;
};
// 用戶數(shù)量
let userCount = ref(0);
// 用戶列表
let userList = ref([]);
// 信息框
let text = ref("");
// 信息列表
let messages = ref([]);
// 信息
let message = {
name: "",
time: "",
msg: "",
};
// 發(fā)送信息
const sendButton = (event: { preventDefault: () => void }) => {
event.preventDefault();
if (text.value != null && text.value !== "" && nickname.value != null) {
message.name = nickname.value;
message.time = formatTime(new Date());
message.msg = text.value;
socket.send(JSON.stringify(message));
message.msg = "";
text.value = "";
}
};
</script>
<style lang="scss">
@import "../assets/css/chat";
</style>
這段代碼使用了Vue的響應(yīng)式數(shù)據(jù)綁定和生命周期鉤子函數(shù)來實(shí)現(xiàn)實(shí)時更新聊天界面的功能。
在模板部分,通過使用Vue的指令和組件來構(gòu)建聊天界面的各個部分。其中包括用戶列表、聊天消息、輸入框和發(fā)送按鈕等元素。
在腳本部分,首先導(dǎo)入了Vue的ref和onActivated函數(shù),以及WebSocket庫。然后定義了一些變量和函數(shù),用于處理用戶的昵稱、連接WebSocket服務(wù)、接收服務(wù)器發(fā)送的消息、格式化時間等操作。
當(dāng)頁面激活時,會執(zhí)行onActivated函數(shù)。在這個函數(shù)中,首先獲取用戶的昵稱,并檢查瀏覽器是否支持WebSocket。如果不支持或發(fā)生錯誤,則跳轉(zhuǎn)到首頁。
接下來,根據(jù)昵稱構(gòu)造WebSocket服務(wù)的URL,并創(chuàng)建一個新的WebSocket實(shí)例。然后設(shè)置一些事件監(jiān)聽器,包括連接成功、接收消息、關(guān)閉連接和發(fā)生錯誤等。
在接收消息的事件監(jiān)聽器中,解析服務(wù)器發(fā)送的數(shù)據(jù),并根據(jù)數(shù)據(jù)類型進(jìn)行相應(yīng)的處理。如果是用戶列表消息,則更新用戶列表和用戶數(shù)量;如果是普通消息,則將消息添加到信息列表中,并滾動到聊天歷史記錄的底部。
最后,定義了一個sendButton函數(shù),用于發(fā)送用戶輸入的信息。這個函數(shù)會在按下回車鍵時觸發(fā),并檢查輸入框和昵稱是否為空。如果不為空,則創(chuàng)建一個新的消息對象,并通過WebSocket服務(wù)發(fā)送給服務(wù)器。
整個聊天應(yīng)用的樣式部分使用了SCSS語言編寫,并引入了一個外部的CSS文件??梢愿鶕?jù)需要進(jìn)一步自定義樣式。
上圖是聊天室頁面的展示。
2. 后端部分
2.1環(huán)境配置
首先在idea中創(chuàng)建maven工程,接著導(dǎo)入依賴,pom.xml文件內(nèi)容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.12</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.chat</groupId>
<artifactId>chat-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>chat-server</name>
<description>chat-server</description>
<packaging>war</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 移除嵌入式tomcat插件 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加tomcat支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<!--<scope>provided</scope>-->
</dependency>
<!-- WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- FastJson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
<!-- FastJson依賴 -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<!-- maven 打包時跳過測試 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.2 后端組件
?創(chuàng)建WebSocket的配置類
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET","HEAD","POST","PUT","DELETE","OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}
WebSocket服務(wù)器端實(shí)現(xiàn)
import com.alibaba.fastjson.JSON;
import com.chat.entity.Message;
import com.chat.entity.UserList;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* WebSocket 服務(wù)
*/
@Component
@ServerEndpoint("/socket/{username}")
public class WebSocketServer {
/**
* 存儲對象 map
*/
public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();
/***
* WebSocket 建立連接事件
* 1.把登錄的用戶存到 sessionMap 中
* 2.發(fā)送給所有人當(dāng)前登錄人員信息
*/
@OnOpen
public void onOpen(Session session, @PathParam("username") String username) {
// 搜索名稱是否存在
boolean isExist = sessionMap.containsKey(username);
if (!isExist) {
System.out.println(username + "加入了聊天室");
sessionMap.put(username, session);
sendAllMessage(setUserList());
showUserList();
}
}
/**
* WebSocket 關(guān)閉連接事件
* 1.把登出的用戶從 sessionMap 中剃除
* 2.發(fā)送給所有人當(dāng)前登錄人員信息
*/
@OnClose
public void onClose(@PathParam("username") String username) {
if (username != null) {
System.out.println(username + "退出了聊天室");
sessionMap.remove(username);
sendAllMessage(setUserList());
showUserList();
}
}
/**
* WebSocket 接受信息事件
* 接收處理客戶端發(fā)來的數(shù)據(jù)
* @param message 信息
*/
@OnMessage
public void onMessage(String message) {
Message msg = JSON.parseObject(message, Message.class);
sendAllMessage(JSON.toJSONString(msg));
}
/**
* WebSocket 錯誤事件
* @param session 用戶 Session
* @param error 錯誤信息
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("發(fā)生錯誤");
error.printStackTrace();
}
/**
* 顯示在線用戶
*/
private void showUserList() {
System.out.println("------------------------------------------");
System.out.println("當(dāng)前在線用戶");
System.out.println("------------------------------------------");
for (String username : sessionMap.keySet()) {
System.out.println(username);
}
System.out.println("------------------------------------------");
System.out.println();
}
/**
* 設(shè)置接收消息的用戶列表
* @return 用戶列表
*/
private String setUserList(){
ArrayList<String> list = new ArrayList<>(sessionMap.keySet());
UserList userList = new UserList();
userList.setUserlist(list);
return JSON.toJSONString(userList);
}
/**
* 發(fā)送消息到所有用戶種
* @param message 消息
*/
private void sendAllMessage(String message) {
try {
for (Session session : sessionMap.values()) {
session.getBasicRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
這段代碼實(shí)現(xiàn)了一個基于WebSocket的聊天室服務(wù)器,具有以下功能:
- 當(dāng)有新的用戶加入聊天室時,將其添加到
sessionMap
中,并向所有已連接的用戶發(fā)送當(dāng)前在線用戶列表。 - 當(dāng)用戶離開聊天室時,將其從
sessionMap
中移除,并向所有已連接的用戶發(fā)送當(dāng)前在線用戶列表。 - 當(dāng)收到客戶端發(fā)送的消息時,將消息轉(zhuǎn)換為JSON格式并發(fā)送給所有已連接的用戶。
- 在發(fā)生錯誤時,打印錯誤堆棧信息。
- 提供了一些輔助方法,如
sendAllMessage
用于向所有用戶發(fā)送消息,setUserList
用于設(shè)置用戶列表并將其轉(zhuǎn)換為JSON格式字符串,showUserList
用于顯示當(dāng)前在線用戶列表。
?用戶列表控制器
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.chat.component.WebSocketServer.sessionMap;
/**
* 用戶列表控制器
*/
@RestController
@RequestMapping("/list")
public class UserListController {
/**
* 判斷用戶名是否存在與聊天室
* @param username 用戶名
* @return json
*/
@GetMapping("/{username}")
public JSONObject getUsername(@PathVariable("username") String username) {
JSONObject jsonObject = new JSONObject();
boolean isEmpty = sessionMap.isEmpty();
jsonObject.put("isEmpty", isEmpty);
jsonObject.put("isExist", false);
if (!isEmpty) {
boolean isExist = sessionMap.containsKey(username);
jsonObject.replace("isExist", isExist);
}
return jsonObject;
}
}
這段代碼是一個用戶列表控制器,用于判斷用戶名是否存在于聊天室中。它使用了Spring框架的注解來實(shí)現(xiàn)HTTP請求映射和處理。
在控制器類`UserListController`中,定義了一個方法`getUsername`,該方法使用`@GetMapping`注解來映射到路徑`/list/{username}`的GET請求。通過`@PathVariable("username")`注解,可以將URL中的`username`參數(shù)傳遞給方法作為輸入。
方法內(nèi)部首先創(chuàng)建一個`JSONObject`對象`jsonObject`,然后檢查`sessionMap`是否為空。如果為空,將`isEmpty`屬性設(shè)置為`true`,表示聊天室中沒有用戶。否則,將`isEmpty`屬性設(shè)置為`false`。
接下來,方法檢查`sessionMap`中是否包含指定的用戶名。如果包含,將`isExist`屬性設(shè)置為`true`,表示用戶名存在于聊天室中;否則,將`isExist`屬性設(shè)置為`false`。
最后,方法返回`jsonObject`對象,其中包含了判斷結(jié)果的信息。
這段代碼中的`sessionMap`是一個靜態(tài)變量,用于存儲當(dāng)前聊天室中的所有用戶會話。
項(xiàng)目代碼文章來源:http://www.zghlxwxcb.cn/news/detail-777350.html
ShuoC/ez-chat文章來源地址http://www.zghlxwxcb.cn/news/detail-777350.html
到了這里,關(guān)于Vue + Element-Plus + SpringBoot + WebSocket實(shí)現(xiàn)簡易網(wǎng)絡(luò)聊天室的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!