springboot+vue实现websocket通信实例
前端代码:
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="姓名" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入姓名"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="密码" prop="pwd">
<el-input
v-model="queryParams.pwd"
placeholder="请输入密码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['testuser:testuser:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['testuser:testuser:edit']"
>修改</el-button>
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleTestNormal"
>自定义</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['testuser:testuser:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['testuser:testuser:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="testuserList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键" align="center" prop="id" />
<el-table-column label="姓名" align="center" prop="name" />
<el-table-column label="密码" align="center" prop="pwd" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['testuser:testuser:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleTestNormal(scope.row)"
v-hasPermi="['testuser:testuser:edit']"
>自定义</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['testuser:testuser:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改testuser对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="密码" prop="pwd">
<el-input v-model="form.pwd" placeholder="请输入密码" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listTestuser, getTestuser, delTestuser, addTestuser, updateTestuser, handleTest} from "@/api/testuser/testuser";
export default {
name: "Testuser",
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// testuser表格数据
testuserList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
name: null,
pwd: null,
},
// 表单参数
form: {},
// 表单校验
rules: {
},
//自定义表单校验
check: {},
reconnectCount : 0
};
},
created() {
this.getList();
this.connectWebSocket();
},
methods: {
connectWebSocket: function() {
// 创建 WebSocket 连接
if('WebSocket' in window){
this.socket = new WebSocket("ws://127.0.0.1:8080/websocket/yuanrenjie");
} else{
alert('Not support websocket')
};
//最大尝试链接次数
const maxReconnectAttempts = 10;
const reconnectInterval = 1000; // 1秒
const tryReconnect = () => {
if (this.reconnectCount < maxReconnectAttempts) {
console.log('Reconnecting...');
this.connectWebSocket();
this.reconnectCount++;
} else {
console.log('Exceeded max reconnect attempts, stopping reconnecting.');
}
};
this.socket.addEventListener('open', (event) => {
console.log('WebSocket 连接已建立')
let msg = {
// clientName: '发送erp仓库连接消息',
clientName: '',
message: null,
printPiece: 1,
labelInfo: null
}
let json = JSON.stringify(msg)
this.socket.send(json)
})
this.socket.addEventListener('message', (event) => {
//接收到的数据
const receivedData = event.data;
// alert('接收到的信息receivedData: ' + receivedData);
console.log('Received data:', receivedData)
})
// socket.addEventListener('close', (event) => {
this.socket.addEventListener('close', (event) => {
console.log('WebSocket connection closed')
// 在连接关闭时触发重连逻辑
// setTimeout(() => {
// console.log('Reconnecting...')
// this.connectWebSocket()
// }, 1000) // 1秒后重连
// 清除重连计数,防止无限递增
// 在连接关闭后,间隔一段时间进行重连
setTimeout(tryReconnect, reconnectInterval);
})
// socket.addEventListener('error', (event) => {
this.socket.addEventListener('error', (event) => {
console.error('WebSocket error:', event)
// 在错误发生时触发重连逻辑
// setTimeout(() => {
// console.log('Reconnecting...')
// this.connectWebSocket()
// }, 1000) // 1秒后重连
// 在连接错误后,间隔一段时间进行重连
setTimeout(tryReconnect, reconnectInterval);
})
window.addEventListener('beforeunload', (event) => {
// 当 tab 关闭时,关闭 WebSocket 连接
this.socket.close();
});
},
/** 查询testuser列表 */
getList() {
this.loading = true;
listTestuser(this.queryParams).then(response => {
this.testuserList = response.rows;
this.total = response.total;
this.loading = false;
});
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单重置
reset() {
this.form = {
id: null,
name: null,
pwd: null,
createTime: null,
updateTime: null
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加testuser";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids
getTestuser(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改testuser";
});
},
/** 自定义按钮操作 */
handleTestNormal(row) {
if(row.id){
handleTest(this.check).then(response => {
});
}else {
const id = row.id || this.ids
getTestuser(id).then(response => {
this.check = response.data;
handleTest(this.check).then(response => {
});
});
}
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateTestuser(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addTestuser(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除testuser编号为"' + ids + '"的数据项?').then(function() {
return delTestuser(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('testuser/testuser/export', {
...this.queryParams
}, `testuser_${new Date().getTime()}.xlsx`)
}
/** 导出发货计划 */
// handleExportExcelNeed(row) {
// const ids = row.id || this.ids;
// toExcel(ids).then(response => {
// this.$alert(response.msg, "导出成功", response.msg);
// });
//
// }
}
};
</script>
后端java:
开启配置
package com.ruoyi.web.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Component
public class WebSocketConfig {
/**
* ServerEndpointExporter 作用
*
* 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
*
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
具体接口:
package com.ruoyi.web.core.config;
import com.ruoyi.common.annotation.Anonymous;
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.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
@Anonymous
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}") // 接口路径 ws://localhost:8087/webSocket/userId;
public class WebSocketServer {
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
* 用户ID
*/
private String userId;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
//虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
// 注:底下WebSocket是当前类名
private static CopyOnWriteArraySet<WebSocketServer> webSockets =new CopyOnWriteArraySet<>();
// 用来存在线连接用户信息
private static ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<String,Session>();
/**
* 链接成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam(value="userId")String userId) {
try {
this.session = session;
this.userId = userId;
webSockets.add(this);
sessionPool.put(userId, session);
log.info("【websocket消息】有新的连接,总数为:"+webSockets.size());
} catch (Exception e) {
}
}
/**
* 链接关闭调用的方法
*/
@OnClose
public void onClose() {
try {
webSockets.remove(this);
sessionPool.remove(this.userId);
log.info("【websocket消息】连接断开,总数为:"+webSockets.size());
} catch (Exception e) {
}
}
/**
* 收到客户端消息后调用的方法
*
* @param message
* @param
*/
@OnMessage
public void onMessage(String message) {
log.info("【websocket消息】收到客户端消息:"+message);
}
/** 发送错误时的处理
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误,原因:"+error.getMessage());
error.printStackTrace();
}
// 此为广播消息
public void sendAllMessage(String message) {
log.info("【websocket消息】广播消息:"+message);
for(WebSocketServer webSocket : webSockets) {
try {
if(webSocket.session.isOpen()) {
webSocket.session.getAsyncRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 此为单点消息
public void sendOneMessage(String userId, String message) {
Session session = sessionPool.get(userId);
if (session != null&&session.isOpen()) {
try {
log.info("【websocket消息】 单点消息:"+message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 此为单点消息(多人)
public void sendMoreMessage(String[] userIds, String message) {
for(String userId:userIds) {
Session session = sessionPool.get(userId);
if (session != null&&session.isOpen()) {
try {
log.info("【websocket消息】 单点消息:"+message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}