Skip to content

Commit e30ff16

Browse files
committed
Merge remote-tracking branch 'origin/dev' into fix/273
2 parents c1b6465 + 51d828e commit e30ff16

File tree

11 files changed

+487
-131
lines changed

11 files changed

+487
-131
lines changed

src/main/java/com/back/domain/user/service/EmailService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void sendUsernameEmail(String toEmail, String maskedUsername) {
6060
// 비밀번호 재설정 메일 전송
6161
public void sendPasswordResetEmail(String toEmail, String token) {
6262
String subject = "[Catfe] 비밀번호 재설정 안내";
63-
String resetUrl = FRONTEND_BASE_URL + "/reset-password?token=" + token;
63+
String resetUrl = FRONTEND_BASE_URL + "/find-pw?token=" + token;
6464

6565
String htmlContent = """
6666
<p>안녕하세요, Catfe입니다.</p>

src/main/java/com/back/global/websocket/config/WebSocketConfig.java

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import lombok.extern.slf4j.Slf4j;
88
import org.springframework.context.annotation.Configuration;
99
import org.springframework.core.Ordered;
10-
import org.springframework.context.annotation.Bean;
1110
import org.springframework.core.annotation.Order;
1211
import org.springframework.messaging.Message;
1312
import org.springframework.messaging.MessageChannel;
@@ -17,8 +16,6 @@
1716
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
1817
import org.springframework.messaging.support.ChannelInterceptor;
1918
import org.springframework.messaging.support.MessageHeaderAccessor;
20-
import org.springframework.scheduling.TaskScheduler;
21-
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
2219
import org.springframework.security.core.Authentication;
2320
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
2421
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@@ -87,30 +84,45 @@ public void registerStompEndpoints(StompEndpointRegistry registry) {
8784
*/
8885
@Override
8986
public void configureClientInboundChannel(ChannelRegistration registration) {
90-
// JWT 인증 인터셉터 등록
9187
registration.interceptors(new ChannelInterceptor() {
9288
@Override
9389
public Message<?> preSend(Message<?> message, MessageChannel channel) {
9490
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
9591

9692
if (accessor != null) {
97-
log.debug("WebSocket 메시지 처리 - Command: {}, Destination: {}, SessionId: {}",
98-
accessor.getCommand(), accessor.getDestination(), accessor.getSessionId());
9993

100-
// CONNECT 시점에서 JWT 토큰 인증
101-
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
102-
authenticateUser(accessor);
94+
log.info("🔥 [INTERCEPT] Command: {}, Dest: {}, SessionId: {}",
95+
accessor.getCommand(),
96+
accessor.getDestination(),
97+
accessor.getSessionId());
98+
99+
try {
100+
// CONNECT 시점에서 JWT 토큰 인증
101+
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
102+
authenticateUser(accessor);
103+
}
104+
105+
// SEND 시점에서 인증 확인 및 활동 시간 업데이트
106+
else if (StompCommand.SEND.equals(accessor.getCommand())) {
107+
log.info("🔥 [SEND] Dest: {}, User: {}",
108+
accessor.getDestination(),
109+
accessor.getUser() != null ? accessor.getUser().getName() : "null");
110+
111+
validateAuthenticationAndUpdateActivity(accessor);
112+
}
113+
} catch (Exception e) {
114+
115+
log.error("🔥 [INTERCEPT ERROR] Command: {}, Dest: {}, Error: {}",
116+
accessor.getCommand(),
117+
accessor.getDestination(),
118+
e.getMessage(), e);
119+
120+
// 예외를 다시 던져서 메시지 차단
121+
throw e;
103122
}
104-
105-
// SEND 시점에서 인증 확인 및 활동 시간 업데이트
106-
else if (StompCommand.SEND.equals(accessor.getCommand())) {
107-
validateAuthenticationAndUpdateActivity(accessor);
108-
}
109-
110-
// SUBSCRIBE/UNSUBSCRIBE는 단순히 채팅 구독일 뿐
111-
// 실제 방 입장/퇴장은 RoomController에서 비즈니스 로직으로 처리
112123
}
113124

125+
log.info("🔥 [INTERCEPT] Message passing through");
114126
return message;
115127
}
116128
});

src/main/java/com/back/global/websocket/controller/WebSocketMessageController.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@
33
import com.back.global.exception.CustomException;
44
import com.back.global.security.user.CustomUserDetails;
55
import com.back.global.websocket.service.WebSocketSessionManager;
6+
import com.back.global.websocket.util.WebSocketAuthHelper;
67
import com.back.global.websocket.util.WebSocketErrorHelper;
78
import lombok.RequiredArgsConstructor;
89
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.messaging.handler.annotation.DestinationVariable;
911
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
1012
import org.springframework.messaging.handler.annotation.MessageMapping;
13+
import org.springframework.messaging.handler.annotation.Payload;
1114
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
1215
import org.springframework.security.core.Authentication;
1316
import org.springframework.stereotype.Controller;
1417

1518
import java.security.Principal;
19+
import java.util.Map;
1620

1721
@Slf4j
1822
@Controller
@@ -22,6 +26,28 @@ public class WebSocketMessageController {
2226
private final WebSocketSessionManager sessionManager;
2327
private final WebSocketErrorHelper errorHelper;
2428

29+
// WebSocket 방 입장 확인 메시지
30+
// 클라이언트가 REST API로 입장 후 WebSocket 세션 동기화 대기를 위해 전송
31+
@MessageMapping("/rooms/{roomId}/join")
32+
public void handleWebSocketJoinRoom(@DestinationVariable Long roomId,
33+
@Payload Map<String, Object> payload,
34+
Principal principal) {
35+
CustomUserDetails userDetails = WebSocketAuthHelper.extractUserDetails(principal);
36+
if (userDetails == null) {
37+
log.warn("📥 [WebSocket] 방 입장 실패 - 인증 정보 없음");
38+
return;
39+
}
40+
41+
Long userId = userDetails.getUserId();
42+
log.info("📥 [WebSocket] 방 입장 확인 - roomId: {}, userId: {}", roomId, userId);
43+
44+
// 활동 시간 업데이트
45+
sessionManager.updateLastActivity(userId);
46+
47+
// 실제 방 입장 로직은 REST API에서 이미 처리했으므로
48+
// 여기서는 단순히 WebSocket 세션이 준비되었음을 확인하는 용도
49+
}
50+
2551
// Heartbeat 처리
2652
@MessageMapping("/heartbeat")
2753
public void handleHeartbeat(Principal principal, SimpMessageHeaderAccessor headerAccessor) {

src/main/java/com/back/global/websocket/dto/WebSocketStatusResponse.java

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/main/java/com/back/global/websocket/webrtc/controller/WebRTCSignalingController.java

Lines changed: 80 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,12 @@
33
import com.back.global.exception.ErrorCode;
44
import com.back.global.exception.CustomException;
55
import com.back.global.security.user.CustomUserDetails;
6-
import com.back.global.websocket.dto.WebSocketSessionInfo;
7-
import com.back.global.websocket.service.WebSocketSessionManager;
86
import com.back.global.websocket.util.WebSocketAuthHelper;
97
import com.back.global.websocket.webrtc.dto.WebRTCErrorResponse;
108
import com.back.global.websocket.webrtc.dto.media.WebRTCMediaStateResponse;
119
import com.back.global.websocket.webrtc.dto.media.WebRTCMediaToggleRequest;
1210
import com.back.global.websocket.webrtc.dto.signal.*;
1311
import com.back.global.websocket.webrtc.service.WebRTCSignalValidator;
14-
import com.back.global.websocket.util.WebSocketErrorHelper;
1512
import lombok.RequiredArgsConstructor;
1613
import lombok.extern.slf4j.Slf4j;
1714
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
@@ -29,9 +26,7 @@
2926
public class WebRTCSignalingController {
3027

3128
private final SimpMessagingTemplate messagingTemplate;
32-
private final WebSocketErrorHelper errorHelper;
3329
private final WebRTCSignalValidator validator;
34-
private final WebSocketSessionManager sessionManager;
3530

3631
// WebRTC Offer 메시지 처리
3732
@MessageMapping("/webrtc/offer")
@@ -44,21 +39,28 @@ public void handleOffer(@Validated @Payload WebRTCOfferRequest request, Principa
4439
Long fromUserId = userDetails.getUserId();
4540
Long targetUserId = request.targetUserId();
4641

47-
log.info("WebRTC Offer 처리 시작 - Room: {}, From: {}, To: {}", request.roomId(), fromUserId, targetUserId);
42+
log.info("[WebRTC] Offer 처리 - Room: {}, From: {}, To: {}", request.roomId(), fromUserId, targetUserId);
4843

44+
// 같은 방에 있는지, 자기 자신에게 보내는 건 아닌지 검증
4945
validator.validateSignal(request.roomId(), fromUserId, targetUserId);
5046

51-
WebSocketSessionInfo targetSessionInfo = sessionManager.getSessionInfo(targetUserId);
52-
if (targetSessionInfo == null) {
53-
log.warn("WebRTC Offer 전송 실패 - 대상이 오프라인 상태입니다. User ID: {}", targetUserId);
54-
throw new CustomException(ErrorCode.WS_TARGET_OFFLINE);
55-
}
56-
47+
// Offer 응답 생성
5748
WebRTCSignalResponse response = WebRTCSignalResponse.offerOrAnswer(
58-
WebRTCSignalType.OFFER, fromUserId, targetUserId, request.roomId(), request.sdp(), request.mediaType()
49+
WebRTCSignalType.OFFER,
50+
fromUserId,
51+
targetUserId,
52+
request.roomId(),
53+
request.sdp(),
54+
request.mediaType()
5955
);
6056

61-
messagingTemplate.convertAndSendToUser(targetSessionInfo.username(), "/queue/webrtc", response);
57+
// 방 토픽으로 브로드캐스트
58+
messagingTemplate.convertAndSend(
59+
"/topic/room/" + request.roomId() + "/webrtc",
60+
response
61+
);
62+
63+
log.debug("[WebRTC] Offer 전송 완료 - Room: {}, From: {}, To: {}", request.roomId(), fromUserId, targetUserId);
6264
}
6365

6466
// WebRTC Answer 메시지 처리
@@ -72,21 +74,28 @@ public void handleAnswer(@Validated @Payload WebRTCAnswerRequest request, Princi
7274
Long fromUserId = userDetails.getUserId();
7375
Long targetUserId = request.targetUserId();
7476

75-
log.info("WebRTC Answer 처리 시작 - Room: {}, From: {}, To: {}", request.roomId(), fromUserId, targetUserId);
77+
log.info("[WebRTC] Answer 처리 - Room: {}, From: {}, To: {}", request.roomId(), fromUserId, targetUserId);
7678

79+
// 같은 방에 있는지, 자기 자신에게 보내는 건 아닌지 검증
7780
validator.validateSignal(request.roomId(), fromUserId, targetUserId);
7881

79-
WebSocketSessionInfo targetSessionInfo = sessionManager.getSessionInfo(targetUserId);
80-
if (targetSessionInfo == null) {
81-
log.warn("WebRTC Answer 전송 실패 - 대상이 오프라인 상태입니다. User ID: {}", targetUserId);
82-
throw new CustomException(ErrorCode.WS_TARGET_OFFLINE);
83-
}
84-
82+
// Answer 응답 생성 (targetUserId 포함)
8583
WebRTCSignalResponse response = WebRTCSignalResponse.offerOrAnswer(
86-
WebRTCSignalType.ANSWER, fromUserId, targetUserId, request.roomId(), request.sdp(), request.mediaType()
84+
WebRTCSignalType.ANSWER,
85+
fromUserId,
86+
targetUserId,
87+
request.roomId(),
88+
request.sdp(),
89+
request.mediaType()
90+
);
91+
92+
// 방 토픽으로 브로드캐스트
93+
messagingTemplate.convertAndSend(
94+
"/topic/room/" + request.roomId() + "/webrtc",
95+
response
8796
);
8897

89-
messagingTemplate.convertAndSendToUser(targetSessionInfo.username(), "/queue/webrtc", response);
98+
log.debug("[WebRTC] Answer 전송 완료 - Room: {}, From: {}, To: {}", request.roomId(), fromUserId, targetUserId);
9099
}
91100

92101
// ICE Candidate 메시지 처리
@@ -100,23 +109,32 @@ public void handleIceCandidate(@Validated @Payload WebRTCIceCandidateRequest req
100109
Long fromUserId = userDetails.getUserId();
101110
Long targetUserId = request.targetUserId();
102111

103-
log.debug("WebRTC ICE Candidate 처리 시작 - Room: {}, From: {}, To: {}", request.roomId(), fromUserId, targetUserId);
112+
log.debug("[WebRTC] ICE Candidate 처리 - Room: {}, From: {}, To: {}", request.roomId(), fromUserId, targetUserId);
104113

114+
// 같은 방에 있는지, 자기 자신에게 보내는 건 아닌지 검증
105115
validator.validateSignal(request.roomId(), fromUserId, targetUserId);
106116

107-
WebSocketSessionInfo targetSessionInfo = sessionManager.getSessionInfo(targetUserId);
108-
if (targetSessionInfo == null) {
109-
return; // ICE Candidate는 실패해도 에러를 보내지 않고 조용히 무시
110-
}
111-
117+
// ICE Candidate 응답 생성
112118
WebRTCSignalResponse response = WebRTCSignalResponse.iceCandidate(
113-
fromUserId, targetUserId, request.roomId(), request.candidate(), request.sdpMid(), request.sdpMLineIndex()
119+
fromUserId,
120+
targetUserId,
121+
request.roomId(),
122+
request.candidate(),
123+
request.sdpMid(),
124+
request.sdpMLineIndex()
114125
);
115126

116-
messagingTemplate.convertAndSendToUser(targetSessionInfo.username(), "/queue/webrtc", response);
127+
// 방 토픽으로 브로드캐스트
128+
messagingTemplate.convertAndSend(
129+
"/topic/room/" + request.roomId() + "/webrtc",
130+
response
131+
);
117132
}
118133

119-
// 미디어 상태 토글 처리 (방 전체에 상태 공유)
134+
/**
135+
* 미디어 상태 토글 처리
136+
* 방 전체에 미디어 상태 변경 브로드캐스트
137+
*/
120138
@MessageMapping("/webrtc/media/toggle")
121139
public void handleMediaToggle(@Validated @Payload WebRTCMediaToggleRequest request, Principal principal) {
122140
CustomUserDetails userDetails = WebSocketAuthHelper.extractUserDetails(principal);
@@ -125,53 +143,71 @@ public void handleMediaToggle(@Validated @Payload WebRTCMediaToggleRequest reque
125143
}
126144

127145
Long userId = userDetails.getUserId();
128-
log.info("미디어 상태 변경 처리 시작 - Room: {}, User: {}", request.roomId(), userId);
129146

147+
log.info("[WebRTC] 미디어 상태 변경 - Room: {}, User: {}, Type: {}, Enabled: {}",
148+
request.roomId(), userId, request.mediaType(), request.enabled());
149+
150+
// 방 멤버인지 검증
130151
validator.validateMediaStateChange(request.roomId(), userId);
131152

153+
// 미디어 상태 응답 생성
132154
WebRTCMediaStateResponse response = WebRTCMediaStateResponse.of(
133-
userId, userDetails.getUsername(), request.mediaType(), request.enabled()
155+
userId,
156+
userDetails.getUsername(),
157+
request.mediaType(),
158+
request.enabled()
134159
);
135160

136-
messagingTemplate.convertAndSend("/topic/room/" + request.roomId() + "/media-status", response);
161+
// 방 전체에 미디어 상태 브로드캐스트
162+
messagingTemplate.convertAndSend(
163+
"/topic/room/" + request.roomId() + "/media-status",
164+
response
165+
);
137166
}
138167

139168
// WebRTC 시그널링 처리 중 발생하는 CustomException 처리
140169
@MessageExceptionHandler(CustomException.class)
141170
public void handleCustomException(CustomException e, Principal principal) {
142171
if (principal == null) {
143-
log.warn("인증 정보 없는 사용자의 WebRTC 오류: {}", e.getMessage());
172+
log.warn("[WebRTC] 인증 정보 없는 사용자의 오류: {}", e.getMessage());
144173
return;
145174
}
146175

147-
log.warn("WebRTC 시그널링 오류 발생 (to {}): {}", principal.getName(), e.getMessage());
176+
CustomUserDetails userDetails = WebSocketAuthHelper.extractUserDetails(principal);
177+
if (userDetails == null) {
178+
log.warn("[WebRTC] Principal에서 사용자 정보 추출 실패");
179+
return;
180+
}
148181

182+
log.warn("[WebRTC] 시그널링 오류 - User: {} (ID: {}), Error: {}",
183+
principal.getName(), userDetails.getUserId(), e.getMessage());
184+
185+
// 에러는 개인 큐로 전송 (방 전체에 보낼 필요 없음)
149186
WebRTCErrorResponse errorResponse = WebRTCErrorResponse.from(e);
150187

151188
messagingTemplate.convertAndSendToUser(
152-
principal.getName(), // 에러를 발생시킨 사람의 username
153-
"/queue/webrtc",
189+
principal.getName(),
190+
"/queue/errors", // 에러 전용 큐
154191
errorResponse
155192
);
156193
}
157194

158195
// 예상치 못한 모든 Exception 처리
159196
@MessageExceptionHandler(Exception.class)
160-
public void handleGeneralException(Exception e, Principal principal) { // headerAccessor -> Principal
197+
public void handleGeneralException(Exception e, Principal principal) {
161198
if (principal == null) {
162-
log.error("WebRTC 처리 중 인증 정보 없는 사용자의 예외 발생", e);
199+
log.error("[WebRTC] 인증 정보 없는 사용자의 예외 발생", e);
163200
return;
164201
}
165202

166-
log.error("WebRTC 시그널링 처리 중 예상치 못한 오류 발생 (to {})", principal.getName(), e);
203+
log.error("[WebRTC] 예상치 못한 오류 발생 - User: {}", principal.getName(), e);
167204

168-
// CustomException으로 감싸서 일관된 형식의 에러 DTO를 생성
169205
CustomException customException = new CustomException(ErrorCode.WS_INTERNAL_ERROR);
170206
WebRTCErrorResponse errorResponse = WebRTCErrorResponse.from(customException);
171207

172208
messagingTemplate.convertAndSendToUser(
173209
principal.getName(),
174-
"/queue/webrtc",
210+
"/queue/errors",
175211
errorResponse
176212
);
177213
}

src/main/java/com/back/global/websocket/webrtc/dto/signal/SdpData.java

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)