Building a Real‑Time Device Monitoring Dashboard with WebSocket and Spring Boot
This tutorial walks through creating a fire‑equipment inspection system where the backend Spring Boot service pushes abnormal device alerts via WebSocket to a Vue‑based front‑end that visualizes device status on a map, covering project setup, WebSocket configuration, client‑side handling, and end‑to‑end testing.
Problem
Need a real‑time monitoring page for fire‑equipment inspections: when a mobile client reports an abnormal device, the server must push the alert instantly to a web page that highlights the faulty device on a location map. WebSocket provides bidirectional low‑latency communication.
Frontend
Plain HTML with Vue.js renders a list of devices. Each device is a block; CSS class .nowI (red background) is applied when state==-1. The Vue data model list contains objects {id, name, state}. WebSocket connection is created to ws://localhost:18801/webSocket/<UUID>. On message, the payload (device id) is parsed, the matching item in list has its state set to -1, and list.splice forces Vue re‑render.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>实时监控</title>
<style>
.item {display:flex; border-bottom:1px solid #000; justify-content:space-between; width:30%; line-height:50px; height:50px;}
.item span:nth-child(2){margin-right:10px; margin-top:15px; width:20px; height:20px; border-radius:50%; background:#55ff00;}
.nowI{background:#ff0000 !important;}
</style>
</head>
<body>
<div id="app">
<div v-for="item in list" class="item">
<span>{{item.id}}.{{item.name}}</span>
<span :class="item.state==-1 ? 'nowI' : ''"></span>
</div>
</div>
<script src="./js/vue.min.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {list:[
{id:1,name:'张三',state:1},
{id:2,name:'李四',state:1},
{id:3,name:'王五',state:1},
{id:4,name:'韩梅梅',state:1},
{id:5,name:'李磊',state:1}
]}
});
var webSocket = null;
if ('WebSocket' in window) {
webSocket = new WebSocket('ws://localhost:18801/webSocket/' + getUUID());
webSocket.onopen = function(){ console.log('已连接'); webSocket.send('消息发送测试'); };
webSocket.onmessage = function(msg){
var serverMsg = msg.data;
var t_id = parseInt(serverMsg);
for (var i=0;i<vm.list.length;i++){
var item = vm.list[i];
if(item.id==t_id){
item.state=-1;
vm.list.splice(i,1,item);
break;
}
}
};
webSocket.onclose = function(){ console.log('websocket已关闭'); };
webSocket.onerror = function(){ console.log('websocket发生了错误'); };
} else { alert('很遗憾,您的浏览器不支持WebSocket!'); }
function getUUID(){
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(c){
var r=Math.random()*16|0;
var v=c=='x'?r:(r&0x3|0x8);
return v.toString(16);
});
}
</script>
</body>
</html>Backend (Spring Boot)
Dependencies: spring-boot-starter-web and spring-boot-starter-websocket. Application runs on port 18801. A simple password jae_123 is stored in application.yml for the demo endpoint.
server:
port: 18801
mySocket:
myPwd: jae_123Key classes:
WebSocketConfig registers ServerEndpointExporter so that Spring can process @ServerEndpoint annotations.
WebSocketServer annotated with @ServerEndpoint("/webSocket/{uid}"). It holds a CopyOnWriteArraySet<Session> of client sessions and an AtomicInteger for online count. Lifecycle callbacks manage session pools and log connection changes. sendMessage sends a text message to a single session; broadCastInfo iterates over sessionPools and sends the same payload to all open sessions.
WebSocketController exposes POST /open/socket/onReceive. It reads id and pwd from the request, validates the password against ${mySocket.myPwd}, and calls webSocketServer.broadCastInfo(id) to push the device id to every connected browser.
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
@ServerEndpoint("/webSocket/{uid}")
@Component
public class WebSocketServer {
private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
private static final AtomicInteger onlineNum = new AtomicInteger(0);
private static final CopyOnWriteArraySet<Session> sessionPools = new CopyOnWriteArraySet<>();
@OnOpen
public void onOpen(Session session, @PathParam("uid") String uid) {
sessionPools.add(session);
onlineNum.incrementAndGet();
log.info(uid + "加入webSocket!当前人数为" + onlineNum);
}
@OnClose
public void onClose(Session session) {
sessionPools.remove(session);
int cnt = onlineNum.decrementAndGet();
log.info("有连接关闭,当前连接数为:{}", cnt);
}
@OnError
public void onError(Session session, Throwable throwable) {
log.error("发生错误");
throwable.printStackTrace();
}
public void sendMessage(Session session, String message) throws IOException {
if (session != null) {
synchronized (session) {
session.getBasicRemote().sendText(message);
}
}
}
public void broadCastInfo(String message) throws IOException {
for (Session session : sessionPools) {
if (session.isOpen()) {
sendMessage(session, message);
}
}
}
} @RestController
@RequestMapping("/open/socket")
public class WebSocketController {
@Value("${mySocket.myPwd}")
private String myPwd;
@Autowired
private WebSocketServer webSocketServer;
@PostMapping("/onReceive")
public void onReceive(String id, String pwd) throws IOException {
if (pwd.equals(myPwd)) {
webSocketServer.broadCastInfo(id);
}
}
}End‑to‑End Test Procedure
Start the Spring Boot application.
Open the HTML page in a browser; the console logs “已连接”. All items appear green because state is 1.
Use an HTTP client (e.g., Postman) to POST {"id":"3","pwd":"jae_123"} to http://localhost:18801/open/socket/onReceive.
The server validates the password, broadcasts the string “3”. The front‑end receives the message, parses the id, finds the matching entry in vm.list, sets state=-1, and Vue re‑renders the second span with class .nowI, turning the block red.
Observed result: the device with id=3 changes from green to red, confirming successful real‑time push.
Key Considerations
WebSocket requires browser support; a fallback alert is provided for unsupported browsers.
Session management uses thread‑safe collections to avoid concurrency issues.
The demo uses a plain password; production should employ proper authentication and encryption.
Vue’s splice call is necessary to trigger reactivity when mutating an object property.
Java Web Project
Focused on Java backend technologies, trending internet tech, and the latest industry developments. The platform serves over 200,000 Java developers, inviting you to learn and exchange ideas together. Check the menu for Java learning resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
