增加用户端websocket连接端点
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,3 +31,4 @@ build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
/log/
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package com.onekeycall.videotablet.config;
|
||||
|
||||
import com.onekeycall.videotablet.handler.CustomWebSocketHandler;
|
||||
import com.onekeycall.videotablet.interceptor.AuthHandshakeInterceptor;
|
||||
import com.onekeycall.videotablet.handler.WebSocketTabletHandler;
|
||||
import com.onekeycall.videotablet.handler.WebSocketUserHandler;
|
||||
import com.onekeycall.videotablet.interceptor.HandshakeTabletInterceptor;
|
||||
import com.onekeycall.videotablet.interceptor.HandshakeUserInterceptor;
|
||||
import com.onekeycall.videotablet.utils.JwtUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
@@ -17,12 +18,17 @@ public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
private final CustomWebSocketHandler webSocketHandler;
|
||||
private final WebSocketTabletHandler webSocketTabletHandler;
|
||||
private final WebSocketUserHandler webSocketUserHandler;
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(webSocketHandler, "/ws/tablet_ws") // 客户端连接端点
|
||||
registry.addHandler(webSocketTabletHandler, "/ws/tablet_ws") // 客户端连接端点
|
||||
.setAllowedOrigins("*") // 允许跨域
|
||||
.addInterceptors(new AuthHandshakeInterceptor(jwtUtil)); // 握手拦截器(如JWT校验)
|
||||
.addInterceptors(new HandshakeTabletInterceptor(jwtUtil)); // 握手拦截器(如JWT校验)
|
||||
|
||||
registry.addHandler(webSocketUserHandler, "/ws/user_ws") // 客户端连接端点
|
||||
.setAllowedOrigins("*") // 允许跨域
|
||||
.addInterceptors(new HandshakeUserInterceptor(jwtUtil)); // 握手拦截器(如JWT校验)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.onekeycall.videotablet.controller;
|
||||
|
||||
import com.onekeycall.videotablet.handler.CustomWebSocketHandler;
|
||||
import com.onekeycall.videotablet.handler.WebSocketTabletHandler;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -11,9 +11,9 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RequestMapping("/api/ws")
|
||||
public class WebSocketController {
|
||||
|
||||
private final CustomWebSocketHandler webSocketHandler;
|
||||
private final WebSocketTabletHandler webSocketHandler;
|
||||
|
||||
public WebSocketController(CustomWebSocketHandler webSocketHandler) {
|
||||
public WebSocketController(WebSocketTabletHandler webSocketHandler) {
|
||||
this.webSocketHandler = webSocketHandler;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,26 +2,22 @@ package com.onekeycall.videotablet.handler;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
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;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CustomWebSocketHandler extends TextWebSocketHandler {
|
||||
public class WebSocketTabletHandler extends TextWebSocketHandler {
|
||||
|
||||
// 存储活跃会话(线程安全)
|
||||
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) throws IOException {
|
||||
// URI uri = session.getUri();
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.onekeycall.videotablet.handler;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class WebSocketUserHandler extends TextWebSocketHandler {
|
||||
|
||||
// 存储活跃会话(线程安全)
|
||||
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) throws IOException {
|
||||
// URI uri = session.getUri();
|
||||
// if (uri == null) {
|
||||
// return;
|
||||
// }
|
||||
// MultiValueMap<String, String> queryParams = UriComponentsBuilder.fromUri(uri).build().getQueryParams();
|
||||
// String sn = queryParams.getFirst("sn");
|
||||
// log.info("sn = " + sn);
|
||||
// if (sn == null) {
|
||||
// session.close(CloseStatus.BAD_DATA.withReason("Missing device SN"));
|
||||
// return;
|
||||
// }
|
||||
String sessionId = session.getId();
|
||||
String sn = (String) session.getAttributes().get("sn");
|
||||
sessions.put(sn, session);
|
||||
log.info("✅ 连接建立: ID={},sn={},当前连接数: {}", sessionId, sn, sessions.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
|
||||
String payload = message.getPayload();
|
||||
log.info("📩 收到消息: {}", payload);
|
||||
|
||||
// 示例:回复客户端
|
||||
String reply = "服务器已接收: " + payload;
|
||||
try {
|
||||
session.sendMessage(new TextMessage(reply));
|
||||
} catch (IOException e) {
|
||||
log.error("回复失败: ID={}", session.getId(), e);
|
||||
}
|
||||
|
||||
// 可选:广播给所有客户端
|
||||
// broadcast("用户" + sessionId + "说: " + payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
String sessionId = session.getId();
|
||||
String sn = (String) session.getAttributes().get("sn");
|
||||
sessions.remove(sn);
|
||||
log.info("❌ 连接关闭: ID={},sn={},Code: {},原因: {},当前连接数: {}", sessionId, sn, status.getCode(), status.getReason(), sessions.size());
|
||||
|
||||
}
|
||||
|
||||
// 广播消息给所有客户端
|
||||
public void broadcast(String message) {
|
||||
sessions.values().stream()
|
||||
.filter(WebSocketSession::isOpen)
|
||||
.forEach(session -> {
|
||||
try {
|
||||
session.sendMessage(new TextMessage(message));
|
||||
} catch (IOException e) {
|
||||
log.error("广播失败: ID={}", session.getId(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,11 @@ import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class AuthHandshakeInterceptor implements HandshakeInterceptor {
|
||||
public class HandshakeTabletInterceptor implements HandshakeInterceptor {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
public AuthHandshakeInterceptor(JwtUtil jwtUtil) {
|
||||
public HandshakeTabletInterceptor(JwtUtil jwtUtil) {
|
||||
this.jwtUtil = jwtUtil;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.onekeycall.videotablet.interceptor;
|
||||
|
||||
import com.onekeycall.videotablet.utils.JwtUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class HandshakeUserInterceptor implements HandshakeInterceptor {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
public HandshakeUserInterceptor(JwtUtil jwtUtil) {
|
||||
this.jwtUtil = jwtUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
String authHeader = request.getHeaders().getFirst("Authorization");
|
||||
String deviceId = request.getHeaders().getFirst("Device-ID");
|
||||
String userId = request.getHeaders().getFirst("User-ID");
|
||||
|
||||
if (request instanceof ServletServerHttpRequest) {
|
||||
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
|
||||
HttpServletRequest httpRequest = servletRequest.getServletRequest();
|
||||
String sn = httpRequest.getParameter("sn");
|
||||
attributes.put("sn", sn);
|
||||
log.info("Intercepted - sn: " + sn);
|
||||
}
|
||||
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ") || userId == null) {
|
||||
return false;
|
||||
}
|
||||
String token = authHeader.substring(7); // 去掉 "Bearer " 前缀
|
||||
return jwtUtil.validateAccessToken(userId, token, deviceId); // 自定义校验逻辑
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,9 @@ jwt.refresh-expire=2592000000
|
||||
jwt.tablet.secret='Your256BitSecretKeyMustBeAtLeast32BytesLong!'
|
||||
|
||||
# 指定日志文件名(项目根目录生成)
|
||||
logging.file.name=app.log
|
||||
logging.file.name=log/onekeycall_video_tablet.log
|
||||
# 或指定日志目录(目录下生成 spring.log)
|
||||
logging.file.path=/var/log/myapp
|
||||
logging.file.path=/var/log/onekeycall
|
||||
|
||||
# 设置日志级别
|
||||
logging.level.root=INFO
|
||||
|
||||
@@ -39,9 +39,9 @@ jwt.refresh-expire=2592000000
|
||||
jwt.tablet.secret='Your256BitSecretKeyMustBeAtLeast32BytesLong!'
|
||||
|
||||
# 指定日志文件名(项目根目录生成)
|
||||
logging.file.name=app.log
|
||||
logging.file.name=log/onekeycall_video_tablet.log
|
||||
# 或指定日志目录(目录下生成 spring.log)
|
||||
logging.file.path=/var/log/myapp
|
||||
logging.file.path=/var/log/onekeycall
|
||||
|
||||
# 设置日志级别
|
||||
logging.level.root=INFO
|
||||
|
||||
@@ -39,9 +39,9 @@ jwt.refresh-expire=2592000000
|
||||
jwt.tablet.secret='Your256BitSecretKeyMustBeAtLeast32BytesLong!'
|
||||
|
||||
# 指定日志文件名(项目根目录生成)
|
||||
logging.file.name=app.log
|
||||
logging.file.name=log/onekeycall_video_tablet.log
|
||||
# 或指定日志目录(目录下生成 spring.log)
|
||||
logging.file.path=/var/log/myapp
|
||||
logging.file.path=/var/log/onekeycall
|
||||
|
||||
# 设置日志级别
|
||||
logging.level.root=INFO
|
||||
|
||||
Reference in New Issue
Block a user