Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/java/game/controller/GameStartController.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
response.sendError(HttpServletResponse.SC_FORBIDDEN, "게임 시작 권한이 없습니다. (host만 가능)");
return;
}

int updated = roomPlayerDao.updatePlayersToInGame(roomId);

System.out.println("[GameStart] roomId=" + roomId + " updatedPlayers=" + updated);
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/lobby/controller/CreateRoomController.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)

String roomId = roomResult.getId();

session.setAttribute("hostUserId", hostUserId);

roomPlayerDAO.enterIfAbsent(roomResult.getId(), hostUserId);

LobbyWebSocket.broadcastRoomList();
Expand Down
144 changes: 144 additions & 0 deletions src/main/java/room/controller/RoomController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package room.controller;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.google.gson.Gson;

import room.dao.RoomPlayerDAO;
import room.dao.RoomPlayerDAOImpl;
import room.dto.RoomActiveCountDTO;

@WebServlet("/room/*")
public class RoomController extends HttpServlet {

private static final long serialVersionUID = 1L;

private final Gson gson = new Gson();
private final RoomPlayerDAO roomPlayerDAO = new RoomPlayerDAOImpl();

@FunctionalInterface
private interface Handler {
void handle(HttpServletRequest req, HttpServletResponse res, String userId) throws Exception;
}

private final Map<String, Handler> getHandlers = new HashMap<>();

@Override
public void init() {
getHandlers.put("count", this::handleGetCount);
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {

String action = getAction(req);
if (action == null) {
sendJson(res, 400, ApiError.of("잘못된 요청입니다."));
return;
}

String userId = getLoginUserId(req);
if (userId == null || userId.isBlank()) {
sendJson(res, 401, ApiError.of("로그인이 필요합니다."));
return;
}

Handler handler = getHandlers.get(action);
if (handler == null) {
sendJson(res, 404, ApiError.of("존재하지 않는 API입니다."));
return;
}

try {
handler.handle(req, res, userId);
} catch (Exception e) {
e.printStackTrace();
sendJson(res, 500, ApiError.of("서버 오류가 발생했습니다."));
}
}

/**
* GET /room/count?roomId=
* 응답: { ok:true, data:{ roomId, activeCount } }
*/
private void handleGetCount(HttpServletRequest req, HttpServletResponse res, String userId) throws Exception {
String roomId = req.getParameter("roomId");
if (roomId == null || roomId.isBlank()) {
sendJson(res, 400, ApiError.of("roomId가 필요합니다."));
return;
}
int activeCount = roomPlayerDAO.countActivePlayers(roomId);
RoomActiveCountDTO dto = new RoomActiveCountDTO(roomId, activeCount);
sendJson(res, 200, ApiSuccess.of(dto));
}

private String getAction(HttpServletRequest req) {
String pathInfo = req.getPathInfo();
if (pathInfo == null || "/".equals(pathInfo))
return null;
return pathInfo.substring(1);
}

private String getLoginUserId(HttpServletRequest req) {
HttpSession session = req.getSession(false);
return (session == null) ? null : (String)session.getAttribute("loginUserId");
}

private void sendJson(HttpServletResponse res, int status, Object body) throws IOException {
res.setStatus(status);
res.setContentType("application/json; charset=UTF-8");
res.getWriter().write(gson.toJson(body));
}

private static class ApiSuccess<T> {
private final boolean ok = true;
private final T data;

private ApiSuccess(T data) {
this.data = data;
}

public static <T> ApiSuccess<T> of(T data) {
return new ApiSuccess<>(data);
}

public boolean isOk() {
return ok;
}

public T getData() {
return data;
}
}

private static class ApiError {
private final boolean ok = false;
private final String message;

private ApiError(String message) {
this.message = message;
}

public static ApiError of(String message) {
return new ApiError(message);
}

public boolean isOk() {
return ok;
}

public String getMessage() {
return message;
}
}
}
20 changes: 19 additions & 1 deletion src/main/java/room/controller/ViewRoomController.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,44 @@
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import room.dao.RoomDAO;
import room.dao.RoomDAOImpl;

@WebServlet("/room")
public class ViewRoomController extends HttpServlet {
private static final long serialVersionUID = 1L;
private final RoomDAO roomDAO = new RoomDAOImpl();

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

try {
String roomId = request.getParameter("roomId");
String playType = request.getParameter("playType");
HttpSession session = request.getSession(false);

if (roomId == null) {
if (playType == null || roomId == null || session == null) {
response.sendRedirect("/lobby");
return;
}

request.setAttribute("roomId", roomId);
request.setAttribute("playType", playType);

String userId = (String)session.getAttribute("loginUserId");
String hostUserId = roomDAO.findHostUserIdByRoomId(roomId);

if (hostUserId == null) {
response.sendRedirect("/lobby?error=host_not_found");
return;
}

session.setAttribute("hostUserId", hostUserId);
session.setAttribute("userId", userId);

request.getRequestDispatcher("/WEB-INF/views/room.jsp").forward(request, response);
return;
} catch (Exception e) {
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/room/dao/RoomDAO.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,11 @@ RoomDTO createRoom(
*/
String getHostUserId(String roomId) throws Exception;

/**
* roomId로 방장 userId 조회
* @param roomId 방 ID
* @return hostUserId (없으면 null)
*/
String findHostUserIdByRoomId(String roomId) throws Exception;

}
23 changes: 23 additions & 0 deletions src/main/java/room/dao/RoomDAOImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,29 @@ public String getHostUserId(String roomId) throws Exception {
}
}

@Override
public String findHostUserIdByRoomId(String roomId) throws Exception {

String sql = """
SELECT host_user_id
FROM room
WHERE id = ?
""";

try (
Connection conn = DB.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, roomId);

try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getString("host_user_id");
}
return null;
}
}
}

