-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathChatMessageService.java
More file actions
143 lines (121 loc) · 6.34 KB
/
Copy pathChatMessageService.java
File metadata and controls
143 lines (121 loc) · 6.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package com.knoc.chat.service;
import com.knoc.chat.dto.ChatMessageResponse;
import com.knoc.chat.entity.ChatMessage;
import com.knoc.chat.entity.ChatRoom;
import com.knoc.chat.entity.ChatRoomStatus;
import com.knoc.chat.entity.MessageType;
import com.knoc.chat.repository.ChatMessageRepository;
import com.knoc.chat.repository.ChatRoomRepository;
import com.knoc.global.exception.BusinessException;
import com.knoc.global.exception.ErrorCode;
import com.knoc.member.Member;
import com.knoc.member.MemberRepository;
import com.knoc.order.entity.Order;
import com.knoc.order.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class ChatMessageService {
private final ChatRoomRepository chatRoomRepository;
private final MemberRepository memberRepository;
private final ChatMessageRepository chatMessageRepository;
private final SimpMessagingTemplate messagingTemplate;
private final ChatRoomService chatRoomService;
private final OrderRepository orderRepository;
// 메시지 목록에서 PAYMENT_REQUESTED 타입만 골라 (orderId, amount) 맵을 구성한다.
// Thymeleaf 첫 렌더링과 페이지네이션 응답 양쪽에서 결제 버튼 금액 표시에 사용.
public Map<Long, Integer> buildOrderAmounts(List<ChatMessage> messages) {
List<Long> paymentOrderIds = messages.stream()
.filter(m -> m.getMessageType() == MessageType.PAYMENT_REQUESTED && m.getReferenceId() != null)
.map(ChatMessage::getReferenceId)
.toList();
if (paymentOrderIds.isEmpty()) return Collections.emptyMap();
return orderRepository.findAllById(paymentOrderIds).stream()
.collect(Collectors.toMap(Order::getId, Order::getAmount));
}
@Transactional
public void sendMessage(Long roomId, String email, String content) {
// 1. 채팅방 조회
ChatRoom chatRoom = chatRoomRepository.findById(roomId)
.orElseThrow(() -> new BusinessException(ErrorCode.CHATROOM_NOT_FOUND));
if(chatRoom.getStatus() == ChatRoomStatus.CLOSED) {
throw new BusinessException(ErrorCode.CHATROOM_ALREADY_CLOSED);
}
// 2. 발신자 조회
Member sender = memberRepository.findByEmail(email)
.orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND));
// 3. 참여자 여부 검증
chatRoomService.verifyParticipant(chatRoom, sender);
// 4. ChatMessage 엔티티 생성 & 저장
ChatMessage chatMessage = ChatMessage.builder()
.chatRoom(chatRoom)
.sender(sender)
.messageType(MessageType.USER)
.content(content)
.referenceId(null)
.build();
ChatMessage savedMessage = chatMessageRepository.save(chatMessage);
// 5. Response DTO 생성
ChatMessageResponse responsePayload = ChatMessageResponse.builder()
.id(savedMessage.getId())
.senderNickname(sender.getNickname())
.content(savedMessage.getContent())
.createdAt(savedMessage.getCreatedAt())
.messageType(savedMessage.getMessageType())
.referenceId(savedMessage.getReferenceId()) // USER 메시지는 null
// amount는 생략 -> null (PAYMENT_REQUESTED에서만 의미 있음)
.roomId(roomId)
.build();
// 6. 수신자/발신자 양쪽에 1:1 queue 전송
String receiverEmail = sender.getId().equals(chatRoom.getJunior().getId())
? chatRoom.getSenior().getEmail()
: chatRoom.getJunior().getEmail();
messagingTemplate.convertAndSendToUser(receiverEmail, "/queue/chat", responsePayload);
messagingTemplate.convertAndSendToUser(sender.getEmail(), "/queue/chat", responsePayload);
}
@Transactional(readOnly = true)
public List<ChatMessageResponse> getPreviousMessages(Long roomId, Long before, String email) {
ChatRoom chatRoom = chatRoomRepository.findById(roomId)
.orElseThrow(() -> new BusinessException(ErrorCode.CHATROOM_NOT_FOUND));
Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND));
chatRoomService.verifyParticipant(chatRoom, member);
List<ChatMessage> messages = chatMessageRepository
.findByChatRoomAndIdLessThanOrderByIdDesc(chatRoom, before, PageRequest.of(0, 20));
// 주니어 전용 시스템 메시지(REVIEW_REQUESTED)는 시니어 화면에서는 노출하지 않는다.
boolean isSenior = chatRoom.getSenior() != null
&& chatRoom.getSenior().getId() != null
&& member.getId() != null
&& chatRoom.getSenior().getId().equals(member.getId());
if (isSenior) {
messages = messages.stream()
.filter(m -> m.getMessageType() != MessageType.REVIEW_REQUESTED)
.toList();
}
messages = new ArrayList<>(messages);
Collections.reverse(messages);
// 이번 페이지에 포함된 PAYMENT_REQUESTED 메시지들의 orderId만 모아 한 번에 금액 조회
Map<Long, Integer> amountByOrderId = buildOrderAmounts(messages);
return messages.stream()
.map(m -> ChatMessageResponse.builder()
.id(m.getId())
.senderNickname(m.getSender() != null ? m.getSender().getNickname() : "SYSTEM")
.content(m.getContent())
.createdAt(m.getCreatedAt())
.messageType(m.getMessageType())
.referenceId(m.getReferenceId())
.amount(m.getMessageType() == MessageType.PAYMENT_REQUESTED
? amountByOrderId.get(m.getReferenceId()) : null)
.build())
.collect(Collectors.toList());
}
}