Skip to content

Commit 7a05e21

Browse files
authored
Merge pull request #174 from prgrms-web-devcourse-final-project/chore#173
[chore] 도메인 수정 및 배포 테스트#173
2 parents 83aea1c + e7f54c2 commit 7a05e21

Some content is hidden

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

48 files changed

+1467
-165
lines changed

build.gradle.kts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,20 @@ dependencies {
4848
implementation("org.springframework.boot:spring-boot-starter-data-redis")
4949
implementation("org.springframework.session:spring-session-data-redis")
5050

51+
// AWS S3
52+
implementation("io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.0")
53+
5154
runtimeOnly("com.h2database:h2")
5255
runtimeOnly("com.mysql:mysql-connector-j")
5356

5457
annotationProcessor("org.projectlombok:lombok")
5558

59+
//json
60+
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
61+
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
62+
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
63+
// json 파싱용
64+
5665
//Spring AI
5766
implementation(platform("org.springframework.ai:spring-ai-bom:1.0.0-M4"))
5867

cookies.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Netscape HTTP Cookie File
2+
# https://curl.se/docs/http-cookies.html
3+
# This file was generated by libcurl! Edit at your own risk.
4+

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

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,10 @@ public ResponseEntity<RsData<ChatResponseDto>> sendMessage(@Valid @RequestBody C
3333
}
3434
}
3535

