Skip to content

✨Feat: 단체채팅(JOIN, LEAVE는 아직 X)#18

Merged
angoroa merged 2 commits intodevelopfrom
feat/chat
Jan 24, 2026
Merged

✨Feat: 단체채팅(JOIN, LEAVE는 아직 X)#18
angoroa merged 2 commits intodevelopfrom
feat/chat

Conversation

@angoroa
Copy link
Copy Markdown
Member

@angoroa angoroa commented Jan 24, 2026

#️⃣ Issue Number

  • closed #

📝 요약(Summary)

  • 단체채팅 기능 html에서 이제는 될꺼임...!

🛠️ PR 유형

어떤 변경 사항이 있나요?

  • 새로운 기능 추가
  • 버그 수정
  • CSS 등 사용자 UI 디자인 변경
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 테스트 추가, 테스트 리팩토링
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📸스크린샷 (선택)

💬 공유사항 to 리뷰어

✅ PR Checklist

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • 커밋 메시지 컨벤션에 맞게 작성했습니다.
  • 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트).

Summary by CodeRabbit

  • 새로운 기능
    • 채팅방 입장/퇴장 시스템 메시지 추가
    • 실시간 사용자 세션 추적 및 관리 기능
    • 채팅 테스트용 웹 인터페이스 제공
    • 메시지 페이로드 구조 개선

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 24, 2026

요약

Walkthrough

채팅 시스템의 사용자 입장/퇴장 메시지 처리를 위한 인프라를 구축합니다. 세션 레지스트리, DTO, 서비스 메서드, 웹소켓 엔드포인트를 추가하고 테스트 UI를 구현하여 join/leave 기능을 지원합니다.

Changes

그룹 / 파일 변경 내용
채팅 메시지 관리
src/main/java/com/be/sportizebe/domain/chat/entity/ChatMessage.java, src/main/java/com/be/sportizebe/domain/chat/service/ChatMessageService.java
ChatMessage에 join(), leave() 정적 메서드 추가; ChatMessageService에 saveSystem(), saveJoinLeave() 메서드 추가하여 시스템 메시지 및 입장/퇴장 메시지 생성 및 저장 기능 제공
웹소켓 세션 관리
src/main/java/com/be/sportizebe/domain/chat/websocket/ChatSessionRegistry.java, src/main/java/com/be/sportizebe/domain/chat/websocket/ChatStompController.java
ChatSessionRegistry 컴포넌트(ConcurrentHashMap 기반 세션 추적)를 신설하고, ChatStompController에 join(), leave() 엔드포인트 추가하여 세션 등록/해제 및 시스템 메시지 브로드캐스트 처리
요청 DTO
src/main/java/com/be/sportizebe/domain/chat/dto/ChatPresenceRequest.java
입장/퇴장 요청을 위한 새로운 DTO 클래스 추가(roomId, userId, nickname 필드 포함)
테스트 UI
assets/stomp-test.html, src/main/java/com/be/sportizebe/test/stomp-test.html
클라이언트 테스트용 HTML 파일 추가 및 기존 파일의 join/leave 페이로드 필드명 변경(senderUserId → userId, senderNickname → nickname)

Sequence Diagram

sequenceDiagram
    actor Client as 클라이언트
    participant WS as STOMP<br/>WebSocket
    participant Ctrl as ChatStompController
    participant Reg as ChatSessionRegistry
    participant Svc as ChatMessageService
    participant DB as Database
    participant Topic as /topic/chat/rooms/{roomId}

    Client->>WS: join(roomId, userId, nickname)
    WS->>Ctrl: `@MessageMapping` /app/chat.join
    Ctrl->>Reg: put(sessionId, roomId, userId, nickname)
    Reg->>Reg: 세션 저장
    Ctrl->>Svc: saveJoinLeave(room, JOIN, userId, nickname, null)
    Svc->>DB: 입장 메시지 저장
    DB-->>Svc: ChatMessage
    Svc-->>Ctrl: ChatMessage
    Ctrl->>Topic: convertAndSend (입장 메시지)
    Topic-->>Client: 모든 구독자에게 브로드캐스트

    Client->>WS: leave(roomId, userId, nickname)
    WS->>Ctrl: `@MessageMapping` /app/chat.leave
    Ctrl->>Reg: remove(sessionId)
    Reg->>Reg: 세션 삭제
    Ctrl->>Svc: saveJoinLeave(room, LEAVE, userId, nickname, null)
    Svc->>DB: 퇴장 메시지 저장
    DB-->>Svc: ChatMessage
    Svc-->>Ctrl: ChatMessage
    Ctrl->>Topic: convertAndSend (퇴장 메시지)
    Topic-->>Client: 모든 구독자에게 브로드캐스트
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 분

Possibly related PRs

  • 🎨Design: stomp-test.html 추가 #16: 동일한 stomp-test.html 테스트 파일을 추가한 PR로, 본 PR에서 join/leave 페이로드 필드명 변경 및 관련 로직 업데이트와 함께 연관됨

Poem

🐰 세션을 등록하고, 메시지를 저장하고,
입장과 퇴장이 이제 안내판처럼,
웹소켓을 통해 춤을 추고,
토끼들의 채팅방이 활기차다! 🎉

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning PR 제목은 '단체채팅(JOIN, LEAVE는 아직 X)'이지만, 실제 변경사항에는 JOIN/LEAVE 기능이 완전히 구현되어 있습니다. PR 제목을 '단체채팅 JOIN/LEAVE 기능 구현'으로 수정하여 실제 변경사항과 일치시키세요. 제목이 현재 상태를 정확히 반영해야 합니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@src/main/java/com/be/sportizebe/domain/chat/service/ChatMessageService.java`:
- Around line 67-79: The saveSystem method in ChatMessageService appears to be
dead/ambiguous: either remove it or make its purpose explicit; either (A) delete
the unused saveSystem(ChatRoom, ChatMessage.Type, String) and any related tests
if no callers exist, or (B) restrict and document it to only create pure system
messages by changing the signature to accept only ChatMessage.Type.SYSTEM
(remove JOIN/LEAVE from allowed values), add a clear javadoc comment, and ensure
callers use saveJoinLeave for JOIN/LEAVE while routing true system notifications
to this method; update any references to use messageRepository.save(msg)
consistently and add a unit test demonstrating the intended behavior.

In
`@src/main/java/com/be/sportizebe/domain/chat/websocket/ChatSessionRegistry.java`:
- Around line 8-26: The registry currently never cleans up when sockets
disconnect; add a WebSocket disconnect event listener that calls
ChatSessionRegistry.remove(sessionId) to clear Presence entries and broadcast a
leave message: implement a component (e.g., WebSocketEventListener) with an
`@EventListener` method handleSessionDisconnect(SessionDisconnectEvent) that
retrieves the sessionId, removes the Presence from ChatSessionRegistry, fetches
the ChatRoom via ChatRoomService, creates/saves a LEAVE ChatMessage via
ChatMessageService (using presence.userId() and presence.nickname()), and sends
the ChatMessageResponse over SimpMessagingTemplate to
"/topic/chat/rooms/{roomId}" so abandoned sessions are removed and clients are
notified.

In
`@src/main/java/com/be/sportizebe/domain/chat/websocket/ChatStompController.java`:
- Around line 57-58: The join() and leave() handlers are broadcasting
ChatMessage entities directly (via messagingTemplate.convertAndSend) which leads
to inconsistent JSON vs send() (which uses ChatMessageResponse.from(saved)),
potential LazyInitializationException for room (FetchType.LAZY), and data
leakage; change join() and leave() to map the ChatMessage entity to the same DTO
used in send() by calling ChatMessageResponse.from(...) (or an equivalent
converter) and broadcast that DTO instead, ensuring any needed associations are
initialized inside the transaction/service layer before conversion so no lazy
fields are accessed during serialization.
- Around line 74-75: The leave() handler in ChatStompController is broadcasting
the raw entity instead of the DTO, causing the same direct-entity broadcast
issue; change the messagingTemplate.convertAndSend call in leave() to send
ChatMessageResponse.from(msg) (same pattern used elsewhere) so you convert the
domain object to the response DTO before broadcasting, updating the call that
currently constructs the destination "/topic/chat/rooms/" + req.getRoomId() and
uses msg.

In `@src/main/java/com/be/sportizebe/test/stomp-test.html`:
- Around line 433-468: In subscribeRoom(), validate the roomId and userId before
building the destination or sending join: call roomIdValue() and ensure it's
non-empty and Number(roomIdValue()) is not NaN, and call myUserId() and ensure
it's a finite number (not NaN); if either check fails, log a debug/warn and
return early instead of subscribing or sending the /app/chat.join payload. Also
ensure destination string construction uses the validated numeric roomId and
include these checks near the top of subscribeRoom (before dest/currentSub
logic) referencing subscribeRoom, roomIdValue(), myUserId(), stompClient,
currentSub and stompClient.send.
🧹 Nitpick comments (4)
src/main/java/com/be/sportizebe/domain/chat/dto/ChatPresenceRequest.java (1)

6-12: 입력 유효성 검증 추가를 권장합니다.

roomId, userId, nickname 필드에 @NotNull 등의 유효성 검증 어노테이션이 없습니다. 잘못된 요청이 들어올 경우 NullPointerException이 발생할 수 있습니다.

♻️ 제안하는 수정
 package com.be.sportizebe.domain.chat.dto;
 
 import lombok.Getter;
 import lombok.NoArgsConstructor;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
 
 `@Getter`
 `@NoArgsConstructor`
 public class ChatPresenceRequest {
+    `@NotNull`
     private Long roomId;
+    `@NotNull`
     private Long userId;
+    `@NotBlank`
     private String nickname;
 }
src/main/java/com/be/sportizebe/domain/chat/websocket/ChatStompController.java (1)

39-59: 중복 입장 처리 고려

동일 사용자가 이미 방에 입장한 상태에서 다시 join을 호출하면 중복 입장 메시지가 브로드캐스트됩니다. 기존 세션을 확인하거나, 중복 입장을 방지하는 로직을 추가하는 것을 고려하세요.

♻️ 제안하는 수정
     `@MessageMapping`("/chat.join")
     public void join(ChatPresenceRequest req, SimpMessageHeaderAccessor headerAccessor) {
         String sessionId = headerAccessor.getSessionId();
 
+        // 이미 입장한 세션인지 확인
+        if (registry.get(sessionId) != null) {
+            return; // 또는 로깅
+        }
+
         ChatRoom room = chatRoomService.getOrThrow(req.getRoomId());
         // ...
src/main/java/com/be/sportizebe/test/stomp-test.html (2)

304-314: 디버그 로그 무제한 누적 방지 고려

장시간 테스트 시 debugLog가 계속 커져 DOM/메모리 부담이 생길 수 있습니다. 로그 라인 상한을 두는 편이 안전합니다.

♻️ 제안 변경
-    let lastSent = { text: "", at: 0 };
+    let lastSent = { text: "", at: 0 };
+    const MAX_LOG_LINES = 500;

     function debug(msg){
         const el = $("debugLog");
-        el.textContent += msg + "\n";
+        const next = (el.textContent + msg + "\n").split("\n");
+        if (next.length > MAX_LOG_LINES) {
+            el.textContent = next.slice(-MAX_LOG_LINES).join("\n");
+        } else {
+            el.textContent += msg + "\n";
+        }
         el.scrollTop = el.scrollHeight;
     }

471-498: DOM 조회 중복 제거로 일관성 확보

이미 roomIdValue(), myUserId(), myNickname()가 있으니 재사용하면 로직 일관성과 유지보수성이 좋아집니다.

♻️ 제안 변경
-        const payload = {
-            roomId: Number(document.getElementById('roomId').value),
-            senderUserId: Number(document.getElementById('senderUserId').value),
-            senderNickname: document.getElementById('senderNickname').value.trim(),
-            content: text
-        };
+        const payload = {
+            roomId: Number(roomIdValue()),
+            senderUserId: myUserId(),
+            senderNickname: myNickname(),
+            content: text
+        };

Comment on lines +67 to +79
// 채팅이 아닌 시스템 메시지 전용!
@Transactional
public ChatMessage saveSystem(ChatRoom room, ChatMessage.Type type, String content) {
ChatMessage msg = ChatMessage.builder()
.room(room)
.senderUserId(0L)
.senderNickname("SYSTEM")
.type(type) // JOIN / LEAVE / SYSTEM
.content(content)
.build();

return messageRepository.save(msg);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for saveSystem method usage in Java files
rg -n "saveSystem" --type=java -B 2 -A 2

Repository: Sportize/be

Length of output: 660


🏁 Script executed:

#!/bin/bash
# Read the ChatMessageService file to see both saveSystem and saveJoinLeave methods
cat -n src/main/java/com/be/sportizebe/domain/chat/service/ChatMessageService.java | sed -n '67,90p'

Repository: Sportize/be

Length of output: 1095


saveSystem 메서드는 사용되지 않는 데드 코드입니다. 제거 또는 용도 명확히 필요합니다.

이 메서드는 코드베이스 전체에서 호출되지 않습니다. 또한 메서드 이름과 주석에서 "시스템 메시지 전용"이라고 명시하지만, type 파라미터는 JOIN/LEAVE/SYSTEM을 모두 받을 수 있어 목적이 불명확합니다. saveJoinLeave 메서드가 JOIN/LEAVE 유형을 처리하므로, saveSystem은 향후 순수 SYSTEM 타입 전용으로 예약된 것인지, 아니면 불필요한 코드인지 확인하여 제거하거나 명확히 하세요.

🤖 Prompt for AI Agents
In `@src/main/java/com/be/sportizebe/domain/chat/service/ChatMessageService.java`
around lines 67 - 79, The saveSystem method in ChatMessageService appears to be
dead/ambiguous: either remove it or make its purpose explicit; either (A) delete
the unused saveSystem(ChatRoom, ChatMessage.Type, String) and any related tests
if no callers exist, or (B) restrict and document it to only create pure system
messages by changing the signature to accept only ChatMessage.Type.SYSTEM
(remove JOIN/LEAVE from allowed values), add a clear javadoc comment, and ensure
callers use saveJoinLeave for JOIN/LEAVE while routing true system notifications
to this method; update any references to use messageRepository.save(msg)
consistently and add a unit test demonstrating the intended behavior.

Comment on lines +8 to +26
@Component
public class ChatSessionRegistry {

public record Presence(Long roomId, Long userId, String nickname) {}

private final ConcurrentMap<String, Presence> store = new ConcurrentHashMap<>();

public void put(String sessionId, Long roomId, Long userId, String nickname) {
store.put(sessionId, new Presence(roomId, userId, nickname));
}

public Presence remove(String sessionId) {
return store.remove(sessionId);
}

public Presence get(String sessionId) {
return store.get(sessionId);
}
} No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

비정상 연결 해제 시 세션 정리 필요

사용자가 /chat.leave를 호출하지 않고 브라우저를 닫거나 네트워크가 끊어지면 세션이 레지스트리에 남아 메모리 누수가 발생할 수 있습니다. WebSocket 연결 해제 이벤트를 처리하여 자동으로 세션을 정리하는 것을 권장합니다.

🛠️ 제안하는 수정 - WebSocket 이벤트 리스너 추가

별도의 이벤트 리스너 클래스를 생성하거나 기존 설정에 추가:

`@Component`
`@RequiredArgsConstructor`
public class WebSocketEventListener {
    
    private final ChatSessionRegistry registry;
    private final ChatMessageService chatMessageService;
    private final ChatRoomService chatRoomService;
    private final SimpMessagingTemplate messagingTemplate;

    `@EventListener`
    public void handleSessionDisconnect(SessionDisconnectEvent event) {
        String sessionId = event.getSessionId();
        ChatSessionRegistry.Presence presence = registry.remove(sessionId);
        
        if (presence != null) {
            // 자동 퇴장 메시지 브로드캐스트
            ChatRoom room = chatRoomService.getOrThrow(presence.roomId());
            String content = presence.nickname() + " 님이 퇴장했습니다.";
            ChatMessage msg = chatMessageService.saveJoinLeave(
                room, ChatMessage.Type.LEAVE,
                presence.userId(), presence.nickname(), content
            );
            messagingTemplate.convertAndSend(
                "/topic/chat/rooms/" + presence.roomId(),
                ChatMessageResponse.from(msg)
            );
        }
    }
}
🤖 Prompt for AI Agents
In
`@src/main/java/com/be/sportizebe/domain/chat/websocket/ChatSessionRegistry.java`
around lines 8 - 26, The registry currently never cleans up when sockets
disconnect; add a WebSocket disconnect event listener that calls
ChatSessionRegistry.remove(sessionId) to clear Presence entries and broadcast a
leave message: implement a component (e.g., WebSocketEventListener) with an
`@EventListener` method handleSessionDisconnect(SessionDisconnectEvent) that
retrieves the sessionId, removes the Presence from ChatSessionRegistry, fetches
the ChatRoom via ChatRoomService, creates/saves a LEAVE ChatMessage via
ChatMessageService (using presence.userId() and presence.nickname()), and sends
the ChatMessageResponse over SimpMessagingTemplate to
"/topic/chat/rooms/{roomId}" so abandoned sessions are removed and clients are
notified.

Comment on lines +57 to +58
// 3) 브로드캐스트
messagingTemplate.convertAndSend("/topic/chat/rooms/" + req.getRoomId(), msg);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

ChatMessage 엔티티 직접 브로드캐스트 문제

send() 메서드는 ChatMessageResponse.from(saved)를 사용하지만, join()leave()ChatMessage 엔티티를 직접 전송합니다. 이로 인해:

  1. JSON 응답 형식이 일관되지 않음
  2. room 필드가 FetchType.LAZY이므로 LazyInitializationException 발생 가능
  3. 의도하지 않은 내부 데이터 노출 위험
🐛 제안하는 수정
         // 3) 브로드캐스트
-        messagingTemplate.convertAndSend("/topic/chat/rooms/" + req.getRoomId(), msg);
+        messagingTemplate.convertAndSend("/topic/chat/rooms/" + req.getRoomId(), ChatMessageResponse.from(msg));
🤖 Prompt for AI Agents
In
`@src/main/java/com/be/sportizebe/domain/chat/websocket/ChatStompController.java`
around lines 57 - 58, The join() and leave() handlers are broadcasting
ChatMessage entities directly (via messagingTemplate.convertAndSend) which leads
to inconsistent JSON vs send() (which uses ChatMessageResponse.from(saved)),
potential LazyInitializationException for room (FetchType.LAZY), and data
leakage; change join() and leave() to map the ChatMessage entity to the same DTO
used in send() by calling ChatMessageResponse.from(...) (or an equivalent
converter) and broadcast that DTO instead, ensuring any needed associations are
initialized inside the transaction/service layer before conversion so no lazy
fields are accessed during serialization.

Comment on lines +74 to +75
);
messagingTemplate.convertAndSend("/topic/chat/rooms/" + req.getRoomId(), msg);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

동일한 엔티티 직접 브로드캐스트 문제

leave() 메서드에서도 동일한 문제가 있습니다. ChatMessageResponse.from(msg)를 사용해야 합니다.

🐛 제안하는 수정
-        messagingTemplate.convertAndSend("/topic/chat/rooms/" + req.getRoomId(), msg);
+        messagingTemplate.convertAndSend("/topic/chat/rooms/" + req.getRoomId(), ChatMessageResponse.from(msg));
🤖 Prompt for AI Agents
In
`@src/main/java/com/be/sportizebe/domain/chat/websocket/ChatStompController.java`
around lines 74 - 75, The leave() handler in ChatStompController is broadcasting
the raw entity instead of the DTO, causing the same direct-entity broadcast
issue; change the messagingTemplate.convertAndSend call in leave() to send
ChatMessageResponse.from(msg) (same pattern used elsewhere) so you convert the
domain object to the response DTO before broadcasting, updating the call that
currently constructs the destination "/topic/chat/rooms/" + req.getRoomId() and
uses msg.

Comment on lines +433 to +468
function subscribeRoom(){
if (!stompClient) return debug("[WARN] connect first");
if (!stompClient.connected) return debug("[WARN] not connected yet");

if (currentSub){
currentSub.unsubscribe();
currentSub = null;
setSub("not subscribed");
debug("[UNSUBSCRIBED] previous");
}

const rid = roomIdValue();
const dest = `/topic/chat/rooms/${rid}`;
$("roomTitle").textContent = `room: ${rid}`;

currentSub = stompClient.subscribe(dest, (frame) => {
// 서버 JSON 그대로 받음
try{
const data = JSON.parse(frame.body);
appendMessage(data);
}catch(e){
// 혹시 JSON 아니면 텍스트로 표시
appendMessage({
senderNickname: "system",
senderUserId: -1,
content: frame.body
});
}
});
stompClient.send("/app/chat.join", {}, JSON.stringify({
roomId: Number(roomIdValue()),
senderUserId: myUserId(),
senderNickname: myNickname()
}));
setSub("subscribed: " + dest);
debug("[SUBSCRIBED] " + dest);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

roomId/userId 유효성 검증 추가 필요

roomId가 비어있거나 userId가 NaN이면 /topic/chat/rooms/로 구독하거나 join 페이로드가 비정상 값이 됩니다. 간단한 검증 후 반환하는 게 안전합니다.

🐛 제안 변경
-        const rid = roomIdValue();
-        const dest = `/topic/chat/rooms/${rid}`;
+        const rid = roomIdValue();
+        const roomIdNum = Number(rid);
+        const userIdNum = myUserId();
+        const nickname = myNickname();
+        if (!rid || !Number.isFinite(roomIdNum) || !Number.isFinite(userIdNum) || !nickname) {
+            return debug("[WARN] invalid roomId/userId/nickname");
+        }
+        const dest = `/topic/chat/rooms/${roomIdNum}`;

-        stompClient.send("/app/chat.join", {}, JSON.stringify({
-            roomId: Number(roomIdValue()),
-            senderUserId: myUserId(),
-            senderNickname: myNickname()
-        }));
+        stompClient.send("/app/chat.join", {}, JSON.stringify({
+            roomId: roomIdNum,
+            senderUserId: userIdNum,
+            senderNickname: nickname
+        }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function subscribeRoom(){
if (!stompClient) return debug("[WARN] connect first");
if (!stompClient.connected) return debug("[WARN] not connected yet");
if (currentSub){
currentSub.unsubscribe();
currentSub = null;
setSub("not subscribed");
debug("[UNSUBSCRIBED] previous");
}
const rid = roomIdValue();
const dest = `/topic/chat/rooms/${rid}`;
$("roomTitle").textContent = `room: ${rid}`;
currentSub = stompClient.subscribe(dest, (frame) => {
// 서버 JSON 그대로 받음
try{
const data = JSON.parse(frame.body);
appendMessage(data);
}catch(e){
// 혹시 JSON 아니면 텍스트로 표시
appendMessage({
senderNickname: "system",
senderUserId: -1,
content: frame.body
});
}
});
stompClient.send("/app/chat.join", {}, JSON.stringify({
roomId: Number(roomIdValue()),
senderUserId: myUserId(),
senderNickname: myNickname()
}));
setSub("subscribed: " + dest);
debug("[SUBSCRIBED] " + dest);
function subscribeRoom(){
if (!stompClient) return debug("[WARN] connect first");
if (!stompClient.connected) return debug("[WARN] not connected yet");
if (currentSub){
currentSub.unsubscribe();
currentSub = null;
setSub("not subscribed");
debug("[UNSUBSCRIBED] previous");
}
const rid = roomIdValue();
const roomIdNum = Number(rid);
const userIdNum = myUserId();
const nickname = myNickname();
if (!rid || !Number.isFinite(roomIdNum) || !Number.isFinite(userIdNum) || !nickname) {
return debug("[WARN] invalid roomId/userId/nickname");
}
const dest = `/topic/chat/rooms/${roomIdNum}`;
$("roomTitle").textContent = `room: ${rid}`;
currentSub = stompClient.subscribe(dest, (frame) => {
// 서버 JSON 그대로 받음
try{
const data = JSON.parse(frame.body);
appendMessage(data);
}catch(e){
// 혹시 JSON 아니면 텍스트로 표시
appendMessage({
senderNickname: "system",
senderUserId: -1,
content: frame.body
});
}
});
stompClient.send("/app/chat.join", {}, JSON.stringify({
roomId: roomIdNum,
senderUserId: userIdNum,
senderNickname: nickname
}));
setSub("subscribed: " + dest);
debug("[SUBSCRIBED] " + dest);
🤖 Prompt for AI Agents
In `@src/main/java/com/be/sportizebe/test/stomp-test.html` around lines 433 - 468,
In subscribeRoom(), validate the roomId and userId before building the
destination or sending join: call roomIdValue() and ensure it's non-empty and
Number(roomIdValue()) is not NaN, and call myUserId() and ensure it's a finite
number (not NaN); if either check fails, log a debug/warn and return early
instead of subscribing or sending the /app/chat.join payload. Also ensure
destination string construction uses the validated numeric roomId and include
these checks near the top of subscribeRoom (before dest/currentSub logic)
referencing subscribeRoom, roomIdValue(), myUserId(), stompClient, currentSub
and stompClient.send.

@angoroa angoroa self-assigned this Jan 24, 2026
@angoroa angoroa merged commit c0c6101 into develop Jan 24, 2026
1 check passed
@imjuyongp
Copy link
Copy Markdown
Member

join, leave 되어 있는데요?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants