websocket模块
This commit is contained in:
parent
9db4faf412
commit
c42abc316e
|
@ -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>
|
|
@ -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("*");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
// 处理接收到的消息
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
* key2:SessionID
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user