# How the WebSocket Notification System Works in ContiNew Admin

> Discover how ContiNew Admin's WebSocket notification system delivers real-time alerts. Learn about token-based messaging and Sa-Token's role.

- Repository: [OpenContiNew/continew-admin](https://github.com/continew-org/continew-admin)
- Tags: internals
- Published: 2026-02-28

---

**ContiNew Admin uses a token-based WebSocket layer (via the continew-starter-messaging-websocket starter) to push real-time notifications, where the Sa-Token serves as the client identifier for private messages and broadcasts.**

The WebSocket notification system in ContiNew Admin enables real-time messaging for system alerts, unread count updates, and broadcast notifications. Built on Spring's messaging infrastructure and integrated with the continew-starter-messaging-websocket starter, this architecture routes messages using Sa-Token authentication tokens as unique client identifiers.

## Client Connection and Token Validation

When a browser initiates a WebSocket connection, it must include the current Sa-Token as a query parameter. The server validates this token during the handshake and uses it as the client identifier for all subsequent messaging operations.

In [`continew-common/src/main/java/top/continew/admin/common/config/websocket/WebSocketClientServiceImpl.java`](https://github.com/continew-org/continew-admin/blob/main/continew-common/src/main/java/top/continew/admin/common/config/websocket/WebSocketClientServiceImpl.java), the `getClientId` method extracts and validates the token:

```java
@Override
public String getClientId(ServletServerHttpRequest request) {
    HttpServletRequest servletRequest = request.getServletRequest();
    String token = servletRequest.getParameter("token");
    if (StpUtil.getLoginIdByToken(token) == null) {
        throw new BusinessException("登录已过期，请重新登录");
    }
    return token;          // the token itself is used as the client identifier
}

```

The method returns the token string, which the starter library stores as the client ID in an internal `ConcurrentHashMap<String, WebSocketSession>`. This mapping enables targeted message delivery to specific authenticated sessions.

## Dispatching Notifications

The core notification logic resides in [`continew-system/src/main/java/top/continew/admin/system/service/impl/MessageServiceImpl.java`](https://github.com/continew-org/continew-admin/blob/main/continew-system/src/main/java/top/continew/admin/system/service/impl/MessageServiceImpl.java). This service handles three primary messaging scenarios using `WebSocketUtils` from the external starter library.

### Private Messages to Specific Users

To notify specific users, the system retrieves all active tokens associated with the user ID and sends the payload to each session:

```java
List<String> tokenList = StpUtil.getTokenValueListByLoginId(userId);
tokenList.parallelStream().forEach(token -> WebSocketUtils.sendMessage(token, "1"));

```

This approach ensures multi-device support, as a user may have multiple valid tokens if logged in from different browsers or devices.

### Unread Count Updates

When a user reads messages, the system pushes the updated unread count directly to that user's current session:

```java
String token = StpUtil.getTokenValueByLoginId(userId);
long unread = baseMapper.selectUnreadListByUserId(userId).size();
WebSocketUtils.sendMessage(token, String.valueOf(unread));

```

The frontend receives the numeric string and updates the UI badge accordingly.

### Broadcasting to All Clients

For system-wide announcements, the system broadcasts to all connected clients without specifying a client ID:

```java
WebSocketUtils.sendMessage("1");

```

The payload `"1"` serves as a lightweight signal instructing the frontend to refresh its message list or notification indicators.

## WebSocket Utility Layer

`WebSocketUtils` (provided by `top.continew.starter.messaging.websocket`) functions as a thin wrapper around Spring's `SimpMessagingTemplate`. It provides two primary operations:

- **Targeted delivery**: Looks up a specific session by token (client ID) and sends the payload to `/topic/notification`
- **Broadcast delivery**: Iterates over all stored sessions and pushes the message to every connected client

The starter library manages the session lifecycle, handling connection open, close, and error events while maintaining the token-to-session mapping.

## Frontend Integration

Clients connect by appending their Sa-Token to the WebSocket URL:

```javascript
const socket = new WebSocket(`ws://host/api/ws?token=${localStorage.getItem('token')}`);

```

The message handler interprets simple string payloads:

```javascript
socket.onmessage = (event) => {
  if (event.data === '1') {
    loadMessages();  // New system message arrived
  } else {
    updateUnreadCount(event.data);  // Numeric unread count
  }
};

```

## Summary

- **Token-based identification**: The system uses Sa-Token as the WebSocket client ID, validated during the handshake in [`WebSocketClientServiceImpl.java`](https://github.com/continew-org/continew-admin/blob/main/WebSocketClientServiceImpl.java)
- **Flexible dispatch**: [`MessageServiceImpl.java`](https://github.com/continew-org/continew-admin/blob/main/MessageServiceImpl.java) supports private messages, multi-device delivery, and broadcasts via `WebSocketUtils`
- **Lightweight protocol**: Simple string payloads (`"1"` for refresh, numeric strings for counts) minimize bandwidth and parsing overhead
- **External starter**: Low-level session management resides in the `continew-starter-messaging-websocket` dependency, keeping the admin codebase focused on business logic

## Frequently Asked Questions

### How does ContiNew Admin authenticate WebSocket connections?

The system authenticates connections during the handshake phase in [`WebSocketClientServiceImpl.java`](https://github.com/continew-org/continew-admin/blob/main/WebSocketClientServiceImpl.java). It extracts the `token` query parameter from the `ServletServerHttpRequest` and validates it using `StpUtil.getLoginIdByToken()`. If validation fails, the system throws a `BusinessException` and rejects the connection.

### What message format does the WebSocket notification system use?

The system uses plain string payloads rather than JSON structures. The frontend interprets `"1"` as a signal to refresh the message list, while numeric strings represent the current unread message count. This minimalist approach reduces parsing overhead for high-frequency updates.

### Can the system send notifications to specific users across multiple devices?

Yes. When targeting a specific user, the code calls `StpUtil.getTokenValueListByLoginId(userId)` to retrieve all active tokens for that user, then iterates through each token to deliver the message. This ensures delivery across all active sessions where the user is logged in.

### Where is the WebSocket session mapping stored?

The session mapping is maintained internally by the `continew-starter-messaging-websocket` starter library in a `ConcurrentHashMap<String, WebSocketSession>` keyed by the Sa-Token. While this implementation is not visible in the ContiNew Admin repository itself, it is accessed through the `WebSocketUtils` utility class.