Skip to content

Commit f0e6336

Browse files
feat: 채팅 메시지 저장 구현 완료
1 parent 2f28f6e commit f0e6336

File tree

5 files changed

+201
-202
lines changed

5 files changed

+201
-202
lines changed

thefirsttake/src/main/java/com/thefirsttake/app/chat/controller/ChatMessageController.java

Lines changed: 84 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import com.thefirsttake.app.chat.dto.request.ChatMessageRequest;
44
import com.thefirsttake.app.chat.entity.ChatRoom;
55
import com.thefirsttake.app.chat.service.ChatMessageProcessorService;
6-
import com.thefirsttake.app.chat.service.ChatMessageSaveService;
6+
//import com.thefirsttake.app.chat.service.ChatMessageSaveService;
77
import com.thefirsttake.app.chat.service.ChatRoomService;
88
import com.thefirsttake.app.chat.service.SendMessageWorkerService;
9-
import com.thefirsttake.app.chat.service.worker.ChatMessageWorkerService;
9+
//import com.thefirsttake.app.chat.service.worker.ChatMessageWorkerService;
1010
import com.thefirsttake.app.common.response.CommonResponse;
1111
import com.thefirsttake.app.common.user.entity.UserEntity;
1212
import com.thefirsttake.app.common.user.service.UserSessionService;
@@ -26,9 +26,9 @@
2626
@RequiredArgsConstructor
2727
public class ChatMessageController {
2828
private final ChatMessageProcessorService chatMessageProcessorService;
29-
private final ChatMessageSaveService chatMessageSaveService;
29+
// private final ChatMessageSaveService chatMessageSaveService;
3030
private final SendMessageWorkerService sendMessageWorkerService;
31-
private final ChatMessageWorkerService chatMessageWorkerService;
31+
// private final ChatMessageWorkerService chatMessageWorkerService;
3232
private final UserSessionService userSessionService;
3333
private final ChatRoomService chatRoomService;
3434

@@ -125,11 +125,12 @@ public CommonResponse sendChatMessage(@RequestParam("roomId") Long roomId, @Requ
125125
// 1. roomid를 바탕으로 -> 채팅방 확인 -> 유저 확인
126126
UserEntity userEntity=chatRoomService.getUserEntityByRoomId(roomId);
127127
// 2. 메시지 PostgreSQL에 저장
128+
Long savedId=chatRoomService.saveUserMessage(userEntity,chatMessageRequest,roomId);
128129
// 3. Redis 워커 큐에 푸시
129130

130131
// 1. 유저 확인/생성
131132
// 2. 채팅방 확인/생성
132-
return CommonResponse.success(userEntity.getId());
133+
return CommonResponse.success(savedId);
133134
// // 1. PostgreSQL 저장
134135
// Long savedId = chatMessageSaveService.saveUserMessage(sessionId, chatMessageRequest);
135136
//
@@ -148,84 +149,86 @@ public CommonResponse sendChatMessage(@RequestParam("roomId") Long roomId, @Requ
148149

149150
}
150151

151-
@Operation(
152-
summary = "채팅 응답 메시지 수신",
153-
description = "Redis 큐에서 세션에 해당하는 응답 메시지가 있는 경우, 여러 개의 메시지를 한번에 꺼내 반환합니다.",
154-
responses = {
155-
@io.swagger.v3.oas.annotations.responses.ApiResponse(
156-
responseCode = "200",
157-
description = "응답 메시지 수신 성공",
158-
content = @Content(
159-
mediaType = "application/json",
160-
schema = @Schema(implementation = CommonResponse.class),
161-
examples = @ExampleObject(
162-
name = "성공 응답 예시",
163-
summary = "Redis에서 가져온 응답 메시지 리스트",
164-
value = """
165-
{
166-
"status": "success",
167-
"message": "요청 성공",
168-
"data": [
169-
"당신의 소개팅은 어떤 분위기를 원하세요? 좀 더 캐주얼하고 편안한 느낌을 원하시면 깔끔한 셔츠와 슬랙스, 약간 더 포멀한 느낌을 원하시면 셔츠와 블레이저를 추천드릴게요.1번째 AI",
170-
"내일 소개팅이라면 깔끔하고 세련된 느낌이 좋겠어요. 단정한 셔츠에 슬림한 팬츠를 추천드려요. 색상은 무난하면서도 포인트를 줄 수 있는 베이지, 하얀색 또는 연한 파스텔 톤이 좋아요. 액세서리는 과하지 않게 심플한 목걸이나 귀걸이로 마무리하면 자연스럽고 매력적으로 보일 거예요. 편안하면서도 신경 쓴 모습이 가장 좋아요!2번째 AI"
171-
]
172-
}
173-
"""
174-
)
175-
)
176-
),
177-
@io.swagger.v3.oas.annotations.responses.ApiResponse(
178-
responseCode = "400",
179-
description = "세션 없음 또는 응답 없음",
180-
content = @Content(
181-
mediaType = "application/json",
182-
examples = {
183-
@ExampleObject(
184-
name = "세션 없음 예시",
185-
summary = "세션이 없을 경우",
186-
value = """
187-
{
188-
"status": "fail",
189-
"message": "세션이 존재하지 않습니다.",
190-
"data": null
191-
}
192-
"""
193-
),
194-
@ExampleObject(
195-
name = "응답 없음 예시",
196-
summary = "아직 응답이 Redis에 없는 경우",
197-
value = """
198-
{
199-
"status": "fail",
200-
"message": "응답이 아직 없습니다.",
201-
"data": null
202-
}
203-
"""
204-
)
205-
}
206-
)
207-
)
208-
}
209-
)
210-
@GetMapping("/receive")
211-
public CommonResponse receiveChatMessage(HttpServletRequest httpRequest) {
212-
HttpSession session = httpRequest.getSession(false);
213-
if (session == null) {
214-
return CommonResponse.fail("세션이 존재하지 않습니다.");
215-
}
216-
217-
String sessionId = session.getId();
218-
List<String> responseMessage=chatMessageWorkerService.processChatQueue(sessionId);
219-
// String redisKey = "chat_response:" + sessionId;
152+
// @Operation(
153+
// summary = "채팅 응답 메시지 수신",
154+
// description = "Redis 큐에서 세션에 해당하는 응답 메시지가 있는 경우, 여러 개의 메시지를 한번에 꺼내 반환합니다.",
155+
// responses = {
156+
// @io.swagger.v3.oas.annotations.responses.ApiResponse(
157+
// responseCode = "200",
158+
// description = "응답 메시지 수신 성공",
159+
// content = @Content(
160+
// mediaType = "application/json",
161+
// schema = @Schema(implementation = CommonResponse.class),
162+
// examples = @ExampleObject(
163+
// name = "성공 응답 예시",
164+
// summary = "Redis에서 가져온 응답 메시지 리스트",
165+
// value = """
166+
// {
167+
// "status": "success",
168+
// "message": "요청 성공",
169+
// "data": [
170+
// "당신의 소개팅은 어떤 분위기를 원하세요? 좀 더 캐주얼하고 편안한 느낌을 원하시면 깔끔한 셔츠와 슬랙스, 약간 더 포멀한 느낌을 원하시면 셔츠와 블레이저를 추천드릴게요.1번째 AI",
171+
// "내일 소개팅이라면 깔끔하고 세련된 느낌이 좋겠어요. 단정한 셔츠에 슬림한 팬츠를 추천드려요. 색상은 무난하면서도 포인트를 줄 수 있는 베이지, 하얀색 또는 연한 파스텔 톤이 좋아요. 액세서리는 과하지 않게 심플한 목걸이나 귀걸이로 마무리하면 자연스럽고 매력적으로 보일 거예요. 편안하면서도 신경 쓴 모습이 가장 좋아요!2번째 AI"
172+
// ]
173+
// }
174+
// """
175+
// )
176+
// )
177+
// ),
178+
// @io.swagger.v3.oas.annotations.responses.ApiResponse(
179+
// responseCode = "400",
180+
// description = "세션 없음 또는 응답 없음",
181+
// content = @Content(
182+
// mediaType = "application/json",
183+
// examples = {
184+
// @ExampleObject(
185+
// name = "세션 없음 예시",
186+
// summary = "세션이 없을 경우",
187+
// value = """
188+
// {
189+
// "status": "fail",
190+
// "message": "세션이 존재하지 않습니다.",
191+
// "data": null
192+
// }
193+
// """
194+
// ),
195+
// @ExampleObject(
196+
// name = "응답 없음 예시",
197+
// summary = "아직 응답이 Redis에 없는 경우",
198+
// value = """
199+
// {
200+
// "status": "fail",
201+
// "message": "응답이 아직 없습니다.",
202+
// "data": null
203+
// }
204+
// """
205+
// )
206+
// }
207+
// )
208+
// )
209+
// }
210+
// )
211+
// @GetMapping("/receive")
212+
// public CommonResponse receiveChatMessage(HttpServletRequest httpRequest) {
213+
// HttpSession session = httpRequest.getSession(false);
214+
// if (session == null) {
215+
// return CommonResponse.fail("세션이 존재하지 않습니다.");
216+
// }
220217
//
221-
// // Redis에서 메시지를 pop (꺼내고 제거)
222-
// String responseMessage = redisTemplate.opsForList().leftPop(redisKey);
218+
// String sessionId = session.getId();
219+
// List<String> responseMessage=chatMessageWorkerService.processChatQueue(sessionId);
223220
//
224-
if (responseMessage == null) {
225-
return CommonResponse.fail("응답이 아직 없습니다."); // 또는 return ResponseEntity.noContent().build();
226-
}
227-
return CommonResponse.success(responseMessage);
228-
}
221+
//
222+
//// String redisKey = "chat_response:" + sessionId;
223+
////
224+
//// // Redis에서 메시지를 pop (꺼내고 제거)
225+
//// String responseMessage = redisTemplate.opsForList().leftPop(redisKey);
226+
////
227+
// if (responseMessage == null) {
228+
// return CommonResponse.fail("응답이 아직 없습니다."); // 또는 return ResponseEntity.noContent().build();
229+
// }
230+
// return CommonResponse.success(responseMessage);
231+
// }
229232

230233

231234
}
Lines changed: 18 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,13 @@
1-
//package com.thefirsttake.app.chat.entity;
2-
//
3-
//import jakarta.persistence.*;
4-
//import lombok.*;
5-
//
6-
//import java.time.LocalDateTime;
7-
//
8-
//@Getter
9-
//@Setter
10-
//@AllArgsConstructor
11-
//@NoArgsConstructor
12-
//@Entity
13-
//@Table(name="chat_message")
14-
//@Builder
15-
//public class ChatMessage {
16-
// @Id
17-
// @GeneratedValue(strategy = GenerationType.IDENTITY)
18-
// private Long id;
19-
//
20-
// @Column(name = "session_id", nullable = false, length = 200)
21-
// private String sessionId;
22-
//
23-
// @Column(nullable = false, length = 10)
24-
// private String sender; // "USER" 또는 "BOT"
25-
//
26-
// @Column(nullable = false, columnDefinition = "TEXT")
27-
// private String message;
28-
//
29-
// @Column(name = "created_at", nullable = false, updatable = false)
30-
// private LocalDateTime createdAt;
31-
// @PrePersist
32-
// public void prePersist() {
33-
// this.createdAt = LocalDateTime.now();
34-
// }
35-
//}
361
package com.thefirsttake.app.chat.entity;
372

3+
import com.thefirsttake.app.common.user.entity.UserEntity;
384
import jakarta.persistence.*;
395
import lombok.*;
406

417
import java.time.LocalDateTime;
428

439
@Entity
44-
@Table(name = "chat_messages")
10+
@Table(name = "chat_messages") // 테이블 이름은 복수형으로 'chat_messages'가 더 일반적입니다.
4511
@Getter
4612
@Setter
4713
@NoArgsConstructor
@@ -53,25 +19,33 @@ public class ChatMessage {
5319
@GeneratedValue(strategy = GenerationType.IDENTITY)
5420
private Long id;
5521

56-
// ManyToOne: 여러 메시지가 하나의 채팅방에 속함
57-
@ManyToOne(fetch = FetchType.LAZY)
58-
@JoinColumn(name = "room_id", nullable = false)
22+
// --- ChatRoom과의 N:1 관계 (메시지 여러 개 -> 하나의 채팅방) ---
23+
// chat_messages 테이블의 room_id 컬럼이 chat_rooms 테이블의 id를 참조합니다.
24+
@ManyToOne(fetch = FetchType.LAZY) // 지연 로딩: 메시지를 조회할 때 당장 채팅방 정보가 필요하지 않으면 로딩하지 않음
25+
@JoinColumn(name = "room_id", nullable = false) // 외래 키 컬럼명 지정, 필수 값
5926
private ChatRoom chatRoom;
6027

61-
@Column(nullable = false, length = 100)
62-
private String sessionId;
28+
// --- User와의 N:1 관계 (메시지 여러 개 -> 하나의 사용자) ---
29+
// chat_messages 테이블의 user_id 컬럼이 users 테이블의 id를 참조합니다.
30+
@ManyToOne(fetch = FetchType.LAZY) // 지연 로딩
31+
@JoinColumn(name = "user_id", nullable = false) // 외래 키 컬럼명 지정, 필수 값
32+
private UserEntity user; // 세션 ID 대신 User 엔티티를 직접 참조합니다.
6333

34+
// sender 필드: 메시지를 보낸 주체가 누구인지 (USER 또는 BOT)
35+
// user 엔티티를 통해 발신자를 알 수 있지만, "시스템 메시지"나 "봇" 메시지 등
36+
// 특정 주체를 구분할 필요가 있다면 유지합니다.
37+
// user_id와 별개로 "누가 보냈는지"의 유형을 나타내는 용도.
6438
@Column(nullable = false, length = 10)
65-
private String sender;
39+
private String senderType; // 'sender' 대신 'senderType'으로 이름을 변경하여 역할 명확화 (예: "USER", "BOT", "SYSTEM")
6640

6741
@Column(nullable = false, columnDefinition = "TEXT")
6842
private String message;
6943

7044
@Column(name = "created_at", nullable = false, updatable = false)
7145
private LocalDateTime createdAt;
7246

73-
@PrePersist
47+
@PrePersist // 엔티티가 영속화되기 전에 실행 (INSERT 전에)
7448
public void prePersist() {
7549
this.createdAt = LocalDateTime.now();
7650
}
77-
}
51+
}

thefirsttake/src/main/java/com/thefirsttake/app/chat/service/ChatMessageSaveService.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@
66
import lombok.RequiredArgsConstructor;
77
import org.springframework.stereotype.Service;
88

9-
@Service
10-
@RequiredArgsConstructor
11-
public class ChatMessageSaveService {
12-
private final ChatMessageRepository chatMessageRepository;
13-
public Long saveUserMessage(String sessionId, ChatMessageRequest chatMessageRequest) {
14-
String userInput=chatMessageRequest.getContent();
15-
ChatMessage message = ChatMessage.builder()
16-
.sessionId(sessionId)
17-
.sender("USER")
18-
.message(userInput)
19-
.build();
20-
21-
ChatMessage saved = chatMessageRepository.save(message);
22-
return saved.getId(); // 저장된 메시지의 ID 반환
23-
}
24-
}
9+
//@Service
10+
//@RequiredArgsConstructor
11+
//public class ChatMessageSaveService {
12+
// private final ChatMessageRepository chatMessageRepository;
13+
// public Long saveUserMessage(String sessionId, ChatMessageRequest chatMessageRequest) {
14+
// String userInput=chatMessageRequest.getContent();
15+
// ChatMessage message = ChatMessage.builder()
16+
// .sessionId(sessionId)
17+
// .sender("USER")
18+
// .message(userInput)
19+
// .build();
20+
//
21+
// ChatMessage saved = chatMessageRepository.save(message);
22+
// return saved.getId(); // 저장된 메시지의 ID 반환
23+
// }
24+
//}
Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package com.thefirsttake.app.chat.service;
22

3+
import com.thefirsttake.app.chat.dto.request.ChatMessageRequest;
4+
import com.thefirsttake.app.chat.entity.ChatMessage;
35
import com.thefirsttake.app.chat.entity.ChatRoom;
6+
import com.thefirsttake.app.chat.repository.ChatMessageRepository;
47
import com.thefirsttake.app.chat.repository.ChatRoomRepository;
58
import com.thefirsttake.app.common.user.entity.UserEntity;
9+
import jakarta.persistence.EntityNotFoundException;
610
import lombok.RequiredArgsConstructor;
11+
import org.apache.catalina.User;
712
import org.springframework.stereotype.Service;
813

914
import java.time.LocalDateTime;
@@ -12,6 +17,7 @@
1217
@RequiredArgsConstructor
1318
public class ChatRoomService {
1419
private final ChatRoomRepository chatRoomRepository;
20+
private final ChatMessageRepository chatMessageRepository;
1521
public ChatRoom getOrCreateChatRoom(UserEntity userEntity) {
1622
return chatRoomRepository.findFirstByUserOrderByIdAsc(userEntity)
1723
.orElseGet(() -> {
@@ -22,14 +28,30 @@ public ChatRoom getOrCreateChatRoom(UserEntity userEntity) {
2228
return chatRoomRepository.save(newRoom);
2329
});
2430
}
25-
// public ChatRoom getRoomById(Long roomId){
26-
// return chatRoomRepository.findById(roomId)
27-
// .orElseThrow(() -> new RuntimeException("채팅방이 존재하지 않습니다."));
28-
// }
31+
public ChatRoom getRoomById(Long roomId){
32+
return chatRoomRepository.findById(roomId)
33+
.orElseThrow(() -> new EntityNotFoundException("ChatRoom not found with ID: " + roomId));
34+
}
2935
public UserEntity getUserEntityByRoomId(Long roomId){
3036
ChatRoom chatRoom = chatRoomRepository.findById(roomId)
3137
.orElseThrow(() -> new RuntimeException("채팅방이 존재하지 않습니다."));
3238

3339
return chatRoom.getUser();
3440
}
41+
public Long saveUserMessage(UserEntity userEntity, ChatMessageRequest chatMessageRequest, Long roomId) {
42+
// 1. roomId를 사용하여 ChatRoom 엔티티를 조회합니다.
43+
// 해당 ID의 채팅방이 존재하지 않으면 예외를 발생시킵니다.
44+
ChatRoom chatRoom = getRoomById(roomId);
45+
46+
String userInput=chatMessageRequest.getContent();
47+
ChatMessage message = ChatMessage.builder()
48+
.chatRoom(chatRoom)
49+
.user(userEntity)
50+
.senderType("USER")
51+
.message(userInput)
52+
.build();
53+
54+
ChatMessage saved = chatMessageRepository.save(message);
55+
return saved.getId(); // 저장된 메시지의 ID 반환
56+
}
3557
}

0 commit comments

Comments
 (0)