项目流程图
1. 前端搭建:
前端用Vue+Element-Plus 来搭建,由登录页面和聊天页面组成
1.1 登录页面
由一个昵称输入框组成,用户输入自己的昵称若昵称和别的用户不重复,则可进入聊天室,否则提示错误并请重新输入。
<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)"
>进入</el-button
>
</el-form-item>
</el-form>
</div>
</template>
这段代码是一个Vue.js组件的模板部分,用于实现聊天应用的用户昵称输入和登录功能
<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: "",
});
// 表单校验
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
组件来构建表单界面。其中,el-form
用于创建表单容器,el-input
用于输入昵称,el-button
用于提交表单。
在表单中,用户需要输入一个昵称,并点击"进入"按钮进行登录操作。当用户按下回车键时,会触发submitButton
函数,该函数会验证表单是否通过校验。如果校验通过,则发送一个HTTP请求到服务器,获取与该昵称对应的用户信息。
上图是登录界面的展示。
1.2聊天页面
<template>
<div class="chat">
<div class="list-pane">
<div class="user-pane">
<div class="user-count">
<h2>当前在线人数:{{ 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"
>发送</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");
// 查询是否设置了昵称
if (nickname.value == null) {
router.push("/");
return;
}
// 查询浏览器是否支持 WebSocket
if (typeof WebSocket == "undefined") {
alert("您的浏览器不支持 WebSocket");
router.push("/");
return;
}
// 开启 WebSocket 服务
let socketHost = "localhost";
let socketPort = "8888";
let socketUrl =
"ws://" + socketHost + ":" + socketPort + "/socket/" + nickname.value;
socket = new WebSocket(socketUrl);
// 连接服务器
socket.onopen = () => {
console.log("已连接至服务器");
};
// 浏览器接收服务端发送的消息
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);
// 获取节点
let chatHistory = document.getElementsByClassName("chat-message")[0];
if (chatHistory.scrollHeight >= chatHistory.clientHeight) {
setTimeout(function () {
//设置滚动条到最底部
chatHistory.scrollTop = chatHistory.scrollHeight;
}, 0);
}
}
};
// 关闭服务
socket.onclose = () => {
console.log("WebSocket 服务已关闭");
};
// 错误事件
socket.onerror = () => {
console.log("WebSocket 服务发生错误");
};
});
// 日期转换
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;
};
// 用户数量
let userCount = ref(0);
// 用户列表
let userList = ref([]);
// 信息框
let text = ref("");
// 信息列表
let messages = ref([]);
// 信息
let message = {
name: "",
time: "",
msg: "",
};
// 发送信息
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的响应式数据绑定和生命周期钩子函数来实现实时更新聊天界面的功能。
在模板部分,通过使用Vue的指令和组件来构建聊天界面的各个部分。其中包括用户列表、聊天消息、输入框和发送按钮等元素。
在脚本部分,首先导入了Vue的ref和onActivated函数,以及WebSocket库。然后定义了一些变量和函数,用于处理用户的昵称、连接WebSocket服务、接收服务器发送的消息、格式化时间等操作。
当页面激活时,会执行onActivated函数。在这个函数中,首先获取用户的昵称,并检查浏览器是否支持WebSocket。如果不支持或发生错误,则跳转到首页。
接下来,根据昵称构造WebSocket服务的URL,并创建一个新的WebSocket实例。然后设置一些事件监听器,包括连接成功、接收消息、关闭连接和发生错误等。
在接收消息的事件监听器中,解析服务器发送的数据,并根据数据类型进行相应的处理。如果是用户列表消息,则更新用户列表和用户数量;如果是普通消息,则将消息添加到信息列表中,并滚动到聊天历史记录的底部。
最后,定义了一个sendButton函数,用于发送用户输入的信息。这个函数会在按下回车键时触发,并检查输入框和昵称是否为空。如果不为空,则创建一个新的消息对象,并通过WebSocket服务发送给服务器。
整个聊天应用的样式部分使用了SCSS语言编写,并引入了一个外部的CSS文件。可以根据需要进一步自定义样式。
上图是聊天室页面的展示。
2. 后端部分
2.1环境配置
首先在idea中创建maven工程,接着导入依赖,pom.xml文件内容如下:
<?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 后端组件
创建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服务器端实现
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 服务
*/
@Component
@ServerEndpoint("/socket/{username}")
public class WebSocketServer {
/**
* 存储对象 map
*/
public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();
/***
* WebSocket 建立连接事件
* 1.把登录的用户存到 sessionMap 中
* 2.发送给所有人当前登录人员信息
*/
@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 关闭连接事件
* 1.把登出的用户从 sessionMap 中剃除
* 2.发送给所有人当前登录人员信息
*/
@OnClose
public void onClose(@PathParam("username") String username) {
if (username != null) {
System.out.println(username + "退出了聊天室");
sessionMap.remove(username);
sendAllMessage(setUserList());
showUserList();
}
}
/**
* WebSocket 接受信息事件
* 接收处理客户端发来的数据
* @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("发生错误");
error.printStackTrace();
}
/**
* 显示在线用户
*/
private void showUserList() {
System.out.println("------------------------------------------");
System.out.println("当前在线用户");
System.out.println("------------------------------------------");
for (String username : sessionMap.keySet()) {
System.out.println(username);
}
System.out.println("------------------------------------------");
System.out.println();
}
/**
* 设置接收消息的用户列表
* @return 用户列表
*/
private String setUserList(){
ArrayList<String> list = new ArrayList<>(sessionMap.keySet());
UserList userList = new UserList();
userList.setUserlist(list);
return JSON.toJSONString(userList);
}
/**
* 发送消息到所有用户种
* @param message 消息
*/
private void sendAllMessage(String message) {
try {
for (Session session : sessionMap.values()) {
session.getBasicRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
这段代码实现了一个基于WebSocket的聊天室服务器,具有以下功能:
- 当有新的用户加入聊天室时,将其添加到
sessionMap
中,并向所有已连接的用户发送当前在线用户列表。 - 当用户离开聊天室时,将其从
sessionMap
中移除,并向所有已连接的用户发送当前在线用户列表。 - 当收到客户端发送的消息时,将消息转换为JSON格式并发送给所有已连接的用户。
- 在发生错误时,打印错误堆栈信息。
- 提供了一些辅助方法,如
sendAllMessage
用于向所有用户发送消息,setUserList
用于设置用户列表并将其转换为JSON格式字符串,showUserList
用于显示当前在线用户列表。
用户列表控制器
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框架的注解来实现HTTP请求映射和处理。
在控制器类`UserListController`中,定义了一个方法`getUsername`,该方法使用`@GetMapping`注解来映射到路径`/list/{username}`的GET请求。通过`@PathVariable("username")`注解,可以将URL中的`username`参数传递给方法作为输入。
方法内部首先创建一个`JSONObject`对象`jsonObject`,然后检查`sessionMap`是否为空。如果为空,将`isEmpty`属性设置为`true`,表示聊天室中没有用户。否则,将`isEmpty`属性设置为`false`。
接下来,方法检查`sessionMap`中是否包含指定的用户名。如果包含,将`isExist`属性设置为`true`,表示用户名存在于聊天室中;否则,将`isExist`属性设置为`false`。
最后,方法返回`jsonObject`对象,其中包含了判断结果的信息。
这段代码中的`sessionMap`是一个静态变量,用于存储当前聊天室中的所有用户会话。
项目代码文章来源:https://www.toymoban.com/news/detail-777350.html
ShuoC/ez-chat文章来源地址https://www.toymoban.com/news/detail-777350.html
到了这里,关于Vue + Element-Plus + SpringBoot + WebSocket实现简易网络聊天室的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!