From 73d061979bdea20f5df70fbb7dcaf5546c6737b7 Mon Sep 17 00:00:00 2001 From: wzp Date: Tue, 24 Dec 2024 18:34:29 +0800 Subject: [PATCH] feat: move the old chess controller to this branch --- build.gradle.kts | 3 +- .../java/org/blue/club/ClubApplication.java | 2 + .../configuration/WebSocketConfiguration.java | 13 + .../blue/club/controller/ChessController.java | 275 ++++++++++++++++++ .../blue/club/entities/dto/chess/Room.java | 234 +++++++++++++++ .../dto/chess/packet/BaseWebSocketPacket.java | 32 ++ .../dto/chess/packet/ErrorPacket.java | 4 + .../dto/chess/packet/HasPlayerWinPacket.java | 6 + .../chess/packet/PlayerJoinRoomPacket.java | 7 + .../dto/chess/packet/PlayerLeavePacket.java | 4 + .../dto/chess/packet/PlayerLosePacket.java | 4 + .../packet/PlayerSideAllocationPacket.java | 4 + .../dto/chess/packet/PlayerWinPacket.java | 4 + .../dto/chess/packet/RoomCreatedPacket.java | 6 + .../dto/chess/packet/RoomInfoPacket.java | 10 + .../dto/chess/packet/RoomListPacket.java | 8 + .../dto/chess/packet/UserInfoPacket.java | 6 + .../packet/request/CreateRoomRequest.java | 4 + .../request/PlaceChessPieceRequest.java | 4 + .../packet/request/PlayerJoinRequest.java | 6 + .../packet/request/ResetRoomRequest.java | 4 + .../chess/packet/request/RoomListRequest.java | 4 + .../schedule/RemoveExpireDataSchedule.java | 56 ++++ .../org/blue/club/utils/WebSocketUtils.java | 34 +++ 24 files changed, 733 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/blue/club/configuration/WebSocketConfiguration.java create mode 100644 src/main/java/org/blue/club/controller/ChessController.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/Room.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/BaseWebSocketPacket.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/ErrorPacket.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/HasPlayerWinPacket.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/PlayerJoinRoomPacket.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/PlayerLeavePacket.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/PlayerLosePacket.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/PlayerSideAllocationPacket.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/PlayerWinPacket.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/RoomCreatedPacket.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/RoomInfoPacket.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/RoomListPacket.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/UserInfoPacket.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/request/CreateRoomRequest.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/request/PlaceChessPieceRequest.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/request/PlayerJoinRequest.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/request/ResetRoomRequest.java create mode 100644 src/main/java/org/blue/club/entities/dto/chess/packet/request/RoomListRequest.java create mode 100644 src/main/java/org/blue/club/schedule/RemoveExpireDataSchedule.java create mode 100644 src/main/java/org/blue/club/utils/WebSocketUtils.java diff --git a/build.gradle.kts b/build.gradle.kts index 7fa1952..08a45c5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,7 +28,8 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") - implementation("org.mmga:make-minecraft-great-again-spring-boot-starter:0.0.5-20241224.081553-6") + implementation("org.springframework.boot:spring-boot-starter-websocket") + implementation("org.mmga:make-minecraft-great-again-spring-boot-starter:0.0.5-20241224.092835-8") implementation("com.mybatis-flex:mybatis-flex-spring-boot-starter:1.10.2") annotationProcessor("com.mybatis-flex:mybatis-flex-processor:1.10.2") // https://mvnrepository.com/artifact/commons-codec/commons-codec diff --git a/src/main/java/org/blue/club/ClubApplication.java b/src/main/java/org/blue/club/ClubApplication.java index dce0250..10be63c 100644 --- a/src/main/java/org/blue/club/ClubApplication.java +++ b/src/main/java/org/blue/club/ClubApplication.java @@ -5,8 +5,10 @@ import com.mybatisflex.core.audit.ConsoleMessageCollector; import com.mybatisflex.core.audit.MessageCollector; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.socket.config.annotation.EnableWebSocket; @SpringBootApplication +@EnableWebSocket public class ClubApplication { public static void main(String[] args) { diff --git a/src/main/java/org/blue/club/configuration/WebSocketConfiguration.java b/src/main/java/org/blue/club/configuration/WebSocketConfiguration.java new file mode 100644 index 0000000..4b437de --- /dev/null +++ b/src/main/java/org/blue/club/configuration/WebSocketConfiguration.java @@ -0,0 +1,13 @@ +package org.blue.club.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +@Configuration +public class WebSocketConfiguration { + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } +} diff --git a/src/main/java/org/blue/club/controller/ChessController.java b/src/main/java/org/blue/club/controller/ChessController.java new file mode 100644 index 0000000..559806e --- /dev/null +++ b/src/main/java/org/blue/club/controller/ChessController.java @@ -0,0 +1,275 @@ +package org.blue.club.controller; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONException; +import com.alibaba.fastjson2.JSONObject; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Nullable; +import jakarta.websocket.*; +import jakarta.websocket.server.PathParam; +import jakarta.websocket.server.ServerEndpoint; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.blue.club.dao.UserDao; +import org.blue.club.entities.dto.chess.Room; +import org.blue.club.entities.dto.chess.packet.*; +import org.blue.club.entities.dto.chess.packet.request.*; +import org.blue.club.entities.dto.user.User; +import org.blue.club.utils.WebSocketUtils; +import org.mmga.spring.boot.starter.componet.JwtUtils; +import org.mmga.spring.boot.starter.utils.VoUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +@Slf4j +@Tag(name = "五子棋", description = "五子棋相关websocket接口") +@ServerEndpoint("/chess/{token}") +@Component +@NoArgsConstructor +public class ChessController { + private static final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap users = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap rooms = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap pingPong = new ConcurrentHashMap<>(); + private static JwtUtils jwtUtils; + private static VoUtils voUtils; + private static UserDao userDao; + + @Autowired + public void setVoUtils(VoUtils voUtils) { + ChessController.voUtils = voUtils; + } + + @Autowired + public void setUserDao(UserDao userDao) { + ChessController.userDao = userDao; + } + + @Autowired + public void setJwtUtils(JwtUtils jwtUtils) { + ChessController.jwtUtils = jwtUtils; + } + + private final Queue messageQueue = new ConcurrentLinkedQueue<>(); + private boolean isLogin = false; + + @SneakyThrows + @OnOpen + public void onOpen(Session session, @PathParam("token") String token) { + isLogin = false; + Optional i = jwtUtils.verifyToken(token); + if (i.isEmpty()) { + WebSocketUtils.sendPacket(new ErrorPacket("token验证失败!"), session); + log.info("closed by token verifier"); + session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "token验证失败!")); + messageQueue.clear(); + return; + } + String sessionId = session.getId(); + User user = voUtils.vo2DtoSafe(userDao.selectOneWithRelationsById(i.get()), User.class); + WebSocketUtils.sendPacket(new UserInfoPacket(user), session); + String existsUser = null; + long id = user.getId(); + for (Map.Entry entry : users.entrySet()) { + User value = entry.getValue(); + if (value.getId() == id) { + existsUser = entry.getKey(); + break; + } + } + users.put(sessionId, user); + sessions.put(sessionId, session); + isLogin = true; + if (existsUser != null) { + users.remove(existsUser); + Room roomByPlayer = getRoomByPlayer(existsUser); + if (roomByPlayer != null) { + roomByPlayer.replace(existsUser, session); + WebSocketUtils.sendPacket(new PlayerSideAllocationPacket(roomByPlayer.isPlayerWhite(existsUser)), session); + WebSocketUtils.sendPacket(roomByPlayer.getRoomInfo(users), session); + } + Session remove = sessions.remove(existsUser); + log.info("session closed by another session"); + remove.close(new CloseReason(CloseReason.CloseCodes.NOT_CONSISTENT, "你的账号已在别处登录")); + } + for (String s : messageQueue) { + this.onMessage(s, session); + } + } + + + @OnMessage + public void onPong(PongMessage pongMessage, Session session) { + ByteBuffer applicationData = pongMessage.getApplicationData(); + ByteBuf byteBuf = Unpooled.copiedBuffer(applicationData); + long l = System.currentTimeMillis() - byteBuf.readLong(); + log.info("client sent pong response, latency is {}ms", l); + pingPong.put(session, true); + } + + @OnMessage + public void onMessage(String message, Session session) { + if (!isLogin) { + messageQueue.add(message); + return; + } + try { + JSONObject jsonObject = JSON.parseObject(message); + String packetName = jsonObject.getString("name"); + if (packetName == null) { + WebSocketUtils.sendPacket(new ErrorPacket("包内缺少name字段!"), session); + } + Object response = handleMessage(jsonObject, packetName, session); + if (response != null) { + WebSocketUtils.sendPacket(response, packetName, session); + } + } catch (JSONException e) { + WebSocketUtils.sendPacket(new ErrorPacket("JSON格式错误"), session); + } + + } + + @Nullable + public Object handleMessage(JSONObject object, String packetName, Session session) { + if (packetName == null) return new ErrorPacket("错误的请求类型"); + try { + Class packetType = Class.forName("org.blue.club.entities.dto.chess.packet.request." + packetName + "Request"); + JSONObject payloadJson = object.getJSONObject("payload"); + if (payloadJson == null) { + Field[] declaredFields = packetType.getDeclaredFields(); + if (declaredFields.length == 0) { + payloadJson = new JSONObject(); + } + } + if (payloadJson == null) return new ErrorPacket("请求类型参数错误!"); + Object payload; + try { + payload = payloadJson.to(packetType); + } catch (Exception e) { + return new ErrorPacket("请求payload解析失败!"); + } + if (payload == null) return null; + return handlePacket(payload, session); + } catch (ClassNotFoundException e) { + return new ErrorPacket("错误的请求类型"); + } + } + + public Object handlePacket(Object payload, Session session) { + String sessionId = session.getId(); + if (payload instanceof PlayerJoinRequest joinRequest) { + Room roomByPlayer = getRoomByPlayer(sessionId); + if (roomByPlayer != null) return new ErrorPacket("你已经加入过一个房间了"); + Room room = rooms.get(joinRequest.roomId()); + if (room == null) return new ErrorPacket("未知的房间"); + Optional join = room.join(session, users.get(sessionId)); + if (join.isPresent()) return new ErrorPacket(join.get()); + room.broadcast(room.getRoomInfo(users)); + } + if (payload instanceof RoomListRequest) { + Collection values = rooms.values(); + return new RoomListPacket(values); + } + if (payload instanceof CreateRoomRequest) { + Room room = new Room(); + UUID roomId = room.getId(); + rooms.put(roomId, room); + return new RoomCreatedPacket(roomId); + } + if (payload instanceof PlaceChessPieceRequest placeChessPieceRequest) { + int x = placeChessPieceRequest.x(); + int y = placeChessPieceRequest.y(); + Room roomByPlayer = getRoomByPlayer(sessionId); + if (roomByPlayer == null) return new ErrorPacket("你还没加入房间呢"); + Optional s = roomByPlayer.downPiece(x, y, sessionId); + if (s.isPresent()) return new ErrorPacket(s.get()); + roomByPlayer.broadcast(roomByPlayer.getRoomInfo(users)); + Optional winInfo = roomByPlayer.isWin(); + winInfo.ifPresent(info -> { + roomByPlayer.broadcast(new HasPlayerWinPacket(info)); + if (info.isWhite()) { + WebSocketUtils.sendPacketIfPossible(new PlayerWinPacket(), roomByPlayer.getWhiteSession()); + WebSocketUtils.sendPacketIfPossible(new PlayerLosePacket(), roomByPlayer.getBlackSession()); + } else { + WebSocketUtils.sendPacketIfPossible(new PlayerWinPacket(), roomByPlayer.getBlackSession()); + WebSocketUtils.sendPacketIfPossible(new PlayerLosePacket(), roomByPlayer.getWhiteSession()); + } + roomByPlayer.setState(Room.RoomState.FINISHED); + roomByPlayer.broadcast(roomByPlayer.getRoomInfo(users)); + }); + } + if (payload instanceof ResetRoomRequest) { + Room roomByPlayer = getRoomByPlayer(sessionId); + if (roomByPlayer == null) return new ErrorPacket("你还没加入房间呢"); + roomByPlayer.requestRestart(session); + roomByPlayer.broadcast(roomByPlayer.getRoomInfo(users)); + } + return null; + } + + @Nullable + private static Room getRoomByPlayer(String id) { + for (Room value : rooms.values()) { + if (value.isIn(id)) return value; + } + return null; + } + + public static void staticOnClose(Session session) { + String sessionId = session.getId(); + Room roomByPlayer = getRoomByPlayer(sessionId); + if (roomByPlayer != null) { + roomByPlayer.leave(session); + roomByPlayer.broadcast(roomByPlayer.getRoomInfo(users)); + } + log.info("session closed {}", sessionId); + sessions.remove(sessionId); + users.remove(sessionId); + pingPong.remove(session); + } + + @OnClose + public void onClose(Session session) { + staticOnClose(session); + } + + + @SneakyThrows + @Scheduled(fixedDelay = 10000) + public static void connectionManager() { + for (Session value : sessions.values()) { + if (!pingPong.getOrDefault(value, true)) { + log.info("closed by connectionManager"); + if (value.isOpen()) { + value.close(new CloseReason(CloseReason.CloseCodes.CLOSED_ABNORMALLY, "链接延迟过高")); + continue; + } + staticOnClose(value); + continue; + } + pingPong.put(value, false); + log.info("send ping to session {}", value.getId()); + value.getAsyncRemote().sendPing(Unpooled.buffer().writeLong(System.currentTimeMillis()).nioBuffer()); + } + Instant instant = new Date().toInstant(); + Instant before10Min = instant.plus(-10, ChronoUnit.MINUTES); + rooms.entrySet().stream() + .filter(e -> e.getValue().getState().equals(Room.RoomState.CREATED)) + .filter(e -> e.getValue().getLastStateChange().toInstant().isBefore(before10Min)) + .map(Map.Entry::getKey) + .forEach(rooms::remove); + rooms.values().forEach(Room::tryClean); + } +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/Room.java b/src/main/java/org/blue/club/entities/dto/chess/Room.java new file mode 100644 index 0000000..1175009 --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/Room.java @@ -0,0 +1,234 @@ +package org.blue.club.entities.dto.chess; + + +import com.alibaba.fastjson2.annotation.JSONField; +import jakarta.websocket.Session; +import lombok.AccessLevel; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.blue.club.entities.dto.chess.packet.PlayerJoinRoomPacket; +import org.blue.club.entities.dto.chess.packet.PlayerLeavePacket; +import org.blue.club.entities.dto.chess.packet.PlayerSideAllocationPacket; +import org.blue.club.entities.dto.chess.packet.RoomInfoPacket; +import org.blue.club.entities.dto.user.User; +import org.blue.club.utils.WebSocketUtils; + +import java.util.*; + +@Data +public class Room { + private UUID id; + private RoomState state; + @JSONField(serialize = false, deserialize = false) + private Session whiteSession; + @JSONField(serialize = false, deserialize = false) + private Session blackSession; + @JSONField(serialize = false, deserialize = false) + private byte[][] pieces; + private boolean isWhiteAcceptRestart; + private boolean isBlackAcceptRestart; + private boolean canWhiteDown; + @Setter(value = AccessLevel.PRIVATE) + private Date lastStateChange; + + public Room() { + this.id = UUID.randomUUID(); + this.state = RoomState.CREATED; + this.lastStateChange = new Date(); + resetPieces(); + } + + public void setState(RoomState state) { + if (state == this.state) return; + this.state = state; + this.lastStateChange = new Date(); + } + + public void resetPieces() { + this.canWhiteDown = false; + this.pieces = new byte[16][16]; + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + this.pieces[i][j] = -1; + } + } + isWhiteAcceptRestart = false; + isBlackAcceptRestart = false; + } + + public boolean isFull() { + return Objects.nonNull(this.whiteSession) && Objects.nonNull(this.blackSession); + } + + public Optional join(Session session, User user) { + if (this.getState() == RoomState.GAMING) { + return Optional.of("你所加入的房间正在游戏!"); + } + if (this.isFull()) { + return Optional.of("所加入的房间已满"); + } + if (Objects.nonNull(this.whiteSession)) { + WebSocketUtils.sendPacket(new PlayerSideAllocationPacket(false), session); + WebSocketUtils.sendPacketIfPossible(new PlayerJoinRoomPacket(user, this), whiteSession); + this.blackSession = session; + } else { + WebSocketUtils.sendPacket(new PlayerSideAllocationPacket(true), session); + WebSocketUtils.sendPacketIfPossible(new PlayerJoinRoomPacket(user, this), blackSession); + this.whiteSession = session; + } + if (this.isFull()) { + this.setState(RoomState.GAMING); + } else { + this.setState(RoomState.WAITING); + } + return Optional.empty(); + } + + public void leave(Session session) { + String sessionId = session.getId(); + if (Objects.nonNull(this.blackSession) && this.blackSession.getId().equals(sessionId)) { + WebSocketUtils.sendPacketIfPossible(new PlayerLeavePacket(sessionId), whiteSession); + this.blackSession = null; + this.setState(RoomState.WAITING); + } + if (Objects.nonNull(this.whiteSession) && this.whiteSession.getId().equals(sessionId)) { + WebSocketUtils.sendPacketIfPossible(new PlayerLeavePacket(sessionId), blackSession); + this.whiteSession = null; + this.setState(RoomState.WAITING); + } + if (Objects.isNull(this.blackSession) && Objects.isNull(this.whiteSession)) { + this.setState(RoomState.CREATED); + } + } + + public RoomInfoPacket getRoomInfo(Map users) { + Session whiteSession = this.getWhiteSession(); + Session blackSession = this.getBlackSession(); + User whiteUser = whiteSession != null ? users.get(whiteSession.getId()) : null; + User blackUser = blackSession != null ? users.get(blackSession.getId()) : null; + return new RoomInfoPacket(this.id, whiteUser, blackUser, this.state, pieces, this.isWhiteAcceptRestart, this.isBlackAcceptRestart, this.canWhiteDown); + } + + public void broadcast(Object packet) { + if (packet == null) return; + WebSocketUtils.sendPacketIfPossible(packet, whiteSession); + WebSocketUtils.sendPacketIfPossible(packet, blackSession); + } + + public boolean isIn(String sessionId) { + return (Objects.nonNull(whiteSession) && whiteSession.getId().equals(sessionId)) || (Objects.nonNull(blackSession) && blackSession.getId().equals(sessionId)); + } + + public Optional downPiece(int x, int y, String sessionId) { + if (!this.getState().equals(RoomState.GAMING)) return Optional.of("游戏暂未开始!"); + byte originalValue = this.pieces[y][x]; + if (originalValue != -1) return Optional.of("此位置已落子!"); + if (whiteSession.getId().equals(sessionId)) { + if (!canWhiteDown) return Optional.of("请等待对手落子"); + canWhiteDown = false; + this.pieces[y][x] = 1; + } else { + if (canWhiteDown) return Optional.of("请等待对手落子"); + canWhiteDown = true; + this.pieces[y][x] = 0; + } + return Optional.empty(); + } + + public Optional isWin() { + for (int y = 0; y < 16; y++) { + for (int x = 0; x < 16; x++) { + byte startPointType = this.pieces[y][x]; + if (startPointType == -1) continue; + for (Faces value : Faces.values()) { + boolean facePossible = true; + for (int i = 1; i < 5; i++) { + int totalXDelta = value.xDelta * i; + int totalYDelta = value.yDelta * i; + int fullyX = x + totalXDelta; + int fullyY = y + totalYDelta; + if (fullyX < 0 || fullyX > 15 || fullyY < 0 || fullyY > 15) { + facePossible = false; + break; + } + if (this.pieces[fullyY][fullyX] != startPointType) { + facePossible = false; + break; + } + } + if (!facePossible) continue; + return Optional.of(new WinInfo(value, x, y, startPointType == 1)); + } + } + } + return Optional.empty(); + } + + public void requestRestart(Session session) { + String sessionId = session.getId(); + if (whiteSession == null || sessionId.equals(whiteSession.getId())) { + isWhiteAcceptRestart = true; + } + if (blackSession == null || sessionId.equals(blackSession.getId())) { + isBlackAcceptRestart = true; + } + if (isBlackAcceptRestart && isWhiteAcceptRestart) { + this.resetPieces(); + if (whiteSession == null || blackSession == null) { + this.setState(RoomState.WAITING); + return; + } + this.setState(RoomState.GAMING); + } + } + + public void replace(String existsUser, Session session) { + if (Objects.nonNull(this.whiteSession) && whiteSession.getId().equals(existsUser)) { + whiteSession = session; + } + if (Objects.nonNull(this.blackSession) && blackSession.getId().equals(existsUser)) { + blackSession = session; + } + } + + public boolean isPlayerWhite(String sessionId) { + return sessionId.equals(whiteSession.getId()); + } + + public void tryClean() { + if (whiteSession != null) { + if (!whiteSession.isOpen()) { + this.leave(whiteSession); + } + } + if (blackSession != null) { + if (!blackSession.isOpen()) { + this.leave(blackSession); + } + } + } + + public enum RoomState { + CREATED, + WAITING, + GAMING, + FINISHED + } + + @RequiredArgsConstructor + public enum Faces { + UP(0, -1), + UP_RIGHT(1, -1), + RIGHT(1, 0), + DOWN_RIGHT(1, 1), + DOWN(0, 1), + DOWN_LEFT(-1, 1), + LEFT(-1, 0), + UP_LEFT(-1, -1); + public final int xDelta, yDelta; + } + + public record WinInfo(Faces face, int originalX, int originalY, boolean isWhite) { + } +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/BaseWebSocketPacket.java b/src/main/java/org/blue/club/entities/dto/chess/packet/BaseWebSocketPacket.java new file mode 100644 index 0000000..0481f4d --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/BaseWebSocketPacket.java @@ -0,0 +1,32 @@ +package org.blue.club.entities.dto.chess.packet; + +import jakarta.annotation.Nullable; +import lombok.Data; +import lombok.NonNull; + +@Data +public class BaseWebSocketPacket { + private String name; + private Object payload; + @Nullable + private String originalPacketName; + + public BaseWebSocketPacket(Object payload) { + Class aClass = payload.getClass(); + this.name = aClass.getSimpleName().replace("Packet", ""); + this.payload = payload; + } + + public BaseWebSocketPacket(Object payload, @NonNull String originalPacketName) { + this(payload); + this.originalPacketName = originalPacketName; + } + + public static BaseWebSocketPacket of(Object payload) { + return new BaseWebSocketPacket(payload); + } + + public static BaseWebSocketPacket of(Object packet, @NonNull String originalPacketName) { + return new BaseWebSocketPacket(packet, originalPacketName); + } +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/ErrorPacket.java b/src/main/java/org/blue/club/entities/dto/chess/packet/ErrorPacket.java new file mode 100644 index 0000000..2f813f0 --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/ErrorPacket.java @@ -0,0 +1,4 @@ +package org.blue.club.entities.dto.chess.packet; + +public record ErrorPacket(String reason) { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/HasPlayerWinPacket.java b/src/main/java/org/blue/club/entities/dto/chess/packet/HasPlayerWinPacket.java new file mode 100644 index 0000000..f32bd18 --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/HasPlayerWinPacket.java @@ -0,0 +1,6 @@ +package org.blue.club.entities.dto.chess.packet; + +import org.blue.club.entities.dto.chess.Room; + +public record HasPlayerWinPacket(Room.WinInfo winInfo) { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerJoinRoomPacket.java b/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerJoinRoomPacket.java new file mode 100644 index 0000000..2a4b2bf --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerJoinRoomPacket.java @@ -0,0 +1,7 @@ +package org.blue.club.entities.dto.chess.packet; + +import org.blue.club.entities.dto.chess.Room; +import org.blue.club.entities.dto.user.User; + +public record PlayerJoinRoomPacket(User user, Room room) { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerLeavePacket.java b/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerLeavePacket.java new file mode 100644 index 0000000..b8ce6d7 --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerLeavePacket.java @@ -0,0 +1,4 @@ +package org.blue.club.entities.dto.chess.packet; + +public record PlayerLeavePacket(String player) { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerLosePacket.java b/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerLosePacket.java new file mode 100644 index 0000000..75896fe --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerLosePacket.java @@ -0,0 +1,4 @@ +package org.blue.club.entities.dto.chess.packet; + +public record PlayerLosePacket() { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerSideAllocationPacket.java b/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerSideAllocationPacket.java new file mode 100644 index 0000000..7686e6b --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerSideAllocationPacket.java @@ -0,0 +1,4 @@ +package org.blue.club.entities.dto.chess.packet; + +public record PlayerSideAllocationPacket(boolean isWhite) { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerWinPacket.java b/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerWinPacket.java new file mode 100644 index 0000000..5d8ac6b --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/PlayerWinPacket.java @@ -0,0 +1,4 @@ +package org.blue.club.entities.dto.chess.packet; + +public record PlayerWinPacket() { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/RoomCreatedPacket.java b/src/main/java/org/blue/club/entities/dto/chess/packet/RoomCreatedPacket.java new file mode 100644 index 0000000..8bd611f --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/RoomCreatedPacket.java @@ -0,0 +1,6 @@ +package org.blue.club.entities.dto.chess.packet; + +import java.util.UUID; + +public record RoomCreatedPacket(UUID roomId) { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/RoomInfoPacket.java b/src/main/java/org/blue/club/entities/dto/chess/packet/RoomInfoPacket.java new file mode 100644 index 0000000..a3ee99a --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/RoomInfoPacket.java @@ -0,0 +1,10 @@ +package org.blue.club.entities.dto.chess.packet; + +import org.blue.club.entities.dto.chess.Room; +import org.blue.club.entities.dto.user.User; + +import java.util.UUID; + +public record RoomInfoPacket(UUID roomId, User whiteUser, User blackUser, Room.RoomState state, byte[][] pieces, + boolean whiteRequestRestart, boolean blackRequestRestart, boolean canWhiteDown) { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/RoomListPacket.java b/src/main/java/org/blue/club/entities/dto/chess/packet/RoomListPacket.java new file mode 100644 index 0000000..3773b2a --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/RoomListPacket.java @@ -0,0 +1,8 @@ +package org.blue.club.entities.dto.chess.packet; + +import org.blue.club.entities.dto.chess.Room; + +import java.util.Collection; + +public record RoomListPacket(Collection rooms) { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/UserInfoPacket.java b/src/main/java/org/blue/club/entities/dto/chess/packet/UserInfoPacket.java new file mode 100644 index 0000000..5e7fc91 --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/UserInfoPacket.java @@ -0,0 +1,6 @@ +package org.blue.club.entities.dto.chess.packet; + +import org.blue.club.entities.dto.user.User; + +public record UserInfoPacket(User user) { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/request/CreateRoomRequest.java b/src/main/java/org/blue/club/entities/dto/chess/packet/request/CreateRoomRequest.java new file mode 100644 index 0000000..306b09e --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/request/CreateRoomRequest.java @@ -0,0 +1,4 @@ +package org.blue.club.entities.dto.chess.packet.request; + +public record CreateRoomRequest() { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/request/PlaceChessPieceRequest.java b/src/main/java/org/blue/club/entities/dto/chess/packet/request/PlaceChessPieceRequest.java new file mode 100644 index 0000000..aa19f0b --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/request/PlaceChessPieceRequest.java @@ -0,0 +1,4 @@ +package org.blue.club.entities.dto.chess.packet.request; + +public record PlaceChessPieceRequest(int x, int y) { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/request/PlayerJoinRequest.java b/src/main/java/org/blue/club/entities/dto/chess/packet/request/PlayerJoinRequest.java new file mode 100644 index 0000000..f7c8d12 --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/request/PlayerJoinRequest.java @@ -0,0 +1,6 @@ +package org.blue.club.entities.dto.chess.packet.request; + +import java.util.UUID; + +public record PlayerJoinRequest(UUID roomId) { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/request/ResetRoomRequest.java b/src/main/java/org/blue/club/entities/dto/chess/packet/request/ResetRoomRequest.java new file mode 100644 index 0000000..3c9fa3d --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/request/ResetRoomRequest.java @@ -0,0 +1,4 @@ +package org.blue.club.entities.dto.chess.packet.request; + +public record ResetRoomRequest() { +} diff --git a/src/main/java/org/blue/club/entities/dto/chess/packet/request/RoomListRequest.java b/src/main/java/org/blue/club/entities/dto/chess/packet/request/RoomListRequest.java new file mode 100644 index 0000000..8700071 --- /dev/null +++ b/src/main/java/org/blue/club/entities/dto/chess/packet/request/RoomListRequest.java @@ -0,0 +1,4 @@ +package org.blue.club.entities.dto.chess.packet.request; + +public record RoomListRequest() { +} diff --git a/src/main/java/org/blue/club/schedule/RemoveExpireDataSchedule.java b/src/main/java/org/blue/club/schedule/RemoveExpireDataSchedule.java new file mode 100644 index 0000000..2f032d2 --- /dev/null +++ b/src/main/java/org/blue/club/schedule/RemoveExpireDataSchedule.java @@ -0,0 +1,56 @@ +package org.blue.club.schedule; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.blue.club.dao.redis.AvatarOperationDao; +import org.blue.club.dao.redis.VerifyDao; +import org.blue.club.entities.vo.VerifyVo; +import org.blue.club.entities.vo.data.AvatarTmpVo; +import org.blue.club.utils.iop.FileUtils; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +@Slf4j +@Component +@RequiredArgsConstructor +public class RemoveExpireDataSchedule { + private final AvatarOperationDao avatarDao; + private final File tmpFolder; + private final VerifyDao verifyDao; + private final FileUtils fileUtils; + + @Scheduled(fixedDelay = 1000) + public void removeTempAvatar() { + Instant now = Instant.now(); + for (AvatarTmpVo avatarTmpVo : avatarDao.findAll()) { + Date time = avatarTmpVo.createTime(); + Instant instant = time.toInstant(); + Instant expireTime = instant.plus(15, ChronoUnit.MINUTES); + if (expireTime.isAfter(now)) { + String s = avatarTmpVo.operationCode(); + File deleteFile = new File(tmpFolder, s); + if (deleteFile.exists()) { + fileUtils.tryDeleteOrDeleteOnExit(deleteFile); + } + avatarDao.delete(avatarTmpVo); + } + } + } + + @Scheduled(fixedDelay = 1000) + public void removeExpiredVerify() { + Instant now = Instant.now(); + for (VerifyVo verifyVo : verifyDao.findAll()) { + Instant instant = verifyVo.createTime().toInstant(); + Instant expireTime = instant.plus(15, ChronoUnit.MINUTES); + if (expireTime.isAfter(now)) { + verifyDao.delete(verifyVo); + } + } + } +} diff --git a/src/main/java/org/blue/club/utils/WebSocketUtils.java b/src/main/java/org/blue/club/utils/WebSocketUtils.java new file mode 100644 index 0000000..1bf67b2 --- /dev/null +++ b/src/main/java/org/blue/club/utils/WebSocketUtils.java @@ -0,0 +1,34 @@ +package org.blue.club.utils; + +import com.alibaba.fastjson2.JSONObject; +import jakarta.annotation.Nullable; +import jakarta.websocket.Session; +import lombok.NonNull; +import lombok.SneakyThrows; +import org.blue.club.entities.dto.chess.packet.BaseWebSocketPacket; + +import java.util.Objects; + +public class WebSocketUtils { + @SneakyThrows + public static void sendJsonMessage(@NonNull JSONObject message, @NonNull Session session) { + session.getBasicRemote().sendText(message.toJSONString()); + } + + public static void sendMessage(@NonNull Object object, @NonNull Session session) { + sendJsonMessage(JSONObject.from(object), session); + } + + public static void sendPacket(@NonNull Object packet, @NonNull Session session) { + sendMessage(BaseWebSocketPacket.of(packet), session); + } + + public static void sendPacket(@NonNull Object packet, String originalPacketName, @NonNull Session session) { + sendMessage(BaseWebSocketPacket.of(packet, originalPacketName), session); + } + + public static void sendPacketIfPossible(@NonNull Object packet, @Nullable Session session) { + if (Objects.isNull(session)) return; + sendPacket(packet, session); + } +}