Skip to content

Commit f9423f4

Browse files
authored
Refactor : 스터디룸 웹소켓 연동과 역할 변경 알림 (#146) (#170)
* refactor: 스더티룸 권한에 대한 로직 개선 * fix: ci에서 통과 못한 테스트코드 수정 * fix:rest api와 웹소켓 중간 경로 통합 * fix:rest api와 웹소켓 중간 경로 통합
1 parent 9b6dfef commit f9423f4

File tree

4 files changed

+130
-35
lines changed

4 files changed

+130
-35
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.back.domain.studyroom.dto;
2+
3+
import com.back.domain.studyroom.entity.RoomRole;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
import java.time.LocalDateTime;
8+
9+
/**
10+
* 역할 변경 WebSocket 알림 DTO
11+
* - 방 멤버의 역할이 변경되었을 때 실시간 브로드캐스트
12+
*/
13+
@Getter
14+
@Builder
15+
public class RoleChangedNotification {
16+
17+
private Long roomId;
18+
private Long userId;
19+
private String nickname;
20+
private String profileImageUrl;
21+
private RoomRole oldRole;
22+
private RoomRole newRole;
23+
private String message;
24+
private LocalDateTime timestamp;
25+
26+
public static RoleChangedNotification of(
27+
Long roomId,
28+
Long userId,
29+
String nickname,
30+
String profileImageUrl,
31+
RoomRole oldRole,
32+
RoomRole newRole) {
33+
34+
String message = buildMessage(nickname, oldRole, newRole);
35+
36+
return RoleChangedNotification.builder()
37+
.roomId(roomId)
38+
.userId(userId)
39+
.nickname(nickname)
40+
.profileImageUrl(profileImageUrl)
41+
.oldRole(oldRole)
42+
.newRole(newRole)
43+
.message(message)
44+
.timestamp(LocalDateTime.now())
45+
.build();
46+
}
47+
48+
private static String buildMessage(String nickname, RoomRole oldRole, RoomRole newRole) {
49+
if (newRole == RoomRole.HOST) {
50+
return String.format("%s님이 방장으로 임명되었습니다.", nickname);
51+
} else if (oldRole == RoomRole.HOST) {
52+
return String.format("%s님이 일반 멤버로 변경되었습니다.", nickname);
53+
} else if (newRole == RoomRole.SUB_HOST) {
54+
return String.format("%s님이 부방장으로 승격되었습니다.", nickname);
55+
} else if (newRole == RoomRole.MEMBER && oldRole == RoomRole.VISITOR) {
56+
return String.format("%s님이 정식 멤버로 승격되었습니다.", nickname);
57+
} else if (newRole == RoomRole.MEMBER) {
58+
return String.format("%s님이 일반 멤버로 변경되었습니다.", nickname);
59+
}
60+
return String.format("%s님의 역할이 변경되었습니다.", nickname);
61+
}
62+
}

src/main/java/com/back/domain/studyroom/service/RoomRedisService.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,16 @@
99
import java.util.Set;
1010

1111
/**
12-
* 방 상태 관리를 위한 Redis 전용 서비스
13-
* 방의 온라인 사용자 관리 (입장/퇴장)
14-
* 실시간 참가자 수 조회
15-
* 온라인 사용자 목록 조회
16-
* Redis: 실시간 온라인 상태만 관리 (휘발성 데이터)
17-
* DB: 영구 멤버십 + 역할 정보 (MEMBER 이상만 저장)
18-
* 역할(Role)은 Redis에 저장하지 않음!
19-
* 이유 1: DB가 Single Source of Truth (데이터 일관성)
20-
* 이유 2: Redis-DB 동기화 복잡도 제거
21-
* 이유 3: 멤버 목록 조회 시 IN 절로 효율적 조회 가능
12+
* 방 상태 관리를 위한 Redis 전용 서비스 (곧 사라질 예정인 파일)
13+
* (현재는 일단 유지 시킨 상황, 에러 방지용)
14+
* @deprecated RoomParticipantService를 사용.
15+
* 현재는 WebSocketSessionManager의 Wrapper일 뿐이며,
16+
* RoomParticipantService에 원래 로직이 옮겨졋습니다.
17+
*
18+
* @see com.back.global.websocket.service.RoomParticipantService 실제 사용 서비스
2219
* @see com.back.global.websocket.service.WebSocketSessionManager WebSocket 세션 관리
23-
* @see com.back.domain.studyroom.repository.RoomMemberRepository DB 멤버십 조회
2420
*/
21+
@Deprecated
2522
@Slf4j
2623
@Service
2724
@RequiredArgsConstructor

src/main/java/com/back/domain/studyroom/service/RoomService.java

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.back.domain.user.repository.UserRepository;
88
import com.back.global.exception.CustomException;
99
import com.back.global.exception.ErrorCode;
10+
import com.back.global.websocket.service.RoomParticipantService;
1011
import lombok.RequiredArgsConstructor;
1112
import lombok.extern.slf4j.Slf4j;
1213
import org.springframework.data.domain.Page;
@@ -41,7 +42,8 @@ public class RoomService {
4142
private final RoomMemberRepository roomMemberRepository;
4243
private final UserRepository userRepository;
4344
private final StudyRoomProperties properties;
44-
private final RoomRedisService roomRedisService;
45+
private final RoomParticipantService roomParticipantService;
46+
private final org.springframework.messaging.simp.SimpMessagingTemplate messagingTemplate;
4547

4648
/**
4749
* 방 생성 메서드
@@ -109,7 +111,7 @@ public RoomMember joinRoom(Long roomId, String password, Long userId) {
109111
}
110112

111113
// Redis에서 현재 온라인 사용자 수 조회
112-
long currentOnlineCount = roomRedisService.getRoomUserCount(roomId);
114+
long currentOnlineCount = roomParticipantService.getParticipantCount(roomId);
113115

114116
// 정원 확인 (Redis 기반)
115117
if (currentOnlineCount >= room.getMaxParticipants()) {
@@ -133,7 +135,7 @@ public RoomMember joinRoom(Long roomId, String password, Long userId) {
133135
RoomMember member = existingMember.get();
134136

135137
// Redis에 온라인 등록
136-
roomRedisService.enterRoom(userId, roomId);
138+
roomParticipantService.enterRoom(userId, roomId);
137139

138140
log.info("기존 멤버 재입장 - RoomId: {}, UserId: {}, Role: {}",
139141
roomId, userId, member.getRole());
@@ -145,7 +147,7 @@ public RoomMember joinRoom(Long roomId, String password, Long userId) {
145147
RoomMember visitorMember = RoomMember.createVisitor(room, user);
146148

147149
// Redis에만 온라인 등록
148-
roomRedisService.enterRoom(userId, roomId);
150+
roomParticipantService.enterRoom(userId, roomId);
149151

150152
log.info("신규 입장 (VISITOR) - RoomId: {}, UserId: {}, DB 저장 안함", roomId, userId);
151153

@@ -172,7 +174,7 @@ public void leaveRoom(Long roomId, Long userId) {
172174
.orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND));
173175

174176
// Redis에서 퇴장 처리 (모든 사용자)
175-
roomRedisService.exitRoom(userId, roomId);
177+
roomParticipantService.exitRoom(userId, roomId);
176178

177179
log.info("방 퇴장 완료 - RoomId: {}, UserId: {}", roomId, userId);
178180
}
@@ -234,9 +236,9 @@ public void terminateRoom(Long roomId, Long userId) {
234236
room.terminate();
235237

236238
// Redis에서 모든 온라인 사용자 제거
237-
Set<Long> onlineUserIds = roomRedisService.getRoomUsers(roomId);
239+
Set<Long> onlineUserIds = roomParticipantService.getParticipants(roomId);
238240
for (Long onlineUserId : onlineUserIds) {
239-
roomRedisService.exitRoom(onlineUserId, roomId);
241+
roomParticipantService.exitRoom(onlineUserId, roomId);
240242
}
241243

242244
log.info("방 종료 완료 - RoomId: {}, UserId: {}, 퇴장 처리: {}명",
@@ -275,7 +277,10 @@ public void changeUserRole(Long roomId, Long targetUserId, RoomRole newRole, Lon
275277

276278
// 3. 대상자 확인 (DB 조회 - VISITOR는 DB에 없을 수 있음)
277279
Optional<RoomMember> targetMemberOpt = roomMemberRepository.findByRoomIdAndUserId(roomId, targetUserId);
278-
280+
281+
// 변경 전 역할 저장 (알림용)
282+
RoomRole oldRole = targetMemberOpt.map(RoomMember::getRole).orElse(RoomRole.VISITOR);
283+
279284
// 4. HOST로 변경하는 경우 - 기존 방장 강등
280285
if (newRole == RoomRole.HOST) {
281286
// 기존 방장을 MEMBER로 강등
@@ -306,6 +311,28 @@ public void changeUserRole(Long roomId, Long targetUserId, RoomRole newRole, Lon
306311
log.info("VISITOR 승격 (DB 저장) - RoomId: {}, UserId: {}, NewRole: {}",
307312
roomId, targetUserId, newRole);
308313
}
314+
315+
// 6. WebSocket으로 역할 변경 알림 브로드캐스트
316+
User targetUser = userRepository.findById(targetUserId)
317+
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
318+
319+
com.back.domain.studyroom.dto.RoleChangedNotification notification =
320+
com.back.domain.studyroom.dto.RoleChangedNotification.of(
321+
roomId,
322+
targetUserId,
323+
targetUser.getNickname(),
324+
targetUser.getProfileImageUrl(),
325+
oldRole,
326+
newRole
327+
);
328+
329+
messagingTemplate.convertAndSend(
330+
"/topic/room/" + roomId + "/role-changed",
331+
notification
332+
);
333+
334+
log.info("역할 변경 알림 전송 완료 - RoomId: {}, UserId: {}, {} → {}",
335+
roomId, targetUserId, oldRole, newRole);
309336
}
310337

311338
/**
@@ -332,7 +359,7 @@ public List<RoomMember> getRoomMembers(Long roomId, Long userId) {
332359
}
333360

334361
// 1. Redis에서 온라인 사용자 ID 조회
335-
Set<Long> onlineUserIds = roomRedisService.getRoomUsers(roomId);
362+
Set<Long> onlineUserIds = roomParticipantService.getParticipants(roomId);
336363

337364
if (onlineUserIds.isEmpty()) {
338365
return List.of();
@@ -412,7 +439,7 @@ public void kickMember(Long roomId, Long targetUserId, Long requesterId) {
412439
}
413440

414441
// Redis에서 제거 (강제 퇴장)
415-
roomRedisService.exitRoom(targetUserId, roomId);
442+
roomParticipantService.exitRoom(targetUserId, roomId);
416443

417444
log.info("멤버 추방 완료 - RoomId: {}, TargetUserId: {}, RequesterId: {}",
418445
roomId, targetUserId, requesterId);
@@ -424,7 +451,7 @@ public void kickMember(Long roomId, Long targetUserId, Long requesterId) {
424451
* RoomResponse 생성 (Redis에서 실시간 참가자 수 조회)
425452
*/
426453
public com.back.domain.studyroom.dto.RoomResponse toRoomResponse(Room room) {
427-
long onlineCount = roomRedisService.getRoomUserCount(room.getId());
454+
long onlineCount = roomParticipantService.getParticipantCount(room.getId());
428455
return com.back.domain.studyroom.dto.RoomResponse.from(room, onlineCount);
429456
}
430457

@@ -435,8 +462,12 @@ public java.util.List<com.back.domain.studyroom.dto.RoomResponse> toRoomResponse
435462
java.util.List<Long> roomIds = rooms.stream()
436463
.map(Room::getId)
437464
.collect(java.util.stream.Collectors.toList());
438-
439-
java.util.Map<Long, Long> participantCounts = roomRedisService.getBulkRoomOnlineUserCounts(roomIds);
465+
466+
java.util.Map<Long, Long> participantCounts = roomIds.stream()
467+
.collect(java.util.stream.Collectors.toMap(
468+
roomId -> roomId,
469+
roomId -> roomParticipantService.getParticipantCount(roomId)
470+
));
440471

441472
return rooms.stream()
442473
.map(room -> com.back.domain.studyroom.dto.RoomResponse.from(
@@ -452,7 +483,7 @@ public java.util.List<com.back.domain.studyroom.dto.RoomResponse> toRoomResponse
452483
public com.back.domain.studyroom.dto.RoomDetailResponse toRoomDetailResponse(
453484
Room room,
454485
java.util.List<com.back.domain.studyroom.entity.RoomMember> members) {
455-
long onlineCount = roomRedisService.getRoomUserCount(room.getId());
486+
long onlineCount = roomParticipantService.getParticipantCount(room.getId());
456487

457488
java.util.List<com.back.domain.studyroom.dto.RoomMemberResponse> memberResponses = members.stream()
458489
.map(com.back.domain.studyroom.dto.RoomMemberResponse::from)
@@ -465,7 +496,7 @@ public com.back.domain.studyroom.dto.RoomDetailResponse toRoomDetailResponse(
465496
* MyRoomResponse 생성 (Redis에서 실시간 참가자 수 조회)
466497
*/
467498
public com.back.domain.studyroom.dto.MyRoomResponse toMyRoomResponse(Room room, RoomRole myRole) {
468-
long onlineCount = roomRedisService.getRoomUserCount(room.getId());
499+
long onlineCount = roomParticipantService.getParticipantCount(room.getId());
469500
return com.back.domain.studyroom.dto.MyRoomResponse.of(room, onlineCount, myRole);
470501
}
471502

@@ -478,8 +509,12 @@ public java.util.List<com.back.domain.studyroom.dto.MyRoomResponse> toMyRoomResp
478509
java.util.List<Long> roomIds = rooms.stream()
479510
.map(Room::getId)
480511
.collect(java.util.stream.Collectors.toList());
481-
482-
java.util.Map<Long, Long> participantCounts = roomRedisService.getBulkRoomOnlineUserCounts(roomIds);
512+
513+
java.util.Map<Long, Long> participantCounts = roomIds.stream()
514+
.collect(java.util.stream.Collectors.toMap(
515+
roomId -> roomId,
516+
roomId -> roomParticipantService.getParticipantCount(roomId)
517+
));
483518

484519
return rooms.stream()
485520
.map(room -> {

src/test/java/com/back/domain/studyroom/service/RoomServiceTest.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.back.domain.user.repository.UserRepository;
1212
import com.back.global.exception.CustomException;
1313
import com.back.global.exception.ErrorCode;
14+
import com.back.global.websocket.service.RoomParticipantService;
1415
import org.junit.jupiter.api.BeforeEach;
1516
import org.junit.jupiter.api.DisplayName;
1617
import org.junit.jupiter.api.Test;
@@ -48,7 +49,7 @@ class RoomServiceTest {
4849
private StudyRoomProperties properties;
4950

5051
@Mock
51-
private RoomRedisService roomRedisService;
52+
private RoomParticipantService roomParticipantService;
5253

5354
@InjectMocks
5455
private RoomService roomService;
@@ -144,15 +145,15 @@ void joinRoom_Success() {
144145
given(roomRepository.findByIdWithLock(1L)).willReturn(Optional.of(testRoom));
145146
given(userRepository.findById(2L)).willReturn(Optional.of(testUser));
146147
given(roomMemberRepository.findByRoomIdAndUserId(1L, 2L)).willReturn(Optional.empty());
147-
given(roomRedisService.getRoomUserCount(1L)).willReturn(0L); // Redis 카운트
148+
given(roomParticipantService.getParticipantCount(1L)).willReturn(0L); // Redis 카운트
148149

149150
// when
150151
RoomMember joinedMember = roomService.joinRoom(1L, null, 2L);
151152

152153
// then
153154
assertThat(joinedMember).isNotNull();
154155
assertThat(joinedMember.getRole()).isEqualTo(RoomRole.VISITOR);
155-
verify(roomRedisService, times(1)).enterRoom(2L, 1L); // Redis 입장 확인
156+
verify(roomParticipantService, times(1)).enterRoom(2L, 1L); // Redis 입장 확인
156157
verify(roomMemberRepository, never()).save(any(RoomMember.class)); // DB 저장 안됨!
157158
}
158159

@@ -183,7 +184,7 @@ void joinRoom_WrongPassword() {
183184
true // useWebRTC
184185
);
185186
given(roomRepository.findByIdWithLock(1L)).willReturn(Optional.of(privateRoom));
186-
given(roomRedisService.getRoomUserCount(1L)).willReturn(0L); // Redis 카운트
187+
given(roomParticipantService.getParticipantCount(1L)).willReturn(0L); // Redis 카운트
187188

188189
// when & then
189190
assertThatThrownBy(() -> roomService.joinRoom(1L, "wrong", 1L))
@@ -201,7 +202,7 @@ void leaveRoom_Success() {
201202
roomService.leaveRoom(1L, 1L);
202203

203204
// then
204-
verify(roomRedisService, times(1)).exitRoom(1L, 1L); // Redis 퇴장 확인
205+
verify(roomParticipantService, times(1)).exitRoom(1L, 1L); // Redis 퇴장 확인
205206
}
206207

207208
@Test
@@ -312,7 +313,7 @@ void updateRoomSettings_NotOwner() {
312313
void terminateRoom_Success() {
313314
// given
314315
given(roomRepository.findById(1L)).willReturn(Optional.of(testRoom));
315-
given(roomRedisService.getRoomUsers(1L)).willReturn(java.util.Set.of()); // 온라인 사용자 없음
316+
given(roomParticipantService.getParticipants(1L)).willReturn(java.util.Set.of()); // 온라인 사용자 없음
316317

317318
// when
318319
roomService.terminateRoom(1L, 1L);
@@ -378,7 +379,7 @@ void kickMember_Success() {
378379
roomService.kickMember(1L, 2L, 1L);
379380

380381
// then
381-
verify(roomRedisService, times(1)).exitRoom(2L, 1L); // Redis 퇴장 확인
382+
verify(roomParticipantService, times(1)).exitRoom(2L, 1L); // Redis 퇴장 확인
382383
}
383384

384385
@Test

0 commit comments

Comments
 (0)