1. 统一包名修改

This commit is contained in:
chenbowen
2025-09-22 11:55:27 +08:00
parent a001fc8f16
commit 0d46897482
2739 changed files with 512 additions and 512 deletions

View File

@@ -0,0 +1,34 @@
package com.zt.plat.framework.websocket.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
/**
* WebSocket 配置项
*
* @author xingyu4j
*/
@ConfigurationProperties("cloud.websocket")
@Data
@Validated
public class WebSocketProperties {
/**
* WebSocket 的连接路径
*/
@NotEmpty(message = "WebSocket 的连接路径不能为空")
private String path = "/ws";
/**
* 消息发送器的类型
*
* 可选值local、redis、rocketmq、kafka、rabbitmq
*/
@NotNull(message = "WebSocket 的消息发送者不能为空")
private String senderType = "local";
}

View File

@@ -0,0 +1,83 @@
package com.zt.plat.framework.websocket.core.handler;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import com.zt.plat.framework.common.util.json.JsonUtils;
import com.zt.plat.framework.tenant.core.util.TenantUtils;
import com.zt.plat.framework.websocket.core.listener.WebSocketMessageListener;
import com.zt.plat.framework.websocket.core.message.JsonWebSocketMessage;
import com.zt.plat.framework.websocket.core.util.WebSocketFrameworkUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
/**
* JSON 格式 {@link WebSocketHandler} 实现类
*
* 基于 {@link JsonWebSocketMessage#getType()} 消息类型,调度到对应的 {@link WebSocketMessageListener} 监听器。
*
* @author ZT
*/
@Slf4j
public class JsonWebSocketMessageHandler extends TextWebSocketHandler {
/**
* type 与 WebSocketMessageListener 的映射
*/
private final Map<String, WebSocketMessageListener<Object>> listeners = new HashMap<>();
@SuppressWarnings({"rawtypes", "unchecked"})
public JsonWebSocketMessageHandler(List<? extends WebSocketMessageListener> listenersList) {
listenersList.forEach((Consumer<WebSocketMessageListener>)
listener -> listeners.put(listener.getType(), listener));
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 1.1 空消息,跳过
if (message.getPayloadLength() == 0) {
return;
}
// 1.2 ping 心跳消息,直接返回 pong 消息。
if (message.getPayloadLength() == 4 && Objects.equals(message.getPayload(), "ping")) {
session.sendMessage(new TextMessage("pong"));
return;
}
// 2.1 解析消息
try {
JsonWebSocketMessage jsonMessage = JsonUtils.parseObject(message.getPayload(), JsonWebSocketMessage.class);
if (jsonMessage == null) {
log.error("[handleTextMessage][session({}) message({}) 解析为空]", session.getId(), message.getPayload());
return;
}
if (StrUtil.isEmpty(jsonMessage.getType())) {
log.error("[handleTextMessage][session({}) message({}) 类型为空]", session.getId(), message.getPayload());
return;
}
// 2.2 获得对应的 WebSocketMessageListener
WebSocketMessageListener<Object> messageListener = listeners.get(jsonMessage.getType());
if (messageListener == null) {
log.error("[handleTextMessage][session({}) message({}) 监听器为空]", session.getId(), message.getPayload());
return;
}
// 2.3 处理消息
Type type = TypeUtil.getTypeArgument(messageListener.getClass(), 0);
Object messageObj = JsonUtils.parseObject(jsonMessage.getContent(), type);
Long tenantId = WebSocketFrameworkUtils.getTenantId(session);
TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj));
} catch (Throwable ex) {
log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload());
}
}
}

View File

@@ -0,0 +1,31 @@
package com.zt.plat.framework.websocket.core.listener;
import com.zt.plat.framework.websocket.core.message.JsonWebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
/**
* WebSocket 消息监听器接口
*
* 目的:前端发送消息给后端后,处理对应 {@link #getType()} 类型的消息
*
* @param <T> 泛型,消息类型
*/
public interface WebSocketMessageListener<T> {
/**
* 处理消息
*
* @param session Session
* @param message 消息
*/
void onMessage(WebSocketSession session, T message);
/**
* 获得消息类型
*
* @see JsonWebSocketMessage#getType()
* @return 消息类型
*/
String getType();
}

