Skip to content

Commit 983ac03

Browse files
authored
[refactor] 챗봇 히스토리 조회 문제 해결을 위한 DB 저장 구조 변경 #256
[refactor] 챗봇 히스토리 조회 문제 해결을 위한 DB 저장 구조 변경 #256
2 parents 812a6de + 476100f commit 983ac03

File tree

5 files changed

+97
-8
lines changed

5 files changed

+97
-8
lines changed

src/main/java/com/back/domain/chatbot/controller/ChatbotController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ public ResponseEntity<RsData<ChatResponseDto>> sendMessage(@Valid @RequestBody C
3838

3939
@GetMapping("/history/user/{userId}")
4040
@Operation(summary = "유저 대화 히스토리", description = "사용자 채팅 기록 조회")
41-
public ResponseEntity<RsData<List<ChatConversation>>> getUserChatHistory(@PathVariable Long userId) {
41+
public ResponseEntity<RsData<List<ChatResponseDto>>> getUserChatHistory(@PathVariable Long userId) {
4242
try {
43-
List<ChatConversation> history = chatbotService.getUserChatHistory(userId);
43+
List<ChatResponseDto> history = chatbotService.getUserChatHistory(userId);
4444
return ResponseEntity.ok(RsData.successOf(history));
4545
} catch (Exception e) {
4646
log.error("사용자 채팅 기록 조회 중 오류 발생: ", e);

src/main/java/com/back/domain/chatbot/dto/ChatRequestDto.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public class ChatRequestDto {
1515

1616
private Long userId;
1717

18+
private String selectedValue; // 예: "NON_ALCOHOLIC"
1819
// 단계별 추천 관련 필드들
1920
/**
2021
* @deprecated currentStep 필드를 사용하세요. 이 필드는 하위 호환성을 위해 유지됩니다.

src/main/java/com/back/domain/chatbot/entity/ChatConversation.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,8 @@ public class ChatConversation {
3434

3535
@CreatedDate
3636
private LocalDateTime createdAt;
37+
38+
// refactor#256 - metadata 필드 추가
39+
@Column(columnDefinition = "TEXT")
40+
private String metadata;
3741
}

src/main/java/com/back/domain/chatbot/repository/ChatConversationRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public interface ChatConversationRepository extends JpaRepository<ChatConversati
1111

1212
List<ChatConversation> findByUserIdOrderByCreatedAtDesc(Long userId);
1313

14+
List<ChatConversation> findByUserIdOrderByCreatedAtAsc(Long userId);
15+
1416
List<ChatConversation> findTop20ByUserIdOrderByCreatedAtDesc(Long userId);
1517

1618
boolean existsByUserIdAndMessage(Long userId, String message);

src/main/java/com/back/domain/chatbot/service/ChatbotService.java

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.back.domain.cocktail.enums.AlcoholBaseType;
1414
import com.back.domain.cocktail.enums.AlcoholStrength;
1515
import com.back.domain.cocktail.repository.CocktailRepository;
16+
import com.fasterxml.jackson.databind.ObjectMapper;
1617
import jakarta.annotation.PostConstruct;
1718
import lombok.RequiredArgsConstructor;
1819
import lombok.extern.slf4j.Slf4j;
@@ -26,12 +27,14 @@
2627
import org.springframework.stereotype.Service;
2728
import org.springframework.transaction.annotation.Transactional;
2829
import org.springframework.util.StreamUtils;
30+
import com.fasterxml.jackson.core.JsonProcessingException;
2931

3032
import java.io.IOException;
3133
import java.nio.charset.StandardCharsets;
3234
import java.time.LocalDateTime;
3335
import java.util.ArrayList;
3436
import java.util.List;
37+
import java.util.Map;
3538
import java.util.stream.Collectors;
3639

3740
@Service
@@ -43,6 +46,8 @@ public class ChatbotService {
4346
private final ChatConversationRepository chatConversationRepository;
4447
private final CocktailRepository cocktailRepository;
4548

49+
private final ObjectMapper objectMapper = new ObjectMapper(); // JSON 변환용
50+
4651
@Value("classpath:prompts/chatbot-system-prompt.txt")
4752
private Resource systemPromptResource;
4853

@@ -87,6 +92,8 @@ public void init() throws IOException {
8792

8893
@Transactional
8994
public ChatResponseDto sendMessage(ChatRequestDto requestDto) {
95+
saveUserMessage(requestDto);
96+
9097
try {
9198
Integer currentStep = requestDto.getCurrentStep();
9299

@@ -185,8 +192,47 @@ else if (currentStep >= 1 && currentStep <= 4) {
185192
}
186193
}
187194

188-
// ============ 수정된 메서드들 ============
195+
private void saveUserMessage(ChatRequestDto requestDto) {
196+
String metadata = null;
197+
if (requestDto.getSelectedValue() != null) {
198+
try {
199+
// 사용자가 선택한 실제 값(value)을 JSON으로 저장
200+
metadata = objectMapper.writeValueAsString(Map.of("selectedValue", requestDto.getSelectedValue()));
201+
} catch (JsonProcessingException e) {
202+
log.error("사용자 선택 값 JSON 직렬화 실패", e);
203+
}
204+
}
205+
206+
ChatConversation userMessage = ChatConversation.builder()
207+
.userId(requestDto.getUserId())
208+
.message(requestDto.getMessage()) // 사용자가 본 텍스트(label)
209+
.sender(MessageSender.USER)
210+
.createdAt(LocalDateTime.now())
211+
.metadata(metadata) // 선택한 실제 값(value)
212+
.build();
213+
chatConversationRepository.save(userMessage);
214+
}
189215

216+
private ChatConversation saveBotResponse(Long userId, String message, Object stepData) {
217+
String metadata = null;
218+
if (stepData != null) {
219+
try {
220+
// 봇이 보낸 옵션, 카드 등 구조화된 데이터를 JSON으로 저장
221+
metadata = objectMapper.writeValueAsString(stepData);
222+
} catch (JsonProcessingException e) {
223+
log.error("봇 응답 메타데이터 JSON 직렬화 실패", e);
224+
}
225+
}
226+
227+
ChatConversation botResponse = ChatConversation.builder()
228+
.userId(userId)
229+
.message(message)
230+
.sender(MessageSender.CHATBOT)
231+
.createdAt(LocalDateTime.now())
232+
.metadata(metadata)
233+
.build();
234+
return chatConversationRepository.save(botResponse);
235+
}
190236
/**
191237
* 대화 컨텍스트 빌드 - 변경사항: sender로 구분하여 대화 재구성
192238
*/
@@ -242,8 +288,44 @@ public ChatConversation saveConversation(ChatRequestDto requestDto, String respo
242288
* 사용자 채팅 기록 조회 - 변경사항: sender 구분 없이 모든 메시지 시간순으로 조회
243289
*/
244290
@Transactional(readOnly = true)
245-
public List<ChatConversation> getUserChatHistory(Long userId) {
246-
return chatConversationRepository.findByUserIdOrderByCreatedAtDesc(userId);
291+
public List<ChatResponseDto> getUserChatHistory(Long userId) {
292+
List<ChatConversation> history = chatConversationRepository.findByUserIdOrderByCreatedAtAsc(userId); // 시간순으로 변경
293+
294+
return history.stream().map(conversation -> {
295+
ChatResponseDto.ChatResponseDtoBuilder builder = ChatResponseDto.builder()
296+
.id(conversation.getId())
297+
.userId(conversation.getUserId())
298+
.message(conversation.getMessage())
299+
.sender(conversation.getSender())
300+
.createdAt(conversation.getCreatedAt());
301+
302+
String metadata = conversation.getMetadata();
303+
if (metadata != null && !metadata.isEmpty()) {
304+
try {
305+
if (conversation.getSender() == MessageSender.CHATBOT) {
306+
StepRecommendationResponseDto stepData = objectMapper.readValue(metadata, StepRecommendationResponseDto.class);
307+
builder.stepData(stepData);
308+
309+
if (stepData.getOptions() != null && !stepData.getOptions().isEmpty()) {
310+
builder.type(MessageType.RADIO_OPTIONS);
311+
} else if (stepData.getRecommendations() != null && !stepData.getRecommendations().isEmpty()) {
312+
builder.type(MessageType.CARD_LIST);
313+
} else {
314+
builder.type(MessageType.TEXT);
315+
}
316+
} else { // sender == USER
317+
// 사용자 메시지의 메타데이터는 FE에서 선택 처리 등에 활용 가능
318+
builder.type(MessageType.TEXT);
319+
}
320+
} catch (JsonProcessingException e) {
321+
log.error("대화 기록 metadata 역직렬화 실패 [ID: {}]: {}", conversation.getId(), e.getMessage());
322+
builder.type(MessageType.TEXT);
323+
}
324+
} else {
325+
builder.type(MessageType.TEXT);
326+
}
327+
return builder.build();
328+
}).collect(Collectors.toList());
247329
}
248330

249331
/**
@@ -429,8 +511,8 @@ private ChatConversation generateAIResponse(ChatRequestDto requestDto) {
429511
// 응답 후처리
430512
response = postProcessResponse(response, messageType);
431513

432-
// 대화 저장 - 사용자 메시지와 봇 응답을 각각 저장하고 저장된 봇 응답 반환
433-
return saveConversation(requestDto, response);
514+
// 봇 응답만 저장 -> 사용자 메시지는 sendmessage()에서 이미 저장됨
515+
return saveBotResponse(requestDto.getUserId(), response, null);
434516
}
435517

436518
/**
@@ -584,7 +666,7 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) {
584666
.sender(MessageSender.CHATBOT)
585667
.createdAt(LocalDateTime.now())
586668
.build();
587-
ChatConversation savedResponse = chatConversationRepository.save(botResponse);
669+
ChatConversation savedResponse = saveBotResponse(requestDto.getUserId(), message, stepData);
588670

589671
// 메타데이터 포함
590672
ChatResponseDto.MetaData metaData = ChatResponseDto.MetaData.builder()

0 commit comments

Comments
 (0)