Skip to content

Commit fe71978

Browse files
committed
2 parents 81c353a + 33c5d40 commit fe71978

File tree

22 files changed

+1949
-39
lines changed

22 files changed

+1949
-39
lines changed

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

Lines changed: 384 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 144 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,40 +19,172 @@
1919
@AllArgsConstructor
2020
public class Room extends BaseEntity {
2121
private String title;
22-
2322
private String description;
24-
2523
private boolean isPrivate;
26-
2724
private String password;
28-
2925
private int maxParticipants;
30-
3126
private boolean isActive;
32-
3327
private boolean allowCamera;
34-
3528
private boolean allowAudio;
36-
3729
private boolean allowScreenShare;
3830

31+
// 방 상태
32+
@Enumerated(EnumType.STRING)
33+
@Column(nullable = false)
34+
private RoomStatus status = RoomStatus.WAITING;
35+
// 현재 참여자
36+
@Column(nullable = false)
37+
private int currentParticipants = 0;
38+
// 방장
3939
@ManyToOne(fetch = FetchType.LAZY)
4040
@JoinColumn(name = "created_by")
4141
private User createdBy;
42-
42+
// 테마
4343
@ManyToOne(fetch = FetchType.LAZY)
4444
@JoinColumn(name = "theme_id")
4545
private RoomTheme theme;
4646

47+
// 연관관계 설정
4748
@OneToMany(mappedBy = "room", cascade = CascadeType.ALL, orphanRemoval = true)
4849
private List<RoomMember> roomMembers = new ArrayList<>();
49-
50+
// 채팅 메시지
5051
@OneToMany(mappedBy = "room", cascade = CascadeType.ALL, orphanRemoval = true)
5152
private List<RoomChatMessage> roomChatMessages = new ArrayList<>();
52-
53+
// 참가자 기록
5354
@OneToMany(mappedBy = "room", cascade = CascadeType.ALL, orphanRemoval = true)
5455
private List<RoomParticipantHistory> roomParticipantHistories = new ArrayList<>();
55-
56+
// 스터디 기록
5657
@OneToMany(mappedBy = "room", cascade = CascadeType.ALL, orphanRemoval = true)
5758
public List<StudyRecord> studyRecords = new ArrayList<>();
59+
60+
61+
/**
62+
* 방에 입장할 수 있는지 확인
63+
* 사용 상황: 사용자가 방 입장을 시도할 때 입장 가능 여부를 미리 체크
64+
방이 활성화되어 있고, 입장 가능한 상태이며, 정원이 초과되지 않은 경우
65+
*/
66+
public boolean canJoin() {
67+
return isActive && status.isJoinable() && currentParticipants < maxParticipants;
68+
}
69+
70+
/**
71+
* 방의 정원이 가득 찼는지 확인
72+
방 목록에서 "만석" 표시하거나, 입장 제한할 때
73+
*/
74+
public boolean isFull() {
75+
return currentParticipants >= maxParticipants;
76+
}
77+
78+
/**
79+
* 참가자 수 증가 (최대 정원까지만)
80+
누군가 방에 입장했을 때 참가자 수를 1 증가시킴
81+
최대 정원을 초과하지 않도록 체크
82+
*/
83+
public void incrementParticipant() {
84+
if (currentParticipants < maxParticipants) {
85+
this.currentParticipants++;
86+
}
87+
}
88+
89+
/**
90+
* 참가자 수 감소 (0 이하로는 감소하지 않음)
91+
누군가 방에서 나갔을 때 참가자 수를 1 감소시킴
92+
음수가 되지 않도록 체크
93+
*/
94+
public void decrementParticipant() {
95+
if (this.currentParticipants > 0) {
96+
this.currentParticipants--;
97+
}
98+
}
99+
100+
/**
101+
* 비밀번호 입력이 필요한 방인지 확인
102+
비공개 방에 입장할 때 비밀번호 입력 폼을 보여줄지 결정
103+
비공개 방이면서 실제로 비밀번호가 설정되어 있는 경우
104+
*/
105+
public boolean needsPassword() {
106+
return isPrivate && password != null && !password.trim().isEmpty();
107+
}
108+
109+
/**
110+
* 방을 활성 상태로 변경
111+
방장이 스터디를 시작할 때 또는 대기 중인 방을 활성화할 때
112+
*/
113+
public void activate() {
114+
this.status = RoomStatus.ACTIVE;
115+
this.isActive = true;
116+
}
117+
118+
/**
119+
* 방을 일시 정지 상태로 변경
120+
*/
121+
public void pause() {
122+
this.status = RoomStatus.PAUSED;
123+
}
124+
125+
/**
126+
* 방을 종료 상태로 변경
127+
방장이 스터디를 완전히 끝내거나, 빈 방을 자동 정리할 때 (종료 처리를 어떻게 뺄지에 따라 변경 될 예정)
128+
*/
129+
public void terminate() {
130+
this.status = RoomStatus.TERMINATED;
131+
this.isActive = false;
132+
}
133+
134+
/**
135+
* 특정 사용자가 이 방의 소유자(방장)인지 확인
136+
방 설정 변경, 방 종료, 멤버 추방 등의 권한이 필요한 작업 전에 체크
137+
*/
138+
public boolean isOwner(Long userId) {
139+
return createdBy != null && createdBy.getId().equals(userId);
140+
}
141+
142+
/**
143+
* 방 생성을 위한 정적 팩토리 메서드
144+
새로운 방을 생성할 때 모든 기본값을 설정 해주는 초기 메서드
145+
기본 상태에서 방장이 임의로 변형하고 싶은 부분만 변경해서 사용 가능
146+
*/
147+
public static Room create(String title, String description, boolean isPrivate,
148+
String password, int maxParticipants, User creator, RoomTheme theme) {
149+
Room room = new Room();
150+
room.title = title;
151+
room.description = description;
152+
room.isPrivate = isPrivate;
153+
room.password = password;
154+
room.maxParticipants = maxParticipants;
155+
room.isActive = true; // 생성 시 기본적으로 활성화
156+
room.allowCamera = true; // 기본적으로 카메라 허용
157+
room.allowAudio = true; // 기본적으로 오디오 허용
158+
room.allowScreenShare = true; // 기본적으로 화면 공유 허용
159+
room.status = RoomStatus.WAITING; // 생성 시 대기 상태
160+
room.currentParticipants = 0; // 생성 시 참가자 0명
161+
room.createdBy = creator;
162+
room.theme = theme;
163+
164+
return room;
165+
}
166+
167+
/**
168+
* 방 설정 일괄 업데이트 메서드
169+
방장이 방 설정을 변경할 때 여러 필드를 한 번에 업데이트
170+
주된 생성 이유.. rtc 단체 제어를 위해 잡아놓았음. 잡아준 필드 변경 가능성 농후!!
171+
*/
172+
public void updateSettings(String title, String description, int maxParticipants,
173+
boolean allowCamera, boolean allowAudio, boolean allowScreenShare) {
174+
this.title = title;
175+
this.description = description;
176+
this.maxParticipants = maxParticipants;
177+
this.allowCamera = allowCamera;
178+
this.allowAudio = allowAudio;
179+
this.allowScreenShare = allowScreenShare;
180+
}
181+
182+
/**
183+
* 방 비밀번호 변경 메서드
184+
방장이 방의 비밀번호를 변경할 때
185+
별도 메서드로 분리한 이유: 비밀번호는 보안상 별도로 관리되어야 하기 때문 (ai의 추천)
186+
*/
187+
public void updatePassword(String newPassword) {
188+
this.password = newPassword;
189+
}
58190
}

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

Lines changed: 170 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,187 @@
88

99
import java.time.LocalDateTime;
1010

11+
/*
12+
RoomMember 엔티티 - 방과 사용자 간의 멤버십 관계를 나타냄
13+
연관관계 :
14+
- Room (1) : RoomMember (N) - 한 방에 여러 멤버가 있을 수 있음
15+
- User (1) : RoomMember (N) - 한 사용자가 여러 방의 멤버가 될 수 있음
16+
@JoinColumn vs @JoinTable 선택 이유:
17+
- @JoinColumn: 외래키를 이용한 직접 관계 (현재 변경)
18+
- @JoinTable: 별도의 연결 테이블을 만드는 관계
19+
RoomMember 테이블에서 그냥 room_id와 user_id 외래키로 직접 연결.
20+
*/
1121
@Entity
1222
@NoArgsConstructor
1323
@Getter
24+
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"room_id", "user_id"}))
1425
public class RoomMember extends BaseEntity {
26+
27+
// 방 정보 - 어떤 방의 멤버인지
1528
@ManyToOne(fetch = FetchType.LAZY)
16-
@JoinColumn(name = "room_id")
29+
@JoinColumn(name = "room_id") // room_member 테이블의 room_id 컬럼이 room 테이블의 id를 참조
1730
private Room room;
1831

32+
// 사용자 정보 - 누가 이 방의 멤버인지
1933
@ManyToOne(fetch = FetchType.LAZY)
20-
@JoinTable(name = "user_id")
34+
@JoinColumn(name = "user_id") // room_member 테이블의 user_id 컬럼이 users 테이블의 id를 참조
2135
private User user;
2236

37+
// 방 내에서의 역할 (방장, 부방장, 멤버, 방문객)
2338
@Enumerated(EnumType.STRING)
24-
private RoomRole role;
39+
@Column(nullable = false)
40+
private RoomRole role = RoomRole.VISITOR;
2541

26-
private LocalDateTime joinedAt;
42+
// 멤버십 기본 정보
43+
@Column(nullable = false)
44+
private LocalDateTime joinedAt; // 방에 처음 입장한 시간
45+
private LocalDateTime lastActiveAt; // 마지막으로 활동한 시간
2746

28-
private LocalDateTime lastActiveAt;
47+
// 실시간 상태 관리 필드들
48+
@Column(nullable = false)
49+
private boolean isOnline = false; // 현재 방에 온라인 상태인지
50+
51+
private String connectionId; // WebSocket 연결 ID (실시간 통신용)
52+
53+
private LocalDateTime lastHeartbeat; // 마지막 heartbeat 시간 (연결 상태 확인용)
54+
55+
// 💡 권한 확인 메서드들 (RoomRole enum의 메서드를 위임)
56+
57+
/**
58+
* 방 관리 권한이 있는지 확인 (방장, 부방장)
59+
방 설정 변경, 공지사항 작성 등의 권한이 필요할 때
60+
*/
61+
public boolean canManageRoom() {
62+
return role.canManageRoom();
63+
}
64+
65+
/**
66+
* 멤버 추방 권한이 있는지 확인 (방장, 부방장)
67+
다른 멤버를 추방하려고 할 때
68+
*/
69+
public boolean canKickMember() {
70+
return role.canKickMember();
71+
}
72+
73+
/**
74+
* 공지사항 관리 권한이 있는지 확인 (방장, 부방장)
75+
공지사항을 작성하거나 삭제할 때
76+
*/
77+
public boolean canManageNotices() {
78+
return role.canManageNotices();
79+
}
80+
81+
/**
82+
* 방장인지 확인
83+
방 소유자만 가능한 작업 (방 삭제, 호스트 권한 이양 등)
84+
*/
85+
public boolean isHost() {
86+
return role.isHost();
87+
}
88+
89+
/**
90+
* 정식 멤버인지 확인 (방문객이 아닌 멤버, 부방장, 방장)
91+
멤버만 접근 가능한 기능 (파일 업로드, 학습 기록 등)
92+
*/
93+
public boolean isMember() {
94+
return role.isMember();
95+
}
96+
97+
/**
98+
* 현재 활성 상태인지 확인
99+
온라인 멤버 목록 표시, 비활성 사용자 정리 등
100+
온라인 상태이고 최근 5분 이내에 heartbeat가 있었던 경우
101+
*/
102+
public boolean isActive() {
103+
return isOnline && lastHeartbeat != null &&
104+
lastHeartbeat.isAfter(LocalDateTime.now().minusMinutes(5));
105+
}
106+
107+
108+
/**
109+
기본 멤버 생성 메서드, 처음 입장 시 사용
110+
*/
111+
public static RoomMember create(Room room, User user, RoomRole role) {
112+
RoomMember member = new RoomMember();
113+
member.room = room;
114+
member.user = user;
115+
member.role = role;
116+
member.joinedAt = LocalDateTime.now();
117+
member.lastActiveAt = LocalDateTime.now();
118+
member.isOnline = true; // 생성 시 온라인 상태
119+
member.lastHeartbeat = LocalDateTime.now();
120+
121+
return member;
122+
}
123+
124+
// 방장 멤버 생성 -> 새로운 방을 생성할 때 방 생성자를 방장으로 등록
125+
public static RoomMember createHost(Room room, User user) {
126+
return create(room, user, RoomRole.HOST);
127+
}
128+
129+
/**
130+
* 일반 멤버 생성, 권한 자동 변경
131+
- 비공개 방에서 초대받은 사용자를 정식 멤버로 등록할 때 (로직 검토 중)
132+
*/
133+
public static RoomMember createMember(Room room, User user) {
134+
return create(room, user, RoomRole.MEMBER);
135+
}
136+
137+
/**
138+
* 방문객 생성
139+
* 사용 상황: 공개 방에 처음 입장하는 사용자를 임시 방문객으로 등록
140+
*/
141+
public static RoomMember createVisitor(Room room, User user) {
142+
return create(room, user, RoomRole.VISITOR);
143+
}
144+
145+
/**
146+
* 멤버의 역할 변경
147+
방장이 멤버를 부방장으로 승격시키거나 강등시킬 때
148+
*/
149+
public void updateRole(RoomRole newRole) {
150+
this.role = newRole;
151+
}
152+
153+
/**
154+
* 온라인 상태 변경
155+
* 사용 상황: 멤버가 방에 입장하거나 퇴장할 때
156+
활동 시간도 함께 업데이트, 온라인이 되면 heartbeat도 갱신
157+
*/
158+
public void updateOnlineStatus(boolean online) {
159+
this.isOnline = online;
160+
this.lastActiveAt = LocalDateTime.now();
161+
if (online) {
162+
this.lastHeartbeat = LocalDateTime.now();
163+
}
164+
}
165+
166+
/**
167+
* WebSocket 연결 ID 업데이트
168+
* 사용 상황: 멤버가 웹소켓으로 방에 연결될 때
169+
+ heartbeat도 함께 갱신
170+
*/
171+
public void updateConnectionId(String connectionId) {
172+
this.connectionId = connectionId;
173+
this.lastHeartbeat = LocalDateTime.now();
174+
}
175+
176+
/**
177+
* 사용 : 클라이언트에서 주기적으로 서버에 연결 상태를 알릴 때
178+
* 목적: 연결이 끊어진 멤버를 자동으로 감지하기 위해 사용, 별도의 다른 것으로 변경 가능
179+
*/
180+
public void heartbeat() {
181+
this.lastHeartbeat = LocalDateTime.now();
182+
this.lastActiveAt = LocalDateTime.now();
183+
this.isOnline = true;
184+
}
185+
186+
/**
187+
* 방 퇴장 처리 (명시적 퇴장과 연결 끊김 상태 로직 분할 예정임.. 일단은 임시로 통합 상태)
188+
멤버가 방을 나가거나 연결이 끊어졌을 때, 오프라인 상태로 변경하고 연결 ID 제거
189+
*/
190+
public void leave() {
191+
this.isOnline = false;
192+
this.connectionId = null;
193+
}
29194
}

0 commit comments

Comments
 (0)