Real-Time Device Monitoring with SpringBoot WebSocket and Vue

This article demonstrates how to build a real‑time fire‑equipment inspection system using SpringBoot WebSocket on the backend and a Vue‑based frontend, covering project setup, WebSocket configuration, server‑client communication, and testing with code examples.

Top Architect
Top Architect
Top Architect
Real-Time Device Monitoring with SpringBoot WebSocket and Vue

The author describes a requirement for fire‑equipment inspection where abnormal device status reported from a mobile client must be pushed to a backend and displayed instantly on a monitoring page, prompting staff to handle the issue.

Because the server needs to actively push messages to the client, WebSocket is chosen as the communication protocol.

Implementation

Frontend

The frontend is a simple Vue page that renders a list of devices and changes the color of a device to red when its state becomes abnormal. The HTML and CSS are defined directly in the page.

<code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">1 <!DOCTYPE html></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">2 <html></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">3   <head></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">4     <meta charset="utf-8" /></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">5     <title>实时监控</title></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">6 </head></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">7 <style></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">8 .item {</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">9   display: flex;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">10   border-bottom: 1px solid #000000;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">11   justify-content: space-between;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">12   width: 30%;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">13   line-height: 50px;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">14   height: 50px;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">15 }</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">16 .item span:nth-child(2){</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">17   margin-right: 10px;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">18   margin-top: 15px;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">19   width: 20px;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">20   height: 20px;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">21   border-radius: 50%;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">22   background: #55ff00;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">23 }</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">24 .nowI{</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">25   background: #ff0000 !important;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">26 }</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">27 </style></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">28 <body></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">29   <div id="app"></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">30     <div v-for="item in list" class="item"></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">31       <span>{{item.id}}.{{item.name}}</span></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">32       <span :class='item.state==-1?"nowI":""'></span></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">33     </div></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">34   </div></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">35 </body></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">36 <script src="./js/vue.min.js"></script></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">37 <script type="text/javascript"></span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">38   var vm = new Vue({</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">39     el: "#app",</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">40     data: {</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">41       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}]</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">42   });</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">43   var webSocket = null;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">44   if ('WebSocket' in window) {</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">45     webSocket = new WebSocket("ws://localhost:18801/webSocket/" + getUUID());</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">46     webSocket.onopen = function() { console.log("已连接"); webSocket.send("消息发送测试"); };</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">47     webSocket.onmessage = function(msg) {</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">48       var serverMsg = msg.data;</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">49       var t_id = parseInt(serverMsg);</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">50       for (var i = 0; i < vm.list.length; i++) {</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">51         var item = vm.list[i];</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">52         if(item.id == t_id){ item.state = -1; vm.list.splice(i,1,item); break; }</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">53       }</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">54     };</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">55   } else { alert("很遗憾,您的浏览器不支持WebSocket!"); }</span></span></code><code style='max-width: 1000%; text-align: left; display: flex; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace'><span style="max-width: 1000%"><span style="max-width: 1000%">56   function getUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random()*16|0, v = c=='x'?r:(r&0x3|0x8); return v.toString(16); }); }</span></span></code>

Backend

The backend is a SpringBoot project with WebSocket dependencies. The application.yml sets the server port to 18801 and defines a simple password for demonstration.

#端口
server:
  port: 18801

#密码,因为接口不需要权限,所以加了个密码做校验
mySocket:
  myPwd: jae_123

A WebSocketConfig class registers ServerEndpointExporter so that beans annotated with @ServerEndpoint are automatically detected.

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

The core server endpoint is WebSocketServer, which manages connections, broadcasts messages, and handles errors. It uses a thread‑safe CopyOnWriteArraySet<Session> to store active sessions and an AtomicInteger to track online count.

@ServerEndpoint("/webSocket/{uid}")
@Component
public class WebSocketServer {
    private static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
    private static final AtomicInteger onlineNum = new AtomicInteger(0);
    private static CopyOnWriteArraySet<Session> sessionPools = new CopyOnWriteArraySet<>();

    @OnOpen
    public void onOpen(Session session, @PathParam(value = "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);
    }

    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        // handle incoming messages if needed
    }

    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);
            }
        }
    }

    @OnError
    public void onError(Session session, Throwable throwable){
        log.error("发生错误");
        throwable.printStackTrace();
    }
}

A simple WebSocketController exposes a POST endpoint /open/socket/onReceive that receives an equipment ID and a password. If the password matches the configured value, the controller triggers a broadcast to all connected WebSocket clients, causing the frontend to mark the corresponding device as abnormal.

@RestController
@RequestMapping("/open/socket")
public class WebSocketController {
    @Value("${mySocket.myPwd}")
    public String myPwd;

    @Autowired
    private WebSocketServer webSocketServer;

    @PostMapping(value = "/onReceive")
    public void onReceive(String id,String pwd) throws IOException {
        if(pwd.equals(myPwd)){
            webSocketServer.broadCastInfo(id);
        }
    }
}

Testing

1. Open the frontend page; the console shows "已连接" indicating a successful WebSocket connection.

2. Initially all devices appear normal (green).

3. Use a tool such as Postman to POST to /open/socket/onReceive with id=3 and the correct password. The server broadcasts the ID, the frontend receives it, and the item with ID 3 turns red, confirming real‑time communication.

Conclusion

The example provides a complete end‑to‑end demonstration of real‑time monitoring using SpringBoot WebSocket on the backend and Vue on the frontend, which can be adapted to similar inspection or alerting scenarios in production environments.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Vuereal-time monitoringSpringBoot
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.