36-
@GetMapping("/history/{sessionId}")
37-
public ResponseEntity<RsData<List<ChatConversation>>> getChatHistory(@PathVariable String sessionId) {
36+
@GetMapping("/history/user/{userId}")
37+
public ResponseEntity<RsData<List<ChatConversation>>> getUserChatHistory(@PathVariable Long userId) {
3838
try {
39-
List<ChatConversation> history = chatbotService.getChatHistory(sessionId);
40-
return ResponseEntity.ok(RsData.successOf(history));
41-
} catch (Exception e) {
42-
log.error("채팅 기록 조회 중 오류 발생: ", e);
43-
return ResponseEntity.internalServerError()
44-
.body(RsData.failOf("서버 오류가 발생했습니다."));
45-
}
46-
}
47-
48-
@GetMapping("/history/user/{userId}/session/{sessionId}")
49-
public ResponseEntity<RsData<List<ChatConversation>>> getUserChatHistory(
50-
@PathVariable Long userId,
51-
@PathVariable String sessionId) {
52-
try {
53-
List<ChatConversation> history = chatbotService.getUserChatHistory(userId, sessionId);
39+
List<ChatConversation> history = chatbotService.getUserChatHistory(userId);
5440
return ResponseEntity.ok(RsData.successOf(history));
5541
} catch (Exception e) {
5642
log.error("사용자 채팅 기록 조회 중 오류 발생: ", e);

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ public class ChatRequestDto {
1313
@NotBlank(message = "메시지는 필수입니다.")
1414
private String message;
1515

16-
private String sessionId;
1716

1817
private Long userId;
1918
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@
1414
public class ChatResponseDto {
1515

1616
private String response;
17-
private String sessionId;
1817
private LocalDateTime timestamp;
1918

20-
public ChatResponseDto(String response, String sessionId) {
19+
public ChatResponseDto(String response) {
2120
this.response = response;
22-
this.sessionId = sessionId;
2321
this.timestamp = LocalDateTime.now();
2422
}
2523
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ public class ChatConversation {
2727
@Column(columnDefinition = "TEXT")
2828
private String botResponse;
2929

30-
private String sessionId;
31-
3230
private LocalDateTime createdAt;
3331

3432
@PrePersist

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
@Repository
1212
public interface ChatConversationRepository extends JpaRepository<ChatConversation, Long> {
1313

14-
List<ChatConversation> findBySessionIdOrderByCreatedAtAsc(String sessionId);
15-
1614
Page<ChatConversation> findByUserIdOrderByCreatedAtDesc(Long userId, Pageable pageable);
1715

18-
List<ChatConversation> findByUserIdAndSessionIdOrderByCreatedAtAsc(Long userId, String sessionId);
16+
List<ChatConversation> findTop5ByUserIdOrderByCreatedAtDesc(Long userId);
1917
}

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

Lines changed: 35 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,24 @@
44
import com.back.domain.chatbot.dto.ChatResponseDto;
55
import com.back.domain.chatbot.entity.ChatConversation;
66
import com.back.domain.chatbot.repository.ChatConversationRepository;
7+
import jakarta.annotation.PostConstruct;
78
import lombok.RequiredArgsConstructor;
89
import lombok.extern.slf4j.Slf4j;
910
import org.springframework.ai.chat.client.ChatClient;
10-
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
11-
import org.springframework.ai.chat.memory.InMemoryChatMemory;
1211
import org.springframework.ai.chat.model.ChatModel;
13-
import org.springframework.ai.chat.messages.*;
1412
import org.springframework.ai.openai.OpenAiChatOptions;
1513
import org.springframework.beans.factory.annotation.Value;
1614
import org.springframework.core.io.Resource;
15+
import org.springframework.data.domain.Pageable;
1716
import org.springframework.stereotype.Service;
1817
import org.springframework.transaction.annotation.Transactional;
1918
import org.springframework.util.StreamUtils;
2019

21-
import jakarta.annotation.PostConstruct;
2220
import java.io.IOException;
2321
import java.nio.charset.StandardCharsets;
2422
import java.time.LocalDateTime;
25-
import java.util.*;
26-
import java.util.concurrent.ConcurrentHashMap;
23+
import java.util.Collections;
24+
import java.util.List;
2725

2826
@Service
2927
@RequiredArgsConstructor
@@ -33,8 +31,6 @@ public class ChatbotService {
3331
private final ChatModel chatModel;
3432
private final ChatConversationRepository chatConversationRepository;
3533

36-
// 세션별 메모리 관리 (Thread-Safe)
37-
private final ConcurrentHashMap<String, InMemoryChatMemory> sessionMemories = new ConcurrentHashMap<>();
3834

3935
@Value("classpath:prompts/chatbot-system-prompt.txt")
4036
private Resource systemPromptResource;
@@ -80,23 +76,24 @@ public void init() throws IOException {
8076

8177
@Transactional
8278
public ChatResponseDto sendMessage(ChatRequestDto requestDto) {
83-
String sessionId = ensureSessionId(requestDto.getSessionId());
84-
8579
try {
8680
// 메시지 타입 감지
8781
MessageType messageType = detectMessageType(requestDto.getMessage());
8882

89-
// 세션별 메모리 가져오기
90-
InMemoryChatMemory chatMemory = getOrCreateSessionMemory(sessionId);
83+
// 최근 대화 기록 조회 (최신 5개)
84+
List<ChatConversation> recentChats =
85+
chatConversationRepository.findTop5ByUserIdOrderByCreatedAtDesc(requestDto.getUserId());
86+
87+
// 대화 히스토리를 시간순으로 정렬 (오래된 것부터)
88+
Collections.reverse(recentChats);
9189

92-
// 이전 대화 기록 로드
93-
loadConversationHistory(sessionId, chatMemory);
90+
// 대화 컨텍스트 생성
91+
String conversationContext = buildConversationContext(recentChats);
9492

9593
// ChatClient 빌더 생성
9694
var promptBuilder = chatClient.prompt()
97-
.system(buildSystemMessage(messageType))
98-
.user(buildUserMessage(requestDto.getMessage(), messageType))
99-
.advisors(new MessageChatMemoryAdvisor(chatMemory));
95+
.system(buildSystemMessage(messageType) + conversationContext)
96+
.user(buildUserMessage(requestDto.getMessage(), messageType));
10097

10198
// RAG 기능은 향후 구현 예정 (Vector DB 설정 필요)
10299

@@ -109,42 +106,31 @@ public ChatResponseDto sendMessage(ChatRequestDto requestDto) {
109106
// 응답 후처리
110107
response = postProcessResponse(response, messageType);
111108

112-
// 대화 저장
113-
saveConversation(requestDto, response, sessionId);
109+
// 대화 저장 (sessionId 없이)
110+
saveConversation(requestDto, response);
114111

115-
return new ChatResponseDto(response, sessionId);
112+
return new ChatResponseDto(response);
116113

117114
} catch (Exception e) {
118115
log.error("채팅 응답 생성 중 오류 발생: ", e);
119-
return handleError(sessionId, e);
116+
return handleError(e);
120117
}
121118
}
122119

123-
private String ensureSessionId(String sessionId) {
124-
return (sessionId == null || sessionId.isEmpty())
125-
? UUID.randomUUID().toString()
126-
: sessionId;
127-
}
128120

129-
private InMemoryChatMemory getOrCreateSessionMemory(String sessionId) {
130-
return sessionMemories.computeIfAbsent(
131-
sessionId,
132-
k -> new InMemoryChatMemory()
133-
);
134-
}
121+
private String buildConversationContext(List<ChatConversation> recentChats) {
122+
if (recentChats.isEmpty()) {
123+
return "";
124+
}
135125

136-
private void loadConversationHistory(String sessionId, InMemoryChatMemory chatMemory) {
137-
List<ChatConversation> conversations =
138-
chatConversationRepository.findBySessionIdOrderByCreatedAtAsc(sessionId);
139-
140-
// 최근 N개의 대화만 메모리에 로드
141-
String sessionIdForMemory = sessionId;
142-
conversations.stream()
143-
.skip(Math.max(0, conversations.size() - maxConversationCount))
144-
.forEach(conv -> {
145-
chatMemory.add(sessionIdForMemory, new UserMessage(conv.getUserMessage()));
146-
chatMemory.add(sessionIdForMemory, new AssistantMessage(conv.getBotResponse()));
147-
});
126+
StringBuilder context = new StringBuilder("\n\n【최근 대화 기록】\n");
127+
for (ChatConversation chat : recentChats) {
128+
context.append("사용자: ").append(chat.getUserMessage()).append("\n");
129+
context.append("봇: ").append(chat.getBotResponse()).append("\n\n");
130+
}
131+
context.append("위 대화를 참고하여 자연스럽게 이어지는 답변을 해주세요.\n");
132+
133+
return context.toString();
148134
}
149135

150136
private String buildSystemMessage(MessageType type) {
@@ -208,19 +194,18 @@ private String postProcessResponse(String response, MessageType type) {
208194
return response;
209195
}
210196

211-
private void saveConversation(ChatRequestDto requestDto, String response, String sessionId) {
197+
private void saveConversation(ChatRequestDto requestDto, String response) {
212198
ChatConversation conversation = ChatConversation.builder()
213199
.userId(requestDto.getUserId())
214200
.userMessage(requestDto.getMessage())
215201
.botResponse(response)
216-
.sessionId(sessionId)
217202
.createdAt(LocalDateTime.now())
218203
.build();
219204

220205
chatConversationRepository.save(conversation);
221206
}
222207

223-
private ChatResponseDto handleError(String sessionId, Exception e) {
208+
private ChatResponseDto handleError(Exception e) {
224209
String errorMessage = "죄송합니다. 잠시 후 다시 시도해주세요.";
225210

226211
if (e.getMessage().contains("rate limit")) {
@@ -229,7 +214,7 @@ private ChatResponseDto handleError(String sessionId, Exception e) {
229214
errorMessage = "응답 시간이 초과되었습니다. 다시 시도해주세요.";
230215
}
231216

232-
return new ChatResponseDto(errorMessage, sessionId);
217+
return new ChatResponseDto(errorMessage);
233218
}
234219

235220
public enum MessageType {
@@ -254,25 +239,9 @@ private MessageType detectMessageType(String message) {
254239
}
255240

256241
@Transactional(readOnly = true)
257-
public List<ChatConversation> getChatHistory(String sessionId) {
258-
return chatConversationRepository.findBySessionIdOrderByCreatedAtAsc(sessionId);
259-
}
260-
261-
@Transactional(readOnly = true)
262-
public List<ChatConversation> getUserChatHistory(Long userId, String sessionId) {
263-
return chatConversationRepository.findByUserIdAndSessionIdOrderByCreatedAtAsc(userId, sessionId);
242+
public List<ChatConversation> getUserChatHistory(Long userId) {
243+
return chatConversationRepository.findByUserIdOrderByCreatedAtDesc(userId, Pageable.unpaged()).getContent();
264244
}
265245

266-
// 정기적인 메모리 정리 (스케줄러로 호출)
267-
public void cleanupInactiveSessions() {
268-
long thirtyMinutesAgo = System.currentTimeMillis() - (30 * 60 * 1000);
269-
270-
sessionMemories.entrySet().removeIf(entry -> {
271-
// 실제로는 마지막 사용 시간을 추적해야 함
272-
return false;
273-
});
274-
275-
log.info("세션 메모리 정리 완료. 현재 활성 세션: {}", sessionMemories.size());
276-
}
277246
}
278247

src/main/java/com/back/domain/cocktail/controller/CocktailController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import java.util.List;
1616

1717
@RestController
18-
@RequestMapping("cocktails")
18+
@RequestMapping("/cocktails")
1919
@Tag(name = "ApiCocktailController", description = "API 칵테일 컨트롤러")
2020
@RequiredArgsConstructor
2121
public class CocktailController {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.back.domain.cocktail.controller;
2+
3+
import com.back.domain.cocktail.dto.CocktailRecommendResponseDto;
4+
import com.back.domain.cocktail.service.RecommendService;
5+
import com.back.global.rsData.RsData;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.tags.Tag;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.RequestMapping;
11+
import org.springframework.web.bind.annotation.RequestParam;
12+
import org.springframework.web.bind.annotation.RestController;
13+
14+
import java.util.List;
15+
16+
@RestController
17+
@RequestMapping("/cocktails/recommend")
18+
@Tag(name = "ApiCocktailRecommendController", description = "API 칵테일 추천 컨트롤러")
19+
@RequiredArgsConstructor
20+
public class CocktailRecommendController {
21+
22+
private final RecommendService recommendService;
23+
24+
// 상세페이지 3개 칵테일 추천 (DTO로 반환)
25+
@Operation(summary = "상세페이지 유사 칵테일 추천", description = "현재 칵테일과 유사한 칵테일 최대 3개를 반환합니다.")
26+
@GetMapping("/related")
27+
public RsData<List<CocktailRecommendResponseDto>> recommendRelated(@RequestParam Long cocktailId) {
28+
return RsData.successOf(recommendService.recommendRelatedCocktails(cocktailId, 3));
29+
}
30+
}

0 commit comments

Comments
 (0)