Skip to content

Commit 7bbf833

Browse files
authored
Merge pull request #82 from DorumDorum/feat/#81/동시성-제어-보강-및-상태-정합성-개선
[FEAT] 동시성 제어 보강 및 상태 정합성 개선
2 parents 7bcbd97 + d99a61b commit 7bbf833

File tree

82 files changed

+1047
-141
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+1047
-141
lines changed

src/main/java/com/project/dorumdorum/domain/calendar/application/usecase/LoadCalendarEventsUseCase.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ public class LoadCalendarEventsUseCase {
1919
private final CalendarEventService calendarEventService;
2020
private final CalendarEventMapper calendarEventMapper;
2121

22+
/**
23+
* 기간 내 캘린더 일정 조회
24+
* - 시작일~종료일 범위의 일정을 조회
25+
* - 응답 DTO 목록으로 변환해 반환
26+
*/
2227
public List<CalendarEventResponse> execute(LocalDate startDate, LocalDate endDate) {
2328
List<CalendarEvent> events = calendarEventService.loadBetween(startDate, endDate);
2429
return calendarEventMapper.toResponseList(events);

src/main/java/com/project/dorumdorum/domain/chat/application/usecase/GetChatRoomMembersUseCase.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ public class GetChatRoomMembersUseCase {
2424
private final RoommateService roommateService;
2525
private final UserService userService;
2626

27+
/**
28+
* 채팅방 멤버 목록 조회
29+
* - 요청 사용자가 채팅방 멤버인지 검증
30+
* - 멤버별 닉네임과 방장 여부를 조회
31+
* - 방장이 먼저 보이도록 정렬해 반환
32+
*/
2733
@Transactional(readOnly = true)
2834
public List<ChatRoomMemberResponse> execute(String chatRoomNo, String requestingUserNo) {
2935
ChatRoom chatRoom = chatRoomService.findByChatRoomNo(chatRoomNo);

src/main/java/com/project/dorumdorum/domain/chat/application/usecase/GetOrCreateDirectChatRoomUseCase.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ public class GetOrCreateDirectChatRoomUseCase {
2323

2424
/**
2525
* 지원자-방장 1:1 채팅방 조회 또는 생성
26-
* - 이미 존재하면 기존 채팅방 반환
27-
* - 없으면 생성 후 반환
28-
* - 호출자는 방장 또는 지원자여야 함
26+
* - 호출자가 방장 또는 지원자인지 검증
27+
* - 기존 채팅방이 있으면 그대로 반환
28+
* - 없으면 생성 후 양쪽 사용자를 입장시켜 반환
2929
*/
3030
public String execute(String callerUserNo, String roomNo, String applicantUserNo) {
3131
Room room = roomService.findById(roomNo);
@@ -46,7 +46,10 @@ public String execute(String callerUserNo, String roomNo, String applicantUserNo
4646
}
4747

4848
/**
49-
* 이벤트 리스너에서 사용 — 인증 없이 채팅방 생성 (방 지원 시 자동 생성)
49+
* 지원자-방장 1:1 채팅방 선생성
50+
* - 이벤트 리스너에서 인증 없이 호출
51+
* - 기존 채팅방이 있으면 그대로 반환
52+
* - 없으면 생성 후 양쪽 사용자를 입장시켜 반환
5053
*/
5154
public String createIfAbsent(String roomNo, String applicantUserNo, String hostUserNo) {
5255
return chatRoomService.findDirectChatRoom(roomNo, applicantUserNo)

src/main/java/com/project/dorumdorum/domain/chat/application/usecase/JoinChatRoomUseCase.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ public class JoinChatRoomUseCase {
2929
private final SimpMessagingTemplate messagingTemplate;
3030

3131
/**
32-
* 방장이 룸메이트 승인(RoommateAcceptedEvent) → 채팅방 입장
33-
* - 채팅방 없으면 생성 후 방장 + 신규 멤버 입장
34-
* - 채팅방 있으면 신규 멤버만 입장 (중복 방지)
35-
* - 입장 시스템 메시지 저장
32+
* 룸메이트 승인 이벤트 처리
33+
* - 그룹 채팅방이 없으면 생성하고 방장을 먼저 입장
34+
* - 승인된 사용자를 중복 없이 입장 처리
35+
* - 입장 시스템 메시지를 저장하고 실시간 전송
3636
*/
3737
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
3838
@Transactional(propagation = Propagation.REQUIRES_NEW)

src/main/java/com/project/dorumdorum/domain/chat/application/usecase/LeaveChatRoomUseCase.java

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,42 +34,72 @@ public class LeaveChatRoomUseCase {
3434
private final UserService userService;
3535
private final SimpMessagingTemplate messagingTemplate;
3636

37+
/**
38+
* 채팅방 퇴장 처리
39+
* - 채팅방 멤버를 조회하고 방장 퇴장 가능 여부를 검증
40+
* - 그룹 채팅방이면 룸메이트와 방 인원수를 함께 갱신
41+
* - 마지막 멤버면 방을 삭제하고, 아니면 퇴장 메시지를 전송
42+
*/
3743
@Transactional
3844
public void execute(String chatRoomNo, String userNo) {
3945
ChatRoom chatRoom = chatRoomService.findByChatRoomNo(chatRoomNo);
4046
ChatRoomMember member = chatRoomMemberService.findByChatRoomAndUserNo(chatRoom, userNo);
4147
long memberCount = chatRoomMemberService.countByChatRoom(chatRoom);
4248

43-
if (memberCount > 1) {
44-
boolean isHost = roommateService.isHostOfRoom(userNo, chatRoom.getRoomNo());
45-
if (isHost) {
46-
throw new RestApiException(HOST_CANNOT_LEAVE);
47-
}
49+
if (ChatRoomType.GROUP.equals(chatRoom.getChatRoomType())) {
50+
leaveGroupChatRoom(chatRoom, member, userNo, memberCount);
51+
return;
52+
}
53+
54+
leaveDirectChatRoom(chatRoom, member, userNo, memberCount);
55+
}
56+
57+
private void leaveGroupChatRoom(ChatRoom chatRoom, ChatRoomMember member, String userNo, long memberCount) {
58+
Room room = roomService.findByIdForUpdate(chatRoom.getRoomNo());
59+
60+
if (memberCount > 1 && roommateService.isHost(userNo, room)) {
61+
throw new RestApiException(HOST_CANNOT_LEAVE);
4862
}
4963

5064
chatRoomMemberService.leave(member);
65+
roommateService.leaveRoom(userNo, chatRoom.getRoomNo());
66+
room.minusCurrentMate();
5167

52-
// GROUP 채팅방인 경우에만 룸메이트 삭제 및 방 인원수 감소
53-
if (ChatRoomType.GROUP.equals(chatRoom.getChatRoomType())) {
54-
roommateService.leaveRoom(userNo, chatRoom.getRoomNo());
55-
Room room = roomService.findById(chatRoom.getRoomNo());
56-
room.minusCurrentMate();
68+
if (memberCount == 1) {
69+
chatMessageService.deleteAllByChatRoom(chatRoom.getChatRoomNo());
70+
chatRoomService.delete(chatRoom);
71+
return;
5772
}
5873

74+
sendLeaveSystemMessage(chatRoom, userNo);
75+
}
76+
77+
private void leaveDirectChatRoom(ChatRoom chatRoom, ChatRoomMember member, String userNo, long memberCount) {
78+
if (memberCount > 1 && roommateService.isHostOfRoom(userNo, chatRoom.getRoomNo())) {
79+
throw new RestApiException(HOST_CANNOT_LEAVE);
80+
}
81+
82+
chatRoomMemberService.leave(member);
83+
5984
if (memberCount == 1) {
60-
// 마지막 멤버가 나가는 경우: 메시지 먼저 삭제 후 채팅방 삭제 (FK 제약 위반 방지)
6185
chatMessageService.deleteAllByChatRoom(chatRoom.getChatRoomNo());
6286
chatRoomService.delete(chatRoom);
63-
} else {
64-
User leavingUser = userService.findById(userNo);
65-
String displayName = (leavingUser.getNickname() != null && !leavingUser.getNickname().isBlank())
66-
? leavingUser.getNickname() : leavingUser.getName();
67-
String content = displayName + "가 퇴장했습니다.";
68-
ChatMessage message = chatMessageService.save(chatRoom, "SYSTEM", content, MessageType.SYSTEM, 0);
69-
ChatMessageResponse response = new ChatMessageResponse(
70-
message.getMessageNo(), chatRoomNo,
71-
"SYSTEM", null, content, MessageType.SYSTEM.name(), message.getCreatedAt());
72-
messagingTemplate.convertAndSend("/topic/chat-room/" + chatRoomNo, response);
87+
return;
7388
}
89+
90+
sendLeaveSystemMessage(chatRoom, userNo);
91+
}
92+
93+
private void sendLeaveSystemMessage(ChatRoom chatRoom, String userNo) {
94+
String chatRoomNo = chatRoom.getChatRoomNo();
95+
User leavingUser = userService.findById(userNo);
96+
String displayName = (leavingUser.getNickname() != null && !leavingUser.getNickname().isBlank())
97+
? leavingUser.getNickname() : leavingUser.getName();
98+
String content = displayName + "가 퇴장했습니다.";
99+
ChatMessage message = chatMessageService.save(chatRoom, "SYSTEM", content, MessageType.SYSTEM, 0);
100+
ChatMessageResponse response = new ChatMessageResponse(
101+
message.getMessageNo(), chatRoomNo,
102+
"SYSTEM", null, content, MessageType.SYSTEM.name(), message.getCreatedAt());
103+
messagingTemplate.convertAndSend("/topic/chat-room/" + chatRoomNo, response);
74104
}
75105
}

src/main/java/com/project/dorumdorum/domain/chat/application/usecase/LoadChatMessagesUseCase.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ public class LoadChatMessagesUseCase {
3333
private final UserService userService;
3434
private static final int LIMIT = 30;
3535

36+
/**
37+
* 채팅 메시지 목록 커서 조회
38+
* - 요청 사용자의 채팅방 참여 여부와 입장 시점을 확인
39+
* - 커서 기준으로 메시지를 조회
40+
* - 발신자 표시명을 보강해 다음 커서와 함께 반환
41+
*/
3642
@Transactional(readOnly = true)
3743
public CursorPage<ChatMessageSummary> execute(String chatRoomNo, String userNo, String cursor) {
3844
ChatRoom chatRoom = chatRoomService.findByChatRoomNo(chatRoomNo);

src/main/java/com/project/dorumdorum/domain/chat/application/usecase/LoadMyChatRoomsUseCase.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public class LoadMyChatRoomsUseCase {
1414

1515
private final ChatRoomService chatRoomService;
1616

17+
/**
18+
* 내 채팅방 목록 조회
19+
* - 사용자가 속한 채팅방 목록을 조회
20+
* - 최근 메시지 기준 요약 정보로 반환
21+
*/
1722
@Transactional(readOnly = true)
1823
public List<ChatRoomSummary> execute(String userNo) {
1924
return chatRoomService.findMyChatRooms(userNo);

src/main/java/com/project/dorumdorum/domain/chat/application/usecase/MarkChatRoomReadUseCase.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package com.project.dorumdorum.domain.chat.application.usecase;
22

3-
import com.project.dorumdorum.domain.chat.domain.entity.ChatRoom;
43
import com.project.dorumdorum.domain.chat.domain.entity.ChatRoomMember;
54
import com.project.dorumdorum.domain.chat.domain.service.ChatMessageService;
65
import com.project.dorumdorum.domain.chat.domain.service.ChatRoomMemberService;
7-
import com.project.dorumdorum.domain.chat.domain.service.ChatRoomService;
86
import lombok.RequiredArgsConstructor;
97
import org.springframework.stereotype.Service;
108
import org.springframework.transaction.annotation.Transactional;
@@ -15,14 +13,18 @@
1513
@RequiredArgsConstructor
1614
public class MarkChatRoomReadUseCase {
1715

18-
private final ChatRoomService chatRoomService;
1916
private final ChatRoomMemberService chatRoomMemberService;
2017
private final ChatMessageService chatMessageService;
2118

19+
/**
20+
* 채팅방 읽음 처리
21+
* - 사용자의 채팅방 멤버 정보를 조회
22+
* - 마지막 읽은 시점 이후 메시지의 안 읽은 수를 감소
23+
* - 현재 시각으로 마지막 읽은 시점을 갱신
24+
*/
2225
@Transactional
2326
public void execute(String chatRoomNo, String userNo) {
24-
ChatRoom chatRoom = chatRoomService.findByChatRoomNo(chatRoomNo);
25-
ChatRoomMember member = chatRoomMemberService.findByChatRoomAndUserNo(chatRoom, userNo);
27+
ChatRoomMember member = chatRoomMemberService.findByChatRoomNoAndUserNoForUpdate(chatRoomNo, userNo);
2628

2729
LocalDateTime fromTime = member.getLastReadAt() != null
2830
? member.getLastReadAt()

src/main/java/com/project/dorumdorum/domain/chat/application/usecase/SendGroupChatMessageUseCase.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ public class SendGroupChatMessageUseCase {
3131
private final NotificationRequestPublisher notificationRequestPublisher;
3232
private final UserService userService;
3333

34+
/**
35+
* 그룹 채팅 메시지 전송
36+
* - 발신자가 채팅방 멤버인지 검증
37+
* - 메시지를 저장하고 채팅방 최근 메시지를 갱신
38+
* - 웹소켓으로 메시지를 전송하고 다른 멤버에게 알림을 발행
39+
*/
3440
@RateLimited(tag = "chat-message", key = "#senderNo + ':' + #chatRoomNo")
3541
@Transactional
3642
public void send(String chatRoomNo, String senderNo, String content) {

src/main/java/com/project/dorumdorum/domain/chat/domain/entity/ChatRoom.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import jakarta.persistence.Id;
1010
import jakarta.persistence.Index;
1111
import jakarta.persistence.Table;
12-
import jakarta.persistence.UniqueConstraint;
1312
import lombok.*;
1413

1514
import java.time.LocalDateTime;
@@ -18,11 +17,7 @@
1817
@Table(
1918
indexes = {
2019
@Index(name = "idx_chat_room_last_message_at", columnList = "last_message_at")
21-
},
22-
uniqueConstraints = @UniqueConstraint(
23-
name = "uk_chat_room_direct",
24-
columnNames = {"room_no", "chat_room_type", "applicant_user_no"}
25-
)
20+
}
2621
)
2722
@Getter
2823
@NoArgsConstructor(access = AccessLevel.PROTECTED)

0 commit comments

Comments
 (0)