由于心血来潮想要做个聊天室项目 ,但是仔细找了一下相关教程,却发现这么多的WebSocket教程里面,很多都没有介绍详细,代码都有所残缺,所以这次带来一个比较完整得使用WebSocket的项目。
目录
一、效果展示
二、准备工作
一、前端框架,Vue + elementUI组件 +JsCookie
二、后端 SpringBoot + websocket包
三、前端代码
四、后端代码
一、效果展示
1.用户交流
二、准备工作
一、前端框架,Vue + elementUI组件 +JsCookie
新建一个vue项目
引入以下组件与依赖
npm i element-ui -S
npm install js-cookie
二、后端 SpringBoot + websocket包
创建SpringBoot项目后
在pom.xml文件中引入以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
注意项目前端为8081端口,后端为8080端口,所以先运行后端再运行前端
三、前端代码
在App.vue中即可引入以下代码:
html:
<template>
<div id="Layout">
<el-container>
<el-aside width="200px">Aside</el-aside>
<el-container>
<el-header style="background-color: rgb(245, 245, 245); border-bottom: 1px solid grey;">
<h3>聊天广场</h3>
</el-header>
<el-main style="background-color: rgb(244, 245, 247);
min-height: 700px; max-height: 700px; ">
<div id="chatContent" style="padding-left: 10px; line-height: normal; ">
<!-- 循环输出对话内容 -->
<el-scrollbar v-for="(message, index) in messages" :key="index">
<div ref="scrollbar" v-if="message.sender !== senderName" class="chat-message" id="ChatContentCard" style=" background-color: white;
margin-top: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)
">
<el-avatar :size="40"
src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"></el-avatar>
<div class="message-content" style="width: 100%;">
<div style="text-align: left; text-indent: 1em;"> {{ message.sender }}</div>
<div id="chatContentText">{{ message.text }}</div>
</div>
</div>
<div ref="scrollbar" v-if="message.sender === senderName" id="myChatContentCard">
<el-avatar :size="40"
src="https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg"></el-avatar>
<div class="message-content" style="width: 100%;">
<div style="text-align: left; text-indent: 1em;"> {{ message.sender }}</div>
<div id="chatContentText">{{ message.text }}</div>
</div>
</div>
</el-scrollbar>
</div>
</el-main>
<!-- 底层交互框 -->
<el-footer style="height: 190px; background-color: rgb(244, 245, 247); ">
<div id="Gadget" style="background-color: rgb(244, 245, 247); height: 35px; margin-bottom: 10px;">
<el-upload class="upload-demo" ref="upload" action="https://jsonplaceholder.typicode.com/posts/"
:on-preview="handlePreview" :on-remove="handleRemove" :file-list="fileList" :auto-upload="false" style="float: left;">
<el-button slot="trigger" size="small" type="primary"><i class="el-icon-picture-outline-round"></i></el-button>
<el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button>
</el-upload>
</div>
<el-form @submit.native.prevent="sendMessage"
style="background-color: rgb(244, 245, 247); height: 80%; width: 100%; position: relative;">
<el-input v-model="messageInput" rows="4" resize="none" type="textarea" placeholder="请输入内容......."
@keyup.enter="sendMessage" style="height: 100%; max-height: 60px; ">
</el-input>
<div style="text-align: right; background-color: rgb(244, 245, 247); margin-top: 34px;">
<el-button type="primary" @click="sendMessage">发送</el-button>
</div>
</el-form>
</el-footer>
</el-container>
</el-container>
</div>
</template>
script:
<script>
import Cookies from 'js-cookie';
export default {
computed: {
senderName() {
return Cookies.get('account') || '游客';
}
},
name: 'App',
data() {
return {
messages: [],
messageInput: '',
ws: null,
fileList: []
};
},
mounted() {
this.initWebSocket();
},
beforeDestroy() {
this.closeWebSocket();
},
methods: {
initWebSocket() {
this.ws = new WebSocket('ws://localhost:8080/chat');
this.ws.onopen = () => {
console.log('Connected to server.');
};
this.ws.onmessage = (event) => {
try {
let messageData;
if (isJson(event.data)) {
messageData = JSON.parse(event.data);
} else {
messageData = { text: event.data };
}
this.messages.push({
sender: messageData.sender || 'Anonymous',
text: messageData.text,
});
// 使用Vue.nextTick确保DOM更新后再执行滚动操作
this.$nextTick(() => {
// 确保scrollbar存在且已渲染
if (this.$refs.scrollbar) {
// 直接滚动到底部,不需要使用contentSize
// this.$refs.scrollbar.$el.scrollTop = this.$refs.scrollbar.$el.scrollHeight;
}
});
} catch (error) {
console.error('Error parsing message:', error);
}
};
// 辅助函数,检查字符串是否可能是JSON格式
function isJson(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
this.ws.onclose = () => {
console.log('Disconnected from server.');
};
},
sendMessage() {
console.log('调用sendMessage');
const senderName = Cookies.get('account');
if (senderName === null) {
this.senderName = "游客";
}
if (this.messageInput.trim() !== '') {
this.ws.send(JSON.stringify({ sender: senderName, text: this.messageInput }));
this.messageInput = ''; // 清空输入框
}
},
closeWebSocket() {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.close();
}
},
// 文件上传函数
submitUpload() {
this.$refs.upload.submit();
},
handleRemove(file, fileList) {
console.log(file, fileList);
},
handlePreview(file) {
console.log(file);
}
},
};
</script>
css:
<style scoped>
.chat-message {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.message-content {
margin-left: 10px;
}
#Layout {
line-height: normal;
}
/* 添加动画关键帧 */
@keyframes slideInFromLeft {
0% {
transform: translateX(-100%);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
#ChatContentCard {
min-height: 80px;
width: 50%;
/* 应用动画 */
animation: slideInFromLeft 0.3s ease-in-out forwards;
border-radius: 30px
}
#chatContentText {
width: 99%;
overflow-wrap: break-word;
}
.el-header,
.el-footer {
background-color: #B3C0D1;
color: #333;
text-align: center;
line-height: 60px;
}
.el-aside {
background-color: #D3DCE6;
color: #333;
text-align: center;
line-height: 200px;
}
.el-main {
background-color: #E9EEF3;
color: #333;
text-align: center;
line-height: 160px;
}
body>.el-container {
margin-bottom: 40px;
}
.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {
line-height: 260px;
}
.el-container:nth-child(7) .el-aside {
line-height: 320px;
}
#myChatContentCard{
display: flex;
align-items: center;
margin-bottom: 10px;
margin-left: 50%;
min-height: 80px;
width: 50%;
/* 应用动画 */
animation: slideInFromRight 0.3s ease-in-out forwards;
border-radius: 30px;
background-color: rgb(149, 236, 105);
margin-top: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)
}
@keyframes slideInFromRight {
0% {
transform: translateX(100%);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
</style>
注意: 这个项目中如果script需要进行修改,由于我这里完成了一个登陆系统,所以采用了对Cookie的使用,而如果只是体验的话,只需要把Cookie去掉将其改为游客+随机字符串去替代即可。
前端启动
npm run serve
四、后端代码
WebSocket配置:
1.WebSocketConfig.java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new ChatWebSocketHandler(), "/chat").setAllowedOrigins("*");
}
}
2.ChatWebSocketHandler.java
@Slf4j
public class ChatWebSocketHandler extends TextWebSocketHandler {
private static final Set<WebSocketSession> sessions = Collections.synchronizedSet(new HashSet<>());
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
broadcast("欢迎新的小伙伴加入");
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
broadcast(message.getPayload());
}
private void broadcast(String message) {
log.info("服务器广播数据:"+message);
sessions.forEach(session -> {
try {
session.sendMessage(new TextMessage(message));
} catch (Exception e) {
e.printStackTrace();
}
});
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
}
}
后端启动: