Skip to content

Commit c1b6465

Browse files
committed
fix: 룸 썸네일 파일 업로드 + url 저장 하이브리드 구현
1 parent f68ff22 commit c1b6465

File tree

6 files changed

+105
-12
lines changed

6 files changed

+105
-12
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package com.back.domain.file.entity;
22

33
public enum EntityType {
4-
POST, AVATAR, PROFILE
5-
}
4+
POST, AVATAR, PROFILE, ROOM_THUMBNAIL
5+
}

src/main/java/com/back/domain/file/service/FileService.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,77 @@ private void checkAccessPermission(FileAttachment fileAttachment, Long userId) {
164164
throw new CustomException(ErrorCode.FILE_ACCESS_DENIED);
165165
}
166166
}
167+
168+
/**
169+
* URL이 우리 S3 버킷의 파일인지 확인
170+
* @param url 확인할 URL
171+
* @return S3 파일이면 true, 외부 URL이면 false
172+
*/
173+
public boolean isOurS3File(String url) {
174+
if (url == null || url.trim().isEmpty()) {
175+
return false;
176+
}
177+
178+
// S3 URL 패턴 체크
179+
// 패턴 1: https://bucket-name.s3.amazonaws.com/...
180+
// 패턴 2: https://s3.amazonaws.com/bucket-name/...
181+
// 패턴 3: https://bucket-name.s3.ap-northeast-2.amazonaws.com/...
182+
return url.contains(".s3.") && url.contains(".amazonaws.com") && url.contains(bucket);
183+
}
184+
185+
/**
186+
* S3 URL에서 파일명(Key) 추출
187+
* @param url S3 전체 URL
188+
* @return 파일명 (예: "uuid-filename.jpg")
189+
*/
190+
public String extractFileNameFromUrl(String url) {
191+
if (url == null || url.trim().isEmpty()) {
192+
return null;
193+
}
194+
195+
try {
196+
// URL에서 마지막 "/" 이후의 파일명 추출
197+
int lastSlashIndex = url.lastIndexOf('/');
198+
if (lastSlashIndex >= 0 && lastSlashIndex < url.length() - 1) {
199+
return url.substring(lastSlashIndex + 1);
200+
}
201+
} catch (Exception e) {
202+
// 추출 실패 시 null 반환
203+
}
204+
205+
return null;
206+
}
207+
208+
/**
209+
* S3 파일을 파일명으로 삭제
210+
* RoomService 등 다른 도메인에서 썸네일 삭제 시 사용
211+
* @param fileName S3에 저장된 파일명
212+
*/
213+
public void deleteS3FileByName(String fileName) {
214+
if (fileName == null || fileName.trim().isEmpty()) {
215+
return;
216+
}
217+
218+
try {
219+
s3Delete(fileName);
220+
} catch (Exception e) {
221+
// S3 삭제 실패해도 무시 (파일이 이미 없을 수 있음)
222+
// 로그만 남기고 계속 진행
223+
}
224+
}
225+
226+
/**
227+
* URL로 S3 파일 삭제 (권한 체크 없음 - 내부 사용용)
228+
* 우리 S3 파일인지 확인 후 삭제
229+
* @param url 삭제할 파일의 전체 URL
230+
*/
231+
public void deleteS3FileByUrl(String url) {
232+
if (!isOurS3File(url)) {
233+
// 외부 URL이면 삭제하지 않음
234+
return;
235+
}
236+
237+
String fileName = extractFileNameFromUrl(url);
238+
deleteS3FileByName(fileName);
239+
}
167240
}

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

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

19-
// 방 썸네일 이미지 URL (선택)
19+
// 방 썸네일 이미지 URL (선택해서 진행 가능)
20+
// 파일 업로드를 사용하는 경우: 먼저 /api/files/upload로 파일을 업로드하고 받은 URL을 여기에 설정
21+
// 직접 URL을 입력하는 경우: 외부 이미지 URL을 직접 입력하게 됨
2022
@Size(max = 500, message = "썸네일 URL은 500자를 초과할 수 없습니다")
2123
private String thumbnailUrl;
2224

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public class MyRoomResponse {
1515
private String title;
1616
private String description;
1717
private Boolean isPrivate; // 비공개 방 여부 (UI에서 🔒 아이콘 표시용)
18+
private String thumbnailUrl; // 썸네일 이미지 URL
1819
private int currentParticipants;
1920
private int maxParticipants;
2021
private RoomStatus status;
@@ -26,8 +27,9 @@ public static MyRoomResponse of(Room room, long currentParticipants, RoomRole my
2627
.roomId(room.getId())
2728
.title(room.getTitle())
2829
.description(room.getDescription() != null ? room.getDescription() : "")
29-
.isPrivate(room.isPrivate()) // 비공개 방 여부
30-
.currentParticipants((int) currentParticipants) // Redis에서 조회한 실시간 값
30+
.isPrivate(room.isPrivate())
31+
.thumbnailUrl(room.getThumbnailUrl())
32+
.currentParticipants((int) currentParticipants)
3133
.maxParticipants(room.getMaxParticipants())
3234
.status(room.getStatus())
3335
.myRole(myRole)

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public class UpdateRoomSettingsRequest {
2121
private Integer maxParticipants;
2222

2323
// 방 썸네일 이미지 URL (선택)
24+
// 파일 업로드를 사용하는 경우: 먼저 /api/files/upload로 파일을 업로드하고 받은 URL을 여기에 설정
25+
// 직접 URL을 입력하는 경우: 외부 이미지 URL을 직접 입력
26+
// null인 경우: 기존 썸네일 유지
2427
@Size(max = 500, message = "썸네일 URL은 500자를 초과할 수 없습니다")
2528
private String thumbnailUrl;
2629

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

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public class RoomService {
5252
private final SimpMessagingTemplate messagingTemplate;
5353
private final ApplicationEventPublisher eventPublisher;
5454
private final AvatarService avatarService;
55+
private final com.back.domain.file.service.FileService fileService;
5556

5657
/**
5758
* 방 생성 메서드
@@ -78,8 +79,6 @@ public Room createRoom(String title, String description, boolean isPrivate,
7879

7980
RoomMember hostMember = RoomMember.createHost(savedRoom, creator);
8081
roomMemberRepository.save(hostMember);
81-
82-
// savedRoom.incrementParticipant(); // Redis로 이관 - DB 업데이트 제거
8382

8483
log.info("방 생성 완료 - RoomId: {}, Title: {}, CreatorId: {}, WebRTC: {}, Thumbnail: {}",
8584
savedRoom.getId(), title, creatorId, useWebRTC, thumbnailUrl != null ? "설정됨" : "없음");
@@ -196,7 +195,6 @@ public Page<Room> getJoinableRooms(Pageable pageable) {
196195

197196
/**
198197
* 모든 방 조회 (공개 + 비공개 전체)
199-
* 비공개 방은 정보 마스킹
200198
*/
201199
public Page<Room> getAllRooms(Pageable pageable) {
202200
return roomRepository.findAllRooms(pageable);
@@ -228,7 +226,7 @@ public Page<Room> getMyHostingRooms(Long userId, Pageable pageable) {
228226
/**
229227
* 모든 방을 RoomResponse로 변환 (비공개 방 마스킹 포함)
230228
* @param rooms 방 목록
231-
* @return 마스킹된 RoomResponse 리스트
229+
* @return RoomResponse 리스트
232230
*/
233231
public java.util.List<RoomResponse> toRoomResponseListWithMasking(java.util.List<Room> rooms) {
234232
java.util.List<Long> roomIds = rooms.stream()
@@ -257,8 +255,6 @@ public Room getRoomDetail(Long roomId, Long userId) {
257255
Room room = roomRepository.findById(roomId)
258256
.orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND));
259257

260-
// ⭐ 비공개 방 접근 제한 제거 - 모든 사용자가 조회 가능
261-
// (프론트엔드에서 입장 시 로그인 체크)
262258

263259
return room;
264260
}
@@ -285,8 +281,18 @@ public void updateRoomSettings(Long roomId, String title, String description,
285281
throw new CustomException(ErrorCode.BAD_REQUEST);
286282
}
287283

284+
// 기존 썸네일 URL 저장
285+
String oldThumbnailUrl = room.getRawThumbnailUrl();
286+
287+
// 방 설정 업데이트
288288
room.updateSettings(title, description, maxParticipants, thumbnailUrl);
289289

290+
// 썸네일이 변경되었고, 기존 썸네일이 S3 파일인 경우 삭제
291+
if (oldThumbnailUrl != null && !oldThumbnailUrl.equals(thumbnailUrl)) {
292+
fileService.deleteS3FileByUrl(oldThumbnailUrl);
293+
log.info("기존 썸네일 삭제 완료 - RoomId: {}, OldUrl: {}", roomId, oldThumbnailUrl);
294+
}
295+
290296
log.info("방 설정 변경 완료 - RoomId: {}, UserId: {}, Thumbnail: {}",
291297
roomId, userId, thumbnailUrl != null ? "변경됨" : "없음");
292298
}
@@ -353,6 +359,13 @@ public void terminateRoom(Long roomId, Long userId) {
353359
throw new CustomException(ErrorCode.NOT_ROOM_MANAGER);
354360
}
355361

362+
// 썸네일이 S3 파일인 경우 삭제
363+
String thumbnailUrl = room.getRawThumbnailUrl();
364+
if (thumbnailUrl != null) {
365+
fileService.deleteS3FileByUrl(thumbnailUrl);
366+
log.info("방 종료 - 썸네일 삭제 완료 - RoomId: {}, ThumbnailUrl: {}", roomId, thumbnailUrl);
367+
}
368+
356369
room.terminate();
357370

358371
// Redis에서 모든 온라인 사용자 제거
@@ -530,7 +543,7 @@ public List<RoomMember> getRoomMembers(Long roomId, Long userId) {
530543
Room room = roomRepository.findById(roomId)
531544
.orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND));
532545

533-
// ⭐ 비공개 방 접근 제한 제거 - 모든 사용자가 조회 가능
546+
// 모든 사용자가 조회 가능
534547

535548
// 1. Redis에서 온라인 사용자 ID 조회
536549
Set<Long> onlineUserIds = roomParticipantService.getParticipants(roomId);

0 commit comments

Comments
 (0)