创建 api.MatchAPI
,继承自 TextWebSocketHandler
作为处理 WebSocket
请求的入口类
ObjectMapper
,后续用来处理 JSON
数据package org.example.java_gobang.api;
import com.fasterxml.jackson.databind.ObjectMapper;
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 请求
@Component
public class MatchAPI extends TextWebSocketHandler {
// 稍后处理 JSON 会用到的对象
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
}
}
修改 config.WebSocketConfig
,把 MatchAPI
加进去
addHandler
之后,再加上一个 .addInterceptors(new HttpSessionHandshakeInterceptor())
代码HttpSession
中存放的数据(主要是 User
对象),放到 WebSocket
的 Session
中,方便后续获取到当前用户的信息在注册
websocket API
的时候,就需要把前面准备好的HttpSession
给搞过来(搞到WebSocket
的Session
中)
- 用户登录就会给
HttpSession
中保存用户的信息- 你点了一下匹配按钮,就需要告诉服务器当前是谁在点按钮
package org.example.java_gobang.config;
import org.example.java_gobang.api.MatchAPI;
import org.example.java_gobang.api.TestAPI;
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;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
/**
* 这个类是用来注册 WebSocketHandler 的配置类
* 这个类是来告诉 Spring(websocket),哪一个类是和哪一个路径相匹配的
*/
@Configuration
@EnableWebSocket // 让 Spring 框架知道这个类是用来配置 websocket 的
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private TestAPI testAPI;
@Autowired
private MatchAPI matchAPI;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
// 当我们的客户端连接到 /test 路径的时候,就会触发到当前的 TestAPI,然后调用执行里面的方法
webSocketHandlerRegistry.addHandler(testAPI, "/test");
webSocketHandlerRegistry.addHandler(matchAPI,"/findMatch")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
}
.addInterceptors(new HttpSessionHandshakeInterceptor()
这个操作来把 HttpSession
⾥的属性放到 WebSocket
的 session
中WebSocket
代码中 WebSocketSession
⾥拿到 HttpSession
中的attribute
此处我们需要能够保存和表示用户的上线状态
之所以要维护用户的在线状态,目的是为了能够在代码中比较方便的获取到某个用户当前的 websocket
会话
可以使用一个哈希表来保存当前用户的在线状态
key
就是用户 id
value
就是用户当前使用的 websocket
会话创建 game.OnlineUserManager
类,借助这个类, ⼀⽅⾯可以判定⽤⼾是否是在线, 同时也可以进⾏⽅便的获取到 Session
从⽽给客⼾端回话
websocket
连接,则将键值对加入 OnlineUserManager
中websocket
连接,则将键值对从 OnlineUserManager
中删除userId
来查询对应的会话,以便向客户端返回数据package org.example.java_gobang.game;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;
import java.util.HashMap;
@Component
public class OnlineUserManager {
// 这个哈希表就用来表示当前用户在游戏大厅的在线状态
private HashMap<Integer, WebSocketSession> gameHall = new HashMap<>();
public void enterGameHall(int userId, WebSocketSession webSocketSession) {
gameHall.put(userId, webSocketSession);
}
public void exitGameHall(int userId) {
gameHall.remove(userId);
}
public WebSocketSession getFromGameHall(int userId) {
return gameHall.get(userId);
}
}
创建 game.MatchRequest
类
package org.example.java_gobang.game;
// 这是表示一个 websocket 的匹配请求
public class MatchRequest {
private String message = "";
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
创建 game.MatchResponse
类
package org.example.java_gobang.game;
// 这是表示一个 websocket 的匹配响应
public class MatchResponse {
private boolean ok;
private String reason;
private String message;
public boolean isOk() {
return ok;
}
public void setOk(boolean ok) {
this.ok = ok;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
实现 afterConnectionEstablished
⽅法
session
对象,拿到之前登录时设置的 User
信息onlineUserManager
来管理用户的在线状态@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 玩家上线,加入到 OnlineUserManager 中
// 1. 获取到当前用户的身份信息(谁在游戏大厅中建立的连接)
// 此处的代码,之所以能够 getAttributes,全靠了在注册 WebSocket 的时候,
// 加上的 .addInterceptors(new HttpSessionHandshakeInterceptor()); // 这个逻辑就把 HttpSession 中的 Attributes 都给拿到 WebSocketSession 中了
// 在 Http 登录逻辑中,往 HttpSession 中存了 User 数据: (UserAPI)httpSession.setAttribute("user", user);
// 此时就可以在 WebSocketSession 中把之前 HttpSession 里存的 User 给拿到了
// 此处我们拿到的 user,是有可能为空的!
// 如果之前用户压根就没有通过 HTTP 来进行登录,直接就通过 /game_hall.html 这个 URL 来访问游戏大厅页面
// 此时就会出现 user 为空的情况(绕开登录界面就会为空)
try {
User user = (User) session.getAttributes().get("user");
// 2. 拿到了身份信息之后,就可以把玩家设成在线状态
onlineUserManager.enterGameHall(user.getUserId(), session);
System.out.println("玩家 " + user.getUsername() + "进入游戏大厅");
}catch (NullPointerException e) {
// 出现空指针异常,说明当前用户的身份是空的,用户未登录
// 把当前用户尚未登录这个信息给返回回去
MatchResponse response = new MatchResponse();
response.setOk(false);
response.setReason("您未登录! 不能进行后续匹配!");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
}
session.sendMessage
ObjectMapper
把 MatchResponse
对象转成 JSON
字符串TextMessage
,再进行传输TestMessage
就表示一个文本格式的 websocket
数据包@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
try {
// 玩家下线,从 OnlineUserManager 中删除
User user = (User) session.getAttributes().get("user");
onlineUserManager.exitGameHall(user.getUserId());
} catch (NullPointerException e) {
e.printStackTrace();
MatchResponse response = new MatchResponse();
response.setOk(false);
response.setReason("您未登录! 不能进行后续匹配!");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
try {
// 玩家下线,从 OnlineUserManager 中删除
User user = (User) session.getAttributes().get("user");
onlineUserManager.exitGameHall(user.getUserId());
} catch (NullPointerException e) {
e.printStackTrace();
MatchResponse response = new MatchResponse();
response.setOk(false);
response.setReason("您未登录! 不能进行后续匹配!");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
}
}