How the WebSocket Notification System Works in ContiNew Admin
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, the getClientId method extracts and validates the token:
@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. 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:
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:
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:
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:
const socket = new WebSocket(`ws://host/api/ws?token=${localStorage.getItem('token')}`);
The message handler interprets simple string payloads:
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 - Flexible dispatch:
MessageServiceImpl.javasupports private messages, multi-device delivery, and broadcasts viaWebSocketUtils - 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-websocketdependency, 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. 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →