websocket模块

This commit is contained in:
阿沐 2024-12-19 19:04:30 +08:00
parent 9db4faf412
commit c42abc316e
10 changed files with 389 additions and 0 deletions

View File

@ -0,0 +1,24 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>net.maku</groupId>
<artifactId>maku-boot-module</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>maku-module-websocket</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>net.maku</groupId>
<artifactId>maku-framework</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,30 @@
package net.maku.websocket.config;
import lombok.AllArgsConstructor;
import net.maku.websocket.handler.MessageWebSocketHandler;
import net.maku.websocket.handler.UserHandshakeInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* WebSocket配置
*
* @author 阿沐 babamu@126.com
* <a href="https://maku.net">MAKU</a>
*/
@Configuration
@EnableWebSocket
@AllArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {
private final MessageWebSocketHandler handler;
private final UserHandshakeInterceptor interceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(handler, "/ws")
.addInterceptors(interceptor).setAllowedOriginPatterns("*");
}
}

View File

@ -0,0 +1,52 @@
package net.maku.websocket.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import net.maku.framework.common.utils.Result;
import net.maku.framework.security.user.SecurityUser;
import net.maku.framework.security.user.UserDetail;
import net.maku.websocket.message.JsonDataMessage;
import net.maku.websocket.sender.WebSocketMessageSender;
import net.maku.websocket.vo.MessageVO;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
/**
* 测试 发送消息
*
* @author 阿沐 babamu@126.com
* <a href="https://maku.net">MAKU</a>
*/
@Tag(name = "WebSocket发送消息")
@AllArgsConstructor
@RestController
@RequestMapping("ws/message")
public class SendMessageController {
private final WebSocketMessageSender webSocketMessageSender;
@PostMapping("send")
@Operation(summary = "发送消息")
public Result<String> send(Long userId, String content) {
// 获取当前用户信息
UserDetail user = SecurityUser.getUser();
// 封装消息内容
MessageVO messageVO = new MessageVO();
messageVO.setName(user.getRealName());
messageVO.setAvatar(user.getAvatar());
messageVO.setContent(content);
messageVO.setSendTime(LocalDateTime.now());
JsonDataMessage<MessageVO> message = new JsonDataMessage<>();
message.setData(messageVO);
// 发送消息
webSocketMessageSender.send(userId, message);
return Result.ok();
}
}

View File

@ -0,0 +1,44 @@
package net.maku.websocket.handler;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.maku.websocket.session.WebSocketSessionManager;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/**
* WebSocket 消息处理器
*
* @author 阿沐 babamu@126.com
* <a href="https://maku.net">MAKU</a>
*/
@Slf4j
@Component
@AllArgsConstructor
public class MessageWebSocketHandler extends TextWebSocketHandler {
private final WebSocketSessionManager webSocketSessionManager;
@Override
public void afterConnectionEstablished(WebSocketSession session) {
// 新增session
webSocketSessionManager.addSession(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
// 移除session
webSocketSessionManager.removeSession(session);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
String payload = message.getPayload();
log.info("Received message: {}", payload);
// 处理接收到的消息
}
}

View File

@ -0,0 +1,35 @@
package net.maku.websocket.handler;
import net.maku.framework.security.user.SecurityUser;
import net.maku.websocket.util.SessionUserUtil;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
/**
* webSocket 握手拦截器
*
* @author 阿沐 babamu@126.com
* <a href="https://maku.net">MAKU</a>
*/
@Component
public class UserHandshakeInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// 当前连接的用户
SessionUserUtil.setUser(SecurityUser.getUser(), attributes);
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
}
}

View File

@ -0,0 +1,22 @@
package net.maku.websocket.message;
import lombok.Data;
/**
* websocket json 消息
*
* @author 阿沐 babamu@126.com
* <a href="https://maku.net">MAKU</a>
*/
@Data
public class JsonDataMessage<T> {
/**
* 消息类型 预留
*/
private String type = "default";
/**
* 消息数据
*/
private T data;
}

View File

@ -0,0 +1,59 @@
package net.maku.websocket.sender;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.maku.framework.common.utils.JsonUtils;
import net.maku.websocket.message.JsonDataMessage;
import net.maku.websocket.session.WebSocketSessionManager;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.util.List;
/**
* WebSocket 消息发送
*
* @author 阿沐 babamu@126.com
* <a href="https://maku.net">MAKU</a>
*/
@Slf4j
@Component
@AllArgsConstructor
public class WebSocketMessageSender {
private final WebSocketSessionManager webSocketSessionManager;
/**
* 发送信息
*
* @param userId 用户ID
* @param message 消息内容
*/
public void send(Long userId, JsonDataMessage<?> message) {
webSocketSessionManager.getSessionList(userId).forEach(session -> send(session, message));
}
/**
* 发送信息
*
* @param userIdList 用户ID列表
* @param message 消息内容
*/
public void send(List<Long> userIdList, JsonDataMessage<?> message) {
userIdList.forEach(userId -> send(userId, message));
}
/**
* 发送信息
*
* @param session WebSocketSession
* @param message 消息内容
*/
public void send(WebSocketSession session, JsonDataMessage<?> message) {
try {
session.sendMessage(new TextMessage(JsonUtils.toJsonString(message)));
} catch (Exception e) {
log.error("send websocket message error{}", e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,62 @@
package net.maku.websocket.session;
import cn.hutool.core.collection.CollUtil;
import net.maku.framework.security.user.UserDetail;
import net.maku.websocket.util.SessionUserUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* WebSocket 会话管理器
*
* @author 阿沐 babamu@126.com
* <a href="https://maku.net">MAKU</a>
*/
@Component
public class WebSocketSessionManager {
/**
* 用户 WebSocket 会话
* key1用户ID
* key2SessionID
*/
private final ConcurrentMap<Long, ConcurrentMap<String, WebSocketSession>> userSessions = new ConcurrentHashMap<>();
public void addSession(WebSocketSession session) {
// 当前连接的用户
UserDetail user = SessionUserUtil.getUser(session);
if (user == null) {
return;
}
userSessions.computeIfAbsent(user.getId(), k -> new ConcurrentHashMap<>())
.putIfAbsent(session.getId(), session);
}
public void removeSession(WebSocketSession session) {
// 当前连接的用户
UserDetail user = SessionUserUtil.getUser(session);
if (user == null) {
return;
}
userSessions.computeIfPresent(user.getId(), (k, sessionMap) -> {
sessionMap.remove(session.getId());
return sessionMap.isEmpty() ? null : sessionMap;
});
}
public List<WebSocketSession> getSessionList(Long userId) {
ConcurrentMap<String, WebSocketSession> sessionMap = userSessions.get(userId);
if (CollUtil.isEmpty(sessionMap)) {
return new ArrayList<>();
}
return new ArrayList<>(sessionMap.values());
}
}

View File

@ -0,0 +1,28 @@
package net.maku.websocket.util;
import net.maku.framework.security.user.UserDetail;
import org.springframework.web.socket.WebSocketSession;
import java.util.Map;
public class SessionUserUtil {
private static final String SESSION_USER = "SESSION_USER";
/**
* 设置用户
*
* @param user 用户
* @param attributes attributes
*/
public static void setUser(UserDetail user, Map<String, Object> attributes) {
attributes.put(SESSION_USER, user);
}
/**
* 获取用户
*/
public static UserDetail getUser(WebSocketSession session) {
return (UserDetail) session.getAttributes().get(SESSION_USER);
}
}

View File

@ -0,0 +1,33 @@
package net.maku.websocket.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Data;
import net.maku.framework.common.utils.DateUtils;
import java.time.LocalDateTime;
/**
* 发送消息的对象
*
* @author 阿沐 babamu@126.com
* <a href="https://maku.net">MAKU</a>
*/
@Data
@Tag(name = "发送消息的对象")
public class MessageVO {
@Schema(description = "用户名")
private String name;
@Schema(description = "用户头像")
private String avatar;
@Schema(description = "消息内容")
private String content;
@Schema(description = "发送时间")
@JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
private LocalDateTime sendTime;
}