private RoomDTO mapToRoom(ResultSet rs) throws SQLException {
return RoomDTO.builder()
.id(rs.getString("id"))
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/room/dto/RoomActiveCountDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package room.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class RoomActiveCountDTO {
private String roomId;
private int activeCount;
}
4 changes: 2 additions & 2 deletions src/main/java/room/ws/RoomWebSocket.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public void onMessage(Session session, String text) {
case "ROOM_EXIT": {
String roomId = sessionContext.getRoomId(session);

service.onExit(session, roomId);
service.onExit(session, roomId, "ROOM_EXIT");
sessionContext.leaveRoom(session);

service.sendIfOpen(session, "ROOM_EXIT", Map.of());
Expand Down Expand Up @@ -215,7 +215,7 @@ public void onClose(Session session, CloseReason reason) {
String result = roomService.exitAndHandleHost(roomId, userId);
System.out.println("[RoomWS][EXIT] roomId=" + roomId + " userId=" + userId + " result=" + result);

service.onExit(session, roomId);
service.onExit(session, roomId, result);
LobbyWebSocket.broadcastRoomList();
}
} catch (Exception e) {
Expand Down
31 changes: 29 additions & 2 deletions src/main/java/room/ws/RoomWebSocketService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import com.google.gson.Gson;

import room.dao.RoomDAO;
import room.dao.RoomDAOImpl;
import room.dao.RoomPlayerDAO;
import room.dao.RoomPlayerDAOImpl;
import room.dto.RoomPlayerDTO;
Expand All @@ -25,6 +27,7 @@ public class RoomWebSocketService {
private static final SessionContext sessionContext = SessionContext.getInstance();
private static final RoomSessionRegistry roomRegistry = RoomSessionRegistry.getInstance();
private final RoomPlayerDAO roomPlayerDao = new RoomPlayerDAOImpl();
private final RoomDAO roomDao = new RoomDAOImpl();

public void sendIfOpen(Session s, String type, Map<String, Object> payload) {
if (s == null || !s.isOpen())
Expand Down Expand Up @@ -74,22 +77,35 @@ public void onChat(Session session, String roomId, String text) {
"text", text));
}

public void onExit(Session session, String roomId) {
public void onExit(Session session, String roomId, String result) {
if (roomId == null || roomId.isBlank())
return;

roomRegistry.leave(roomId, session);

if ("HOST_CHANGE".equals(result)) {
String hostUserId;
try {
hostUserId = roomDao.getHostUserId(roomId);
if (hostUserId != null) {
broadcastHostChanged(roomId, hostUserId);
}
} catch (Exception e) {
e.printStackTrace();
}

}
broadcast(roomId, "USER_EXIT", Map.of(
"userId", sessionContext.getUserId(session),
"nickname", sessionContext.getNickname(session)));

}

/** @OnClose/@OnError 최종 정리 */
public void cleanup(Session session) {
String roomId = sessionContext.getRoomId(session);
if (roomId != null && !roomId.isBlank()) {
onExit(session, roomId);
onExit(session, roomId, null);
sessionContext.leaveRoom(session);
} else {
roomRegistry.removeFromAnyRoom(session);
Expand All @@ -111,6 +127,17 @@ public void broadcastGameStart(String roomId, String gameId, String playType) {
broadcast(roomId, "GAME_START", payload);
}

public void broadcastHostChanged(String roomId, String hostUserId) {
if (roomId == null || roomId.isBlank())
return;

Map<String, Object> payload = new HashMap<>();
payload.put("roomId", roomId);
payload.put("hostUserId", hostUserId);

broadcast(roomId, "HOST_CHANGE", payload);
}

private void broadcast(String roomId, String type, Map<String, Object> payload) {
Set<Session> sessions = roomRegistry.getSessions(roomId);
for (Session s : sessions) {
Expand Down
2 changes: 0 additions & 2 deletions src/main/webapp/WEB-INF/views/lobby.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,8 @@
</div>
</header>

<!-- ✅ 추가 wrapper(레이아웃용). 기존 태그/클래스/아이디는 유지 -->
<main class="lobby-layout">

<!-- 좌측: 방 목록 -->
<section class="lobby-left">
<h2>게임 방 목록</h2>
<p class="subtext">참여할 게임을 선택하세요</p>
Expand Down
11 changes: 5 additions & 6 deletions src/main/webapp/WEB-INF/views/room.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
data-room-name="<c:out value='${roomName}'/>"
data-play-type="<c:out value='${playType}'/>"
data-host-user-id="<c:out value='${hostUserId}'/>"
data-user-id="<c:out value='${userId}'/>"
>

<header class="header">
Expand Down Expand Up @@ -49,16 +50,14 @@
<section class="grid">
<section class="card side-nav">
<div>
<h3>👥 참가자</h3>
<h3 >👥 참가자</h3>
<ul id="user-list" class="user-list"></ul>
</div>
<form id="start-form"
method="post"
action="${pageContext.request.contextPath}/game/start?roomId=${roomId}&playType=${playType}">
<button type="submit" id="btn-start" class="btn-start">
<c:if test="${sessionScope.loginUserId eq sessionScope.hostUserId}">
<button type="submit" id="btn-start" class="btn-start">
🎯 시작하기
</button>
</form>
</c:if>
</section>
<section class="card">
<h3>💬 채팅</h3>
Expand Down
Loading