SpringBoot WebSocket多消息推送過程
更新時(shí)間:2026年01月13日 14:35:28 作者:掐指一算乀缺錢
介紹了如何實(shí)現(xiàn)多種消息推送功能,包括給分組、用戶推送消息,處理多設(shè)備登錄,監(jiān)控連接心跳,并使用公平鎖支持并發(fā),詳細(xì)描述了WebSocket配置類、攔截器、處理器和工具類的實(shí)現(xiàn),并提供了測(cè)試地址
實(shí)現(xiàn)功能
- 1. 給某個(gè)分組推送消息
- 2. 給所有分組推送消息
- 3. 給所有用戶推送消息
- 4. 給某一個(gè)用戶單獨(dú)推送消息(在分組中)
- 5. 給某一個(gè)用戶推送消息(不在在分組中)
- 6. 用戶可能存在多個(gè)分組
- 7. 用戶多設(shè)備登錄
- 8. 監(jiān)控連接心跳(后端實(shí)現(xiàn)不需要前端實(shí)現(xiàn)配合)
- 9. 失敗超過5次關(guān)閉連接
使用公平鎖支持并發(fā)。
添加依賴
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>websocket配置類
/**
* @Description websocket配置類
* @Author WangKun
* @Date 2025/8/10 19:02
* @Version
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
/**
* @Description websocket攔截器配置端點(diǎn)
* @param registry WebSocket處理程序注冊(cè)表,用于注冊(cè)WebSocket處理器和攔截器
* @Throws 可能拋出WebSocket相關(guān)的異常
* @Return void 無返回值
* @Date 2025-08-10 19:14:42
* @Author WangKun
**/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注冊(cè)WebSocket處理器,并設(shè)置URL路徑為"/ws/{id}"
registry.addHandler(webSocketHandler(), "/ws/{id}")
// 允許所有來源的跨域請(qǐng)求
.setAllowedOrigins("*");
}
/**
* @Description websocket核心處理器
* @Throws 可能拋出Bean創(chuàng)建相關(guān)的異常
* @Return JWebSocketManager 返回WebSocket管理器的實(shí)例
* @Date 2025-08-10 19:13:48
* @Author WangKun
**/
@Bean
public WebSocketManager webSocketManager() {
return new WebSocketManager();
}
/**
* @Description websocket攔截處理
* @Throws 可能拋出Bean創(chuàng)建相關(guān)的異常
* @Return JWebSocketHandler 返回WebSocket處理器的實(shí)例
* @Date 2025-08-10 19:15:20
* @Author WangKun
**/
@Bean // 將此方法返回的對(duì)象注冊(cè)為Spring容器中的Bean
public WebSocketHandler webSocketHandler() {
// 創(chuàng)建WebSocket處理器實(shí)例,并注入WebSocket管理器
return new WebSocketHandler(webSocketManager());
}
}websocket攔截器
/**
* @Description websocket攔截器
* @Author WangKun
* @Date 2025/8/10 19:14
* @Version
*/
@Slf4j
public class WebSocketHandler extends TextWebSocketHandler {
private final WebSocketManager manager; // WebSocket管理器,用于管理WebSocket會(huì)話
/**
* @Description 構(gòu)造器注入核心
* @param manager WebSocket管理器實(shí)例
* @Throws
* @Return
* @Date 2025-08-21 09:27:35
* @Author WangKun
**/
public WebSocketHandler(WebSocketManager manager) {
this.manager = manager;
}
/**
* @param session WebSocket會(huì)話對(duì)象
* @Description 初始化會(huì)話
* @Throws 當(dāng)會(huì)話建立過程中出現(xiàn)異常時(shí)拋出
* @Return void
* @Date 2025-08-10 19:17:57
* @Author WangKun
**/
@Override
public void afterConnectionEstablished(WebSocketSession session) {
manager.addSession(extractId(session), session);
}
/**
* @param session WebSocket會(huì)話對(duì)象
* @param message 接收到的文本消息
* @Description PONG消息處理
* @Throws 當(dāng)消息處理過程中出現(xiàn)異常時(shí)拋出
* @Return void
* @Date 2025-08-10 19:18:01
* @Author WangKun
**/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
String payload = message.getPayload();
log.info("收到來自[{}]的PONG消息: {}", extractId(session), payload);
if ("PONG".equals(payload)) {
manager.handlePongMessage(session);
}
}
/**
* @param session WebSocket會(huì)話對(duì)象
* @param message 接收到的PONG消息
* @Description 消息處理
* @Throws 當(dāng)消息處理過程中出現(xiàn)異常時(shí)拋出
* @Return void
* @Date 2025-08-10 19:18:08
* @Author WangKun
**/
@Override
public void handlePongMessage(WebSocketSession session, PongMessage message) {
manager.handlePongMessage(session);
}
/**
* @param session WebSocket會(huì)話對(duì)象
* @param status 會(huì)話關(guān)閉狀態(tài)
* @Description 會(huì)話關(guān)閉
* @Throws 當(dāng)會(huì)話關(guān)閉過程中出現(xiàn)異常時(shí)拋出
* @Return void
* @Date 2025-08-10 19:18:13
* @Author WangKun
**/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
manager.removeToSession(session);
}
/**
* @param session WebSocket會(huì)話對(duì)象
* @Description 攔截請(qǐng)求參數(shù)處理
* @Throws 當(dāng)URI解析過程中出現(xiàn)異常時(shí)拋出
* @Return java.lang.String 返回從URI中提取的ID
* @Date 2025-08-10 19:18:22
* @Author WangKun
**/
private String extractId(WebSocketSession session) {
// 從URI路徑提取ID:/ws/{id}
String path = session.getUri().getPath();
return path.substring(path.lastIndexOf('/') + 1);
}
}websocket處理器
/**
* @Description websocket處理器
* @Author WangKun
* @Date 2025/8/10 19:20
* @Version
*/
@Slf4j
public class WebSocketManager {
// ID -> 設(shè)備會(huì)話集合 (支持多設(shè)備)
private final ConcurrentMap<String, Set<WebSocketSession>> SESSIONS = new ConcurrentHashMap<>();
// 會(huì)話 -> ID
private final ConcurrentMap<WebSocketSession, String> SESSION_MAP = new ConcurrentHashMap<>();
// 分組ID -> ID集合
private final ConcurrentMap<String, Set<String>> GROUPS = new ConcurrentHashMap<>();
// 會(huì)話失敗計(jì)數(shù)器 (Session -> 失敗次數(shù))
private final ConcurrentMap<WebSocketSession, Integer> FAILURE_COUNTS = new ConcurrentHashMap<>();
// 心跳狀態(tài)監(jiān)控 (Session -> 最后活躍時(shí)間)
private final ConcurrentMap<WebSocketSession, Long> LAST_ACTIVE_TIMES = new ConcurrentHashMap<>();
// 公平鎖,確保所有操作的原子性
private final ReentrantLock MAIN_LOCK = new ReentrantLock(true);
// 心跳狀態(tài)監(jiān)控
private final ConcurrentMap<WebSocketSession, Long> lastPongTimes = new ConcurrentHashMap<>();
private final ConcurrentMap<WebSocketSession, ScheduledFuture<?>> pingTasks = new ConcurrentHashMap<>();
// 心跳間隔25秒
private static final long HEARTBEAT_INTERVAL = 25000;
// 心跳超時(shí)40秒
private static final long HEARTBEAT_TIMEOUT = 40000;
// 心跳PING NIO包裝
private static final ByteBuffer PING_PAYLOAD = ByteBuffer.wrap(new byte[]{0x1});
// 線程池
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(4);
public WebSocketManager() {
// 心跳檢測(cè)任務(wù)
scheduler.scheduleAtFixedRate(this::checkHeartbeats, 10, 10, TimeUnit.SECONDS);
// 會(huì)話清理任務(wù)
scheduler.scheduleAtFixedRate(this::cleanExpiredSessions, 5, 5, TimeUnit.MINUTES);
}
/**
* @param id
* @param session
* @Description 建立連接添加到session
* @Throws
* @Return void
* @Date 2025-08-10 19:37:14
* @Author WangKun
**/
public void addSession(String id, WebSocketSession session) {
MAIN_LOCK.lock();
try {
if (!SESSION_MAP.containsKey(session)) {
// 會(huì)話管理
SESSIONS.computeIfAbsent(id, k -> ConcurrentHashMap.newKeySet()).add(session);
SESSION_MAP.put(session, id);
// 初始化心跳狀態(tài)
lastPongTimes.put(session, System.currentTimeMillis());
startPingTask(session);
log.info("[連接建立] {}: 會(huì)話:{}", id, session.getId());
}
} finally {
MAIN_LOCK.unlock();
}
}
/**
* @param session
* @Description 移除session中的
* @Throws
* @Return void
* @Date 2025-08-10 19:40:11
* @Author WangKun
**/
public void removeToSession(WebSocketSession session) {
MAIN_LOCK.lock();
try {
String id = SESSION_MAP.get(session);
if (id != null) {
// 清理會(huì)話
Set<WebSocketSession> sessions = SESSIONS.get(id);
if (sessions != null) {
sessions.remove(session);
if (sessions.isEmpty()) {
SESSIONS.remove(id);
}
}
// 清理心跳任務(wù)
stopPingTask(session);
lastPongTimes.remove(session);
SESSION_MAP.remove(session);
log.info("[連接關(guān)閉] {}: 會(huì)話{}: ", id, session.getId());
}
} finally {
MAIN_LOCK.unlock();
}
}
/**
* @param session
* @Description 開始心跳PING
* @Throws
* @Return void
* @Date 2025-08-10 19:41:06
* @Author WangKun
**/
private void startPingTask(WebSocketSession session) {
ScheduledFuture<?> task = scheduler.scheduleAtFixedRate(() -> {
MAIN_LOCK.lock();
try {
if (session.isOpen()) {
try {
// 標(biāo)準(zhǔn)WebSocket Ping發(fā)送
if (session instanceof StandardWebSocketSession) {
((StandardWebSocketSession) session).getNativeSession()
.getAsyncRemote().sendPing(PING_PAYLOAD);
} else {
session.sendMessage(new PingMessage(PING_PAYLOAD));
}
log.info("[Ping發(fā)送] 會(huì)話{}: ", session.getId());
} catch (Exception e) {
log.info("[Ping失敗] 會(huì)話{}: 錯(cuò)誤{}: {}", session.getId(), e.getMessage(), session.getUri());
closeSession(session);
}
}
} finally {
MAIN_LOCK.unlock();
}
}, HEARTBEAT_INTERVAL, HEARTBEAT_INTERVAL, TimeUnit.MILLISECONDS);
pingTasks.put(session, task);
}
/**
* @param session
* @Description 心跳停止
* @Throws
* @Return void
* @Date 2025-08-10 19:41:55
* @Author WangKun
**/
private void stopPingTask(WebSocketSession session) {
ScheduledFuture<?> task = pingTasks.remove(session);
if (task != null) {
task.cancel(true);
}
}
/**
* @param session
* @Description 心跳PONG接收
* @Throws
* @Return void
* @Date 2025-08-10 19:42:20
* @Author WangKun
**/
public void handlePongMessage(WebSocketSession session) {
MAIN_LOCK.lock();
try {
lastPongTimes.put(session, System.currentTimeMillis());
log.info("[Pong接收] 會(huì)話{}: {}", session.getId(), session.getUri());
} finally {
MAIN_LOCK.unlock();
}
}
/**
* @param id
* @param groupId
* @Description 添加至分組
* @Throws
* @Return void
* @Date 2025-08-10 19:43:20
* @Author WangKun
**/
public boolean addToGroup(String id, String groupId) {
MAIN_LOCK.lock();
if (StringUtils.isBlank(id) && StringUtils.isEmpty(id)) {
log.info("加入分組失敗,id 為空");
return false;
}
if (StringUtils.isBlank(groupId) && StringUtils.isEmpty(groupId)) {
log.info("加入分組失敗,groupId 為空");
return false;
}
try {
boolean flag = GROUPS.computeIfAbsent(groupId, k -> ConcurrentHashMap.newKeySet()).add(id);
log.info("[加入分組] {}: → 分組{}: (當(dāng)前組內(nèi): {})", id, groupId, GROUPS.get(groupId).size());
return flag;
} finally {
MAIN_LOCK.unlock();
}
}
/**
* @param id
* @param groupId
* @Description 從分組移除
* @Throws
* @Return void
* @Date 2025-08-10 19:44:10
* @Author WangKun
**/
public boolean removeToGroup(String id, String groupId) {
MAIN_LOCK.lock();
try {
if (StringUtils.isBlank(id) && StringUtils.isEmpty(id)) {
log.info("移除分組失敗,id 為空");
return false;
}
if (StringUtils.isBlank(groupId) && StringUtils.isEmpty(groupId)) {
log.info("移除分組失敗,groupId 為空");
return false;
}
Set<String> groups = GROUPS.get(groupId);
if (groups != null) {
groups.remove(id);
if (groups.isEmpty()) {
GROUPS.remove(groupId);
}
log.info("[離開分組] {}: ← 分組{}: ", id, groupId);
return true;
}
return false;
} finally {
MAIN_LOCK.unlock();
}
}
/**
* @param id
* @param message
* @Description 消息發(fā)送,多設(shè)備
* @Throws
* @Return boolean
* @Date 2025-08-10 19:44:48
* @Author WangKun
**/
public boolean sendMessages(String id, String message) {
MAIN_LOCK.lock();
try {
if (StringUtils.isBlank(id) && StringUtils.isEmpty(id)) {
log.info("消息發(fā)送失敗,id 為空");
return false;
}
Set<WebSocketSession> sessions = SESSIONS.get(id);
if (sessions == null || sessions.isEmpty()) {
log.info("[單發(fā)消息失敗] {}: 無活躍會(huì)話", id);
return false;
}
// 創(chuàng)建副本防止并發(fā)修改
List<WebSocketSession> sessionsCopy = new ArrayList<>(sessions);
boolean allSuccess = true;
for (WebSocketSession session : sessionsCopy) {
if (validateSession(session)) {
allSuccess = false;
continue;
}
allSuccess &= sendMessage(session, message);
}
log.info("[單發(fā)消息完成] {}: 設(shè)備數(shù){}:成功{} ", id, sessionsCopy.size(), allSuccess ? "全部" : "部分");
return allSuccess;
} finally {
MAIN_LOCK.unlock();
}
}
/**
* @param groupId
* @param message
* @Description 按組發(fā)送
* @Throws
* @Return boolean
* @Date 2025-08-10 19:46:15
* @Author WangKun
**/
public boolean sendGroupMessages(String groupId, String message) {
MAIN_LOCK.lock();
try {
if (StringUtils.isBlank(groupId)&& StringUtils.isEmpty(message)) {
log.info("[按組發(fā)失敗] groudId為空");
return false;
}
Set<String> ids = GROUPS.get(groupId);
if (ids == null || ids.isEmpty()) {
log.info("[按組發(fā)失敗] 分組{}: 無成員", groupId);
return false;
}
boolean allSuccess = true;
// 創(chuàng)建副本防止并發(fā)修改
for (String id : new ArrayList<>(ids)) {
allSuccess &= sendMessages(id, message);
}
log.info("[組發(fā)完成] 分組{}: 成員數(shù){}: 成功{}", groupId, ids.size(), allSuccess ? "全部" : "部分");
return allSuccess;
} finally {
MAIN_LOCK.unlock();
}
}
/**
* @param message
* @Description 全局廣播無論在不在分組
* @Throws
* @Return boolean
* @Date 2025-08-10 19:47:33
* @Author WangKun
**/
public boolean sendGlobalMessages(String message) {
MAIN_LOCK.lock();
try {
if (SESSIONS.isEmpty()) {
log.info("[廣播失敗] 無活躍");
return false;
}
boolean allSuccess = true;
int totalSessions = 0;
// 創(chuàng)建副本防止并發(fā)修改
Set<String> ids = new HashSet<>(SESSIONS.keySet());
for (String id : ids) {
Set<WebSocketSession> sessions = SESSIONS.get(id);
if (sessions != null) {
// 創(chuàng)建會(huì)話副本
List<WebSocketSession> sessionsCopy = new ArrayList<>(sessions);
totalSessions += sessionsCopy.size();
for (WebSocketSession session : sessionsCopy) {
if (validateSession(session)) {
allSuccess = false;
continue;
}
allSuccess &= sendMessage(session, message);
}
}
}
log.info("[廣播完成] 總{}: 總設(shè)備{}: 成功{} ", ids.size(), totalSessions, allSuccess ? "全部" : "部分");
return allSuccess;
} finally {
MAIN_LOCK.unlock();
}
}
/**
* @param session
* @param message
* @Description 會(huì)話發(fā)送消息
* @Throws
* @Return boolean
* @Date 2025-08-10 19:49:00
* @Author WangKun
**/
private boolean sendMessage(WebSocketSession session, String message) {
try {
if (!session.isOpen()) {
throw new IllegalStateException("會(huì)話已關(guān)閉");
}
// 會(huì)話級(jí)別的同步,鎖,防止亂發(fā)
synchronized (session) {
session.sendMessage(new TextMessage(message));
recordSuccessfulSend(session);
return true;
}
} catch (Exception e) {
handleSendFailure(session, e);
return false;
}
}
/**
* @param session
* @Description 發(fā)送記錄 ,心跳更新狀態(tài)
* @Throws
* @Return void
* @Date 2025-08-10 19:50:41
* @Author WangKun
**/
private void recordSuccessfulSend(WebSocketSession session) {
FAILURE_COUNTS.put(session, 0);
LAST_ACTIVE_TIMES.put(session, System.currentTimeMillis());
}
/**
* @param session
* @param e
* @Description 失敗關(guān)閉連接
* @Throws
* @Return void
* @Date 2025-08-10 19:51:10
* @Author WangKun
**/
private void handleSendFailure(WebSocketSession session, Exception e) {
MAIN_LOCK.lock();
try {
int failures = FAILURE_COUNTS.getOrDefault(session, 0) + 1;
FAILURE_COUNTS.put(session, failures);
log.info("[發(fā)送失敗] 會(huì)話{}: 次數(shù){}: 原因{}: ", session.getId(), failures, e.getMessage());
if (failures >= 5) {
log.info("[自動(dòng)清理] 達(dá)到失敗上限 會(huì)話: {}", session.getId());
closeSession(session);
}
} finally {
MAIN_LOCK.unlock();
}
}
/**
* @param
* @Description 心跳健康監(jiān)控
* @Throws
* @Return void
* @Date 2025-08-10 19:52:24
* @Author WangKun
**/
private void checkHeartbeats() {
MAIN_LOCK.lock();
try {
long currentTime = System.currentTimeMillis();
new ArrayList<>(lastPongTimes.keySet()).forEach(session -> {
Long lastPong = lastPongTimes.get(session);
if (lastPong != null && currentTime - lastPong > HEARTBEAT_TIMEOUT) {
log.info("[心跳超時(shí)] 會(huì)話{}: 最后Pong{}: 前 ", session.getId(), currentTime - lastPong);
closeSession(session);
}
});
} finally {
MAIN_LOCK.unlock();
}
}
/**
* @param session
* @Description 檢驗(yàn)session
* @Throws
* @Return boolean
* @Date 2025-08-10 19:53:35
* @Author WangKun
**/
private boolean validateSession(WebSocketSession session) {
if (!session.isOpen()) {
removeToSession(session);
return true;
}
return false;
}
/**
* @param session
* @Description 關(guān)閉session, 會(huì)話結(jié)束
* @Throws
* @Return void
* @Date 2025-08-10 19:54:07
* @Author WangKun
**/
private void closeSession(WebSocketSession session) {
try {
if (session.isOpen()) {
session.close(CloseStatus.SESSION_NOT_RELIABLE);
}
} catch (Exception e) {
log.info("[關(guān)閉異常] 會(huì)話{}: 原因{}: ", session.getId(), e.getMessage());
} finally {
removeToSession(session);
}
}
/**
* @param
* @Description 清理無效會(huì)話
* @Throws
* @Return void
* @Date 2025-08-10 19:55:08
* @Author WangKun
**/
private void cleanExpiredSessions() {
MAIN_LOCK.lock();
try {
new ArrayList<>(SESSION_MAP.keySet()).forEach(session -> {
if (!session.isOpen()) {
removeToSession(session);
}
});
} finally {
MAIN_LOCK.unlock();
}
}
}websocket工具類封裝
(SpringUtils就是一個(gè)自己封裝獲取spring上下文的工具)
/**
* @Description websocket工具類
* @Author WangKun
* @Date 2025/8/11 10:08
* @Version
*/
@Component
public class WebSocketUtils {
/**
* websocket處理器資源
**/
private static final WebSocketManager WEB_SOCKET_MANAGER = SpringUtils.getBean(WebSocketManager.class);
/**
* @param groupId
* @param id
* @Description 添加至分組
* @Throws
* @Return boolean
* @Date 2025-08-11 10:14:27
* @Author WangKun
**/
public static boolean addToGroup(String id, String groupId) {
return WEB_SOCKET_MANAGER.addToGroup(id, groupId);
}
/**
* @param id
* @param groupId
* @Description 移除分組
* @Throws
* @Return boolean
* @Date 2025-08-11 10:44:47
* @Author WangKun
**/
public static boolean removeToGroup(String id, String groupId) {
return WEB_SOCKET_MANAGER.removeToGroup(id, groupId);
}
/**
* @param id
* @param message
* @Description 指定單發(fā)消息
* @Throws
* @Return boolean
* @Date 2025-08-11 11:03:14
* @Author WangKun
**/
public static boolean sendMessage(String id, String message) {
return WEB_SOCKET_MANAGER.sendMessages(id, message);
}
/**
* @param groupId
* @param message
* @Description 指定分組發(fā)消息
* @Throws
* @Return boolean
* @Date 2025-08-11 11:09:22
* @Author WangKun
**/
public static boolean sendGroupMessages(String groupId, String message) {
return WEB_SOCKET_MANAGER.sendGroupMessages(groupId, message);
}
/**
* @param message
* @Description 全局廣播發(fā)消息
* @Throws
* @Return boolean
* @Date 2025-08-11 11:16:54
* @Author WangKun
**/
public static boolean sendGlobalMessages(String message) {
return WEB_SOCKET_MANAGER.sendGlobalMessages(message);
}
}
websocket控制器
/**
* @Description websocket控制器
* @Author WangKun
* @Date 2025/8/10 19:59
* @Version
*/
@RestController
@RequestMapping("/api/websocket")
public class WebSocketController {
private final WebSocketManager manager;
/**
* @param manager
* @Description 構(gòu)造函數(shù),通過依賴注入方式初始化WebSocket管理器
* @Throws
* @Return
* @Date 2025-08-21 09:25:44
* @Author WangKun
**/
public WebSocketController(WebSocketManager manager) {
this.manager = manager;
}
/**
* @param
* @Description 測(cè)試連接地址
* @Throws
* @Return java.lang.String
* @Date 2025-08-10 19:59:26
* @Author WangKun
**/
@GetMapping("/test") // 處理GET請(qǐng)求,用于測(cè)試WebSocket連接
public String testConnection() {
WebSocketUtils.addToGroup("test", "test");
WebSocketUtils.sendGlobalMessages("壓測(cè)");
return "ws://localhost:8099/ws/{id}";
}
/**
* @param id 用戶ID
* @param groupId 分組ID
* @Description 添加到分組
* @Throws
* @Return java.lang.String
* @Date 2025-08-10 19:59:42
* @Author WangKun
**/
@PostMapping("/addToGroup")
public String addToGroup(@RequestParam String id, @RequestParam String groupId) {
boolean flag = manager.addToGroup(id, groupId);
return "[" + id + "]添加到分組[" + groupId + "]:" + flag;
}
/**
* @param id 用戶ID
* @param groupId 分組ID
* @Description 從分組移除
* @Throws
* @Return java.lang.String
* @Date 2025-08-10 19:59:48
* @Author WangKun
**/
@PostMapping("/removeToGroup")
public String removeToGroup(@RequestParam String id, @RequestParam String groupId) {
boolean flag = manager.removeToGroup(id, groupId);
return "[" + id + "]從分組[" + groupId + "]移除:" + flag;
}
/**
* @param id 用戶ID
* @param message 要發(fā)送的消息內(nèi)容
* @Description 發(fā)送消息
* @Throws
* @Return java.lang.String
* @Date 2025-08-10 19:59:56
* @Author WangKun
**/
@PostMapping("/sendMessages")
public String sendMessages(@RequestParam String id, @RequestParam String message) {
boolean success = manager.sendMessages(id, message);
return success ? "發(fā)送成功" : "發(fā)送失敗(無活躍連接)";
}
/**
* @param groupId 分組ID
* @param message 要發(fā)送的消息內(nèi)容
* @Description 給分組發(fā)送消息
* @Throws
* @Return java.lang.String
* @Date 2025-08-10 20:00:06
* @Author WangKun
**/
@PostMapping("/sendGroupMessages")
public String sendToGroup(@RequestParam String groupId, @RequestParam String message) {
boolean success = manager.sendGroupMessages(groupId, message);
return success ? "發(fā)送成功" : "發(fā)送失?。ǚ纸M不存在或無成員)";
}
/**
* @param message 要廣播的消息內(nèi)容
* @Description 全局廣播
* @Throws
* @Return java.lang.String
* @Date 2025-08-10 20:00:13
* @Author WangKun
**/
@PostMapping("/sendGlobalMessages")
public String broadcast(@RequestParam String message) {
boolean success = manager.sendGlobalMessages(message);
return success ? "廣播成功" : "部分發(fā)送失敗";
}
}測(cè)試地址
ws://localhost:8099/ws/user1