View File

@@ -0,0 +1,29 @@
package com.zt.plat.framework.websocket.core.message;
import com.zt.plat.framework.websocket.core.listener.WebSocketMessageListener;
import lombok.Data;
import java.io.Serializable;
/**
* JSON 格式的 WebSocket 消息帧
*
* @author ZT
*/
@Data
public class JsonWebSocketMessage implements Serializable {
/**
* 消息类型
*
* 目的:用于分发到对应的 {@link WebSocketMessageListener} 实现类
*/
private String type;
/**
* 消息内容
*
* 要求 JSON 对象
*/
private String content;
}

View File

@@ -0,0 +1,42 @@
package com.zt.plat.framework.websocket.core.security;
import com.zt.plat.framework.security.core.LoginUser;
import com.zt.plat.framework.security.core.filter.TokenAuthenticationFilter;
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
import com.zt.plat.framework.websocket.core.util.WebSocketFrameworkUtils;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
/**
* 登录用户的 {@link HandshakeInterceptor} 实现类
*
* 流程如下:
* 1. 前端连接 websocket 时,会通过拼接 ?token={token} 到 ws:// 连接后,这样它可以被 {@link TokenAuthenticationFilter} 所认证通过
* 2. {@link LoginUserHandshakeInterceptor} 负责把 {@link LoginUser} 添加到 {@link WebSocketSession} 中
*
* @author ZT
*/
public class LoginUserHandshakeInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) {
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
if (loginUser != null) {
WebSocketFrameworkUtils.setLoginUser(loginUser, attributes);
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {
// do nothing
}
}

View File

@@ -0,0 +1,24 @@
package com.zt.plat.framework.websocket.core.security;
import com.zt.plat.framework.security.config.AuthorizeRequestsCustomizer;
import com.zt.plat.framework.websocket.config.WebSocketProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
/**
* WebSocket 的权限自定义
*
* @author ZT
*/
@RequiredArgsConstructor
public class WebSocketAuthorizeRequestsCustomizer extends AuthorizeRequestsCustomizer {
private final WebSocketProperties webSocketProperties;
@Override
public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
registry.requestMatchers(webSocketProperties.getPath()).permitAll();
}
}

View File

@@ -0,0 +1,52 @@
package com.zt.plat.framework.websocket.core.sender;
import com.zt.plat.framework.common.util.json.JsonUtils;
/**
* WebSocket 消息的发送器接口
*
* @author ZT
*/
public interface WebSocketMessageSender {
/**
* 发送消息给指定用户
*
* @param userType 用户类型
* @param userId 用户编号
* @param messageType 消息类型
* @param messageContent 消息内容JSON 格式
*/
void send(Integer userType, Long userId, String messageType, String messageContent);
/**
* 发送消息给指定用户类型
*
* @param userType 用户类型
* @param messageType 消息类型
* @param messageContent 消息内容JSON 格式
*/
void send(Integer userType, String messageType, String messageContent);
/**
* 发送消息给指定 Session
*
* @param sessionId Session 编号
* @param messageType 消息类型
* @param messageContent 消息内容JSON 格式
*/
void send(String sessionId, String messageType, String messageContent);
default void sendObject(Integer userType, Long userId, String messageType, Object messageContent) {
send(userType, userId, messageType, JsonUtils.toJsonString(messageContent));
}
default void sendObject(Integer userType, String messageType, Object messageContent) {
send(userType, messageType, JsonUtils.toJsonString(messageContent));
}
default void sendObject(String sessionId, String messageType, Object messageContent) {
send(sessionId, messageType, JsonUtils.toJsonString(messageContent));
}
}

View File

@@ -0,0 +1,35 @@
package com.zt.plat.framework.websocket.core.sender.kafka;
import lombok.Data;
/**
* Kafka 广播 WebSocket 的消息
*
* @author ZT
*/
@Data
public class KafkaWebSocketMessage {
/**
* Session 编号
*/
private String sessionId;
/**
* 用户类型
*/
private Integer userType;
/**
* 用户编号
*/
private Long userId;
/**
* 消息类型
*/
private String messageType;
/**
* 消息内容
*/
private String messageContent;
}

Some files were not shown because too many files have changed in this diff Show More