Skip to content

Commit 5a6f6ae

Browse files
authored
Feat: 스터디룸 썸네일 기능 및 설정 변경 시 webrtc 주석 (#217) (#225)
1 parent f76f33b commit 5a6f6ae

File tree

12 files changed

+151
-59
lines changed

12 files changed

+151
-59
lines changed

src/main/java/com/back/domain/studyroom/controller/RoomController.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ public ResponseEntity<RsData<RoomResponse>> createRoom(
6464
request.getPassword(),
6565
request.getMaxParticipants() != null ? request.getMaxParticipants() : 10,
6666
currentUserId,
67-
request.getUseWebRTC() != null ? request.getUseWebRTC() : true // 디폴트: true
67+
request.getUseWebRTC() != null ? request.getUseWebRTC() : true, // 디폴트: true
68+
request.getThumbnailUrl() // 썸네일 URL
6869
);
6970

7071
RoomResponse response = roomService.toRoomResponse(room);
@@ -339,7 +340,7 @@ public ResponseEntity<RsData<List<MyRoomResponse>>> getMyRooms() {
339340
@PutMapping("/{roomId}")
340341
@Operation(
341342
summary = "방 설정 수정",
342-
description = "방의 제목, 설명, 정원, RTC 설정 등을 수정합니다. 방장만 수정 가능합니다."
343+
description = "방의 제목, 설명, 정원, 썸네일을 수정합니다. 방장만 수정 가능합니다. WebRTC 설정은 현재 수정 불가합니다."
343344
)
344345
@ApiResponses({
345346
@ApiResponse(responseCode = "200", description = "수정 성공"),
@@ -359,9 +360,7 @@ public ResponseEntity<RsData<Void>> updateRoom(
359360
request.getTitle(),
360361
request.getDescription(),
361362
request.getMaxParticipants(),
362-
request.getAllowCamera() != null ? request.getAllowCamera() : true,
363-
request.getAllowAudio() != null ? request.getAllowAudio() : true,
364-
request.getAllowScreenShare() != null ? request.getAllowScreenShare() : true,
363+
request.getThumbnailUrl(),
365364
currentUserId
366365
);
367366

src/main/java/com/back/domain/studyroom/dto/CreateRoomRequest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ public class CreateRoomRequest {
1616
@Size(max = 500, message = "방 설명은 500자를 초과할 수 없습니다")
1717
private String description;
1818

19+
// 방 썸네일 이미지 URL (선택)
20+
@Size(max = 500, message = "썸네일 URL은 500자를 초과할 수 없습니다")
21+
private String thumbnailUrl;
22+
1923
private Boolean isPrivate = false;
2024

2125
private String password;

src/main/java/com/back/domain/studyroom/dto/RoomResponse.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class RoomResponse {
1414
private String title;
1515
private String description;
1616
private Boolean isPrivate; // 비공개 방 여부 (UI에서 🔒 아이콘 표시용)
17+
private String thumbnailUrl; // 썸네일 이미지 URL
1718
private int currentParticipants;
1819
private int maxParticipants;
1920
private RoomStatus status;
@@ -31,6 +32,7 @@ public static RoomResponse from(Room room, long currentParticipants) {
3132
.title(room.getTitle())
3233
.description(room.getDescription() != null ? room.getDescription() : "")
3334
.isPrivate(room.isPrivate()) // 비공개 방 여부
35+
.thumbnailUrl(room.getThumbnailUrl()) // 썸네일 URL
3436
.currentParticipants((int) currentParticipants) // Redis에서 조회한 실시간 값
3537
.maxParticipants(room.getMaxParticipants())
3638
.status(room.getStatus())
@@ -52,6 +54,7 @@ public static RoomResponse fromMasked(Room room) {
5254
.title("🔒 비공개 방") // 제목 마스킹
5355
.description("비공개 방입니다") // 설명 마스킹
5456
.isPrivate(true)
57+
.thumbnailUrl(null) // 썸네일 숨김
5558
.currentParticipants(0) // 참가자 수 숨김
5659
.maxParticipants(0) // 정원 숨김
5760
.status(room.getStatus())

src/main/java/com/back/domain/studyroom/dto/UpdateRoomSettingsRequest.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,15 @@ public class UpdateRoomSettingsRequest {
2020
@Max(value = 100, message = "최대 100명까지 가능합니다")
2121
private Integer maxParticipants;
2222

23-
private Boolean allowCamera = true;
24-
private Boolean allowAudio = true;
25-
private Boolean allowScreenShare = true;
23+
// 방 썸네일 이미지 URL (선택)
24+
@Size(max = 500, message = "썸네일 URL은 500자를 초과할 수 없습니다")
25+
private String thumbnailUrl;
26+
27+
// ===== WebRTC 설정 (추후 팀원 구현 시 주석 해제) =====
28+
// WebRTC 기능은 방 생성 이후 별도 API로 관리 예정
29+
// 현재는 방 생성 시의 useWebRTC 값으로만 초기 설정됨
30+
31+
// private Boolean allowCamera = true;
32+
// private Boolean allowAudio = true;
33+
// private Boolean allowScreenShare = true;
2634
}

src/main/java/com/back/domain/studyroom/entity/Room.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,31 @@ public class Room extends BaseEntity {
2525
private String password;
2626
private int maxParticipants;
2727
private boolean isActive;
28+
29+
// 방 썸네일 이미지 URL
30+
private String thumbnailUrl;
31+
32+
// 디폴트 썸네일 URL
33+
private static final String DEFAULT_THUMBNAIL_URL = "/images/default-room-thumbnail.png";
34+
35+
/**
36+
* 썸네일 URL 조회 (디폴트 처리 포함)
37+
* null인 경우 디폴트 이미지 반환
38+
*/
39+
public String getThumbnailUrl() {
40+
return (thumbnailUrl != null && !thumbnailUrl.trim().isEmpty())
41+
? thumbnailUrl
42+
: DEFAULT_THUMBNAIL_URL;
43+
}
44+
45+
/**
46+
* 원본 썸네일 URL 조회 (디폴트 처리 없음)
47+
* DB에 실제 저장된 값 그대로 반환
48+
*/
49+
public String getRawThumbnailUrl() {
50+
return thumbnailUrl;
51+
}
52+
2853
private boolean allowCamera;
2954
private boolean allowAudio;
3055
private boolean allowScreenShare;
@@ -153,14 +178,15 @@ public boolean isOwner(Long userId) {
153178
*/
154179
public static Room create(String title, String description, boolean isPrivate,
155180
String password, int maxParticipants, User creator, RoomTheme theme,
156-
boolean useWebRTC) {
181+
boolean useWebRTC, String thumbnailUrl) {
157182
Room room = new Room();
158183
room.title = title;
159184
room.description = description;
160185
room.isPrivate = isPrivate;
161186
room.password = password;
162187
room.maxParticipants = maxParticipants;
163188
room.isActive = true; // 생성 시 기본적으로 활성화
189+
room.thumbnailUrl = thumbnailUrl; // 썸네일 URL
164190
room.allowCamera = useWebRTC; // WebRTC 사용 여부에 따라 설정
165191
room.allowAudio = useWebRTC; // WebRTC 사용 여부에 따라 설정
166192
room.allowScreenShare = useWebRTC; // WebRTC 사용 여부에 따라 설정
@@ -179,15 +205,22 @@ public static Room create(String title, String description, boolean isPrivate,
179205
}
180206

181207
/**
182-
* 방 설정 일괄 업데이트 메서드
183-
방장이 방 설정을 변경할 때 여러 필드를 한 번에 업데이트
184-
주된 생성 이유.. rtc 단체 제어를 위해 잡아놓았음. 잡아준 필드 변경 가능성 농후!!
208+
* 방 설정 일괄 업데이트 메서드 (썸네일 포함)
209+
* 방장이 방 설정을 변경할 때 여러 필드를 한 번에 업데이트
210+
* WebRTC 설정은 제외 (확장성을 위해 제거가 아닌 주석으로 현재 놔둠..)
185211
*/
186-
public void updateSettings(String title, String description, int maxParticipants,
187-
boolean allowCamera, boolean allowAudio, boolean allowScreenShare) {
212+
public void updateSettings(String title, String description, int maxParticipants, String thumbnailUrl) {
188213
this.title = title;
189214
this.description = description;
190215
this.maxParticipants = maxParticipants;
216+
this.thumbnailUrl = thumbnailUrl;
217+
}
218+
219+
/**
220+
* WebRTC 설정 업데이트 메서드 (추후 사용 가능!)
221+
* 현재는 미사용 - 추후 팀원이 만약 구현 시 활성화 되도록
222+
*/
223+
public void updateWebRTCSettings(boolean allowCamera, boolean allowAudio, boolean allowScreenShare) {
191224
this.allowCamera = allowCamera;
192225
this.allowAudio = allowAudio;
193226
this.allowScreenShare = allowScreenShare;

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,21 @@ public class RoomService {
6767
*/
6868
@Transactional
6969
public Room createRoom(String title, String description, boolean isPrivate,
70-
String password, int maxParticipants, Long creatorId, boolean useWebRTC) {
70+
String password, int maxParticipants, Long creatorId, boolean useWebRTC, String thumbnailUrl) {
7171

7272
User creator = userRepository.findById(creatorId)
7373
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
7474

75-
Room room = Room.create(title, description, isPrivate, password, maxParticipants, creator, null, useWebRTC);
75+
Room room = Room.create(title, description, isPrivate, password, maxParticipants, creator, null, useWebRTC, thumbnailUrl);
7676
Room savedRoom = roomRepository.save(room);
7777

7878
RoomMember hostMember = RoomMember.createHost(savedRoom, creator);
7979
roomMemberRepository.save(hostMember);
8080

8181
// savedRoom.incrementParticipant(); // Redis로 이관 - DB 업데이트 제거
8282

83-
log.info("방 생성 완료 - RoomId: {}, Title: {}, CreatorId: {}, WebRTC: {}",
84-
savedRoom.getId(), title, creatorId, useWebRTC);
83+
log.info("방 생성 완료 - RoomId: {}, Title: {}, CreatorId: {}, WebRTC: {}, Thumbnail: {}",
84+
savedRoom.getId(), title, creatorId, useWebRTC, thumbnailUrl != null ? "설정됨" : "없음");
8585

8686
return savedRoom;
8787
}
@@ -268,8 +268,7 @@ public List<Room> getUserRooms(Long userId) {
268268

269269
@Transactional
270270
public void updateRoomSettings(Long roomId, String title, String description,
271-
int maxParticipants, boolean allowCamera,
272-
boolean allowAudio, boolean allowScreenShare, Long userId) {
271+
int maxParticipants, String thumbnailUrl, Long userId) {
273272

274273
Room room = roomRepository.findById(roomId)
275274
.orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND));
@@ -285,10 +284,10 @@ public void updateRoomSettings(Long roomId, String title, String description,
285284
throw new CustomException(ErrorCode.BAD_REQUEST);
286285
}
287286

288-
room.updateSettings(title, description, maxParticipants,
289-
allowCamera, allowAudio, allowScreenShare);
287+
room.updateSettings(title, description, maxParticipants, thumbnailUrl);
290288

291-
log.info("방 설정 변경 완료 - RoomId: {}, UserId: {}", roomId, userId);
289+
log.info("방 설정 변경 완료 - RoomId: {}, UserId: {}, Thumbnail: {}",
290+
roomId, userId, thumbnailUrl != null ? "변경됨" : "없음");
292291
}
293292

294293
/**

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

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
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;
1011
import org.springframework.core.annotation.Order;
1112
import org.springframework.messaging.Message;
1213
import org.springframework.messaging.MessageChannel;
@@ -16,6 +17,8 @@
1617
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
1718
import org.springframework.messaging.support.ChannelInterceptor;
1819
import org.springframework.messaging.support.MessageHeaderAccessor;
20+
import org.springframework.scheduling.TaskScheduler;
21+
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
1922
import org.springframework.security.core.Authentication;
2023
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
2124
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@@ -36,14 +39,37 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
3639
* - /topic: 1:N 브로드캐스트 (방 채팅)
3740
* - /queue: 1:1 메시지 (개인 DM)
3841
* - /app: 클라이언트에서 서버로 메시지 전송 시 prefix
42+
*
43+
* STOMP 하트비트 설정(임시 주석 상태):
44+
* - 25초마다 자동 하트비트 전송 (쓰기 비활성 시)
45+
* - 25초 이상 응답 없으면 연결 종료 (읽기 비활성 시)
3946
*/
4047
@Override
4148
public void configureMessageBroker(MessageBrokerRegistry config) {
4249
config.enableSimpleBroker("/topic", "/queue");
50+
//.setHeartbeatValue(new long[]{25000, 25000}) // [서버→클라이언트, 클라이언트→서버]
51+
//.setTaskScheduler(heartBeatScheduler());
52+
4353
config.setApplicationDestinationPrefixes("/app");
4454
config.setUserDestinationPrefix("/user");
4555
}
4656

57+
/**(임시 주석 상태)
58+
* STOMP 하트비트 전용 스케줄러!!
59+
* - 별도 스레드 풀로 하트비트 처리
60+
* - 메인 비즈니스 로직에 영향 없음
61+
62+
@Bean
63+
public TaskScheduler heartBeatScheduler() {
64+
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
65+
scheduler.setPoolSize(1);
66+
scheduler.setThreadNamePrefix("wss-heartbeat-");
67+
scheduler.initialize();
68+
log.info("STOMP 하트비트 스케줄러 초기화 완료 - 주기: 25초");
69+
return scheduler;
70+
}
71+
*/
72+
4773
/**
4874
* STOMP 엔드포인트 등록
4975
* 클라이언트가 WebSocket 연결을 위해 사용할 엔드포인트
@@ -124,24 +150,22 @@ private void authenticateUser(StompHeaderAccessor accessor) {
124150
}
125151

126152
/**
127-
* 메시지 전송 시 인증 상태 확인 및 활동 시간 업데이트
153+
* 메시지 전송 시 인증 상태 확인
128154
*/
129155
private void validateAuthenticationAndUpdateActivity(StompHeaderAccessor accessor) {
130156
if (accessor.getUser() == null) {
131157
throw new RuntimeException("인증이 필요합니다");
132158
}
133159

134-
// 인증된 사용자 정보 추출 및 활동 시간 업데이트
135160
Authentication auth = (Authentication) accessor.getUser();
136161
if (auth.getPrincipal() instanceof CustomUserDetails userDetails) {
137162
Long userId = userDetails.getUserId();
138163

139-
// 사용자 활동 시간 업데이트 (Heartbeat 효과)
164+
// 전역 세션 활동 시간 업데이트
140165
try {
141166
sessionManager.updateLastActivity(userId);
142167
} catch (Exception e) {
143168
log.warn("활동 시간 업데이트 실패 - 사용자: {}, 오류: {}", userId, e.getMessage());
144-
// 활동 시간 업데이트 실패해도 메시지 전송은 계속 진행
145169
}
146170

147171
log.debug("인증된 사용자 메시지 전송 - 사용자: {} (ID: {}), 목적지: {}",

src/test/java/com/back/domain/notification/event/studyroom/StudyRoomNotificationEventListenerTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ void setUp() {
6666
10,
6767
actor,
6868
null,
69-
true
69+
true, // useWebRTC
70+
null // thumbnailUrl
7071
);
7172
}
7273

src/test/java/com/back/domain/study/record/controller/StudyRecordControllerTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ private Room createRoom(User owner) {
133133
20, // 최대 20명
134134
owner,
135135
null, // 테마 없음
136-
true // WebRTC 활성화
136+
true, // WebRTC 활성화
137+
null // thumbnailUrl
137138
);
138139

139140
testRoom = roomRepository.save(testRoom);

0 commit comments

Comments
 (0)