剩下的測(cè)試地址都在控制中。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot傳遞單一參數(shù)時(shí)@RequestParam和@RequestBody的區(qū)別小結(jié)
用SpringBoot框架做項(xiàng)目時(shí),經(jīng)常需要前端給后端傳遞參數(shù),本文主要介紹了SpringBoot傳遞單一參數(shù)時(shí)@RequestParam和@RequestBody的區(qū)別,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08
完美解決因數(shù)據(jù)庫一次查詢數(shù)據(jù)量過大導(dǎo)致的內(nèi)存溢出問題
今天小編就為大家分享一篇完美解決因數(shù)據(jù)庫一次查詢數(shù)據(jù)量過大導(dǎo)致的內(nèi)存溢出問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-06-06
Java中實(shí)例初始化和靜態(tài)初始化的過程詳解
Java代碼初始化塊是Java語言中的一個(gè)非常重要的概念。初始化塊負(fù)責(zé)在創(chuàng)建對(duì)象時(shí)進(jìn)行一些必要的操作,例如設(shè)置對(duì)象的初始狀態(tài)、初始化成員變量等。初始化塊被分為實(shí)例初始化塊和靜態(tài)初始化塊兩種類型。本文詳細(xì)介紹了初始化的過程,需要的朋友可以參考下2023-05-05
SpringBoot加載不出來application.yml文件的解決方法
這篇文章主要介紹了SpringBoot加載不出來application.yml文件的解決方法,文中通過示例代碼講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作有一定的幫助,需要的朋友跟著小編來一起來學(xué)習(xí)吧2023-12-12

