Skip to content

Commit c1addf5

Browse files
authored
Merge pull request #359 from prgrms-web-devcourse-final-project/develop
mysql rds로 변경
2 parents b296e8f + 6d85edb commit c1addf5

Some content is hidden

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

47 files changed

+1056
-312
lines changed

.github/workflows/CI-CD_Pipeline.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ jobs:
463463
464464
# prod.env 복원
465465
install -d -m 700 /home/ec2-user/configs
466-
printf "%s" "${{ secrets.PROD_ENV_BASE64 }}" | base64 -d > /home/ec2-user/configs/prod.env
466+
printf "%s" "${{ secrets.PROD_TEST_ENV_BASE64 }}" | base64 -d > /home/ec2-user/configs/prod.env
467467
chmod 600 /home/ec2-user/configs/prod.env
468468
test -s /home/ec2-user/configs/prod.env || { echo "prod.env empty"; exit 1; }
469469

backend/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ dependencies {
4141
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
4242
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '3.0.5'
4343
implementation 'org.springframework.boot:spring-boot-starter-batch'
44-
implementation 'org.springframework.kafka:spring-kafka'
4544
testImplementation 'org.springframework.kafka:spring-kafka-test'
4645

4746
// API Documentation (문서화)

backend/docker-compose.yml

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -82,40 +82,8 @@ services:
8282
timeout: 5s
8383
retries: 10
8484

85-
zookeeper:
86-
image: confluentinc/cp-zookeeper:7.4.4
87-
container_name: zookeeper
88-
restart: unless-stopped
89-
ports:
90-
- "2181:2181"
91-
environment:
92-
ZOOKEEPER_CLIENT_PORT: 2181
93-
ZOOKEEPER_TICK_TIME: 2000
94-
95-
kafka:
96-
image: confluentinc/cp-kafka:7.4.4
97-
container_name: kafka
98-
restart: unless-stopped
99-
depends_on:
100-
- zookeeper
101-
ports:
102-
- "9092:9092"
103-
environment:
104-
KAFKA_BROKER_ID: 1
105-
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
106-
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
107-
KAFKA_LISTENERS: INTERNAL://0.0.0.0:29092,EXTERNAL://0.0.0.0:9092
108-
KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:29092,EXTERNAL://localhost:9092
109-
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
110-
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
111-
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
112-
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
113-
volumes:
114-
- kafka-data:/var/lib/kafka/data
115-
11685
volumes:
11786
mysql-data:
11887
redis-data:
11988
qdrant-data:
120-
ollama-data:
121-
kafka-data:
89+
ollama-data:

backend/src/main/java/com/ai/lawyer/domain/chatbot/controller/ChatBotController.java

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,45 @@
33
import com.ai.lawyer.domain.chatbot.dto.ChatDto.ChatRequest;
44
import com.ai.lawyer.domain.chatbot.dto.ChatDto.ChatResponse;
55
import com.ai.lawyer.domain.chatbot.service.ChatBotService;
6+
import com.ai.lawyer.global.util.AuthUtil;
67
import io.swagger.v3.oas.annotations.Operation;
78
import io.swagger.v3.oas.annotations.tags.Tag;
89
import lombok.RequiredArgsConstructor;
910
import lombok.extern.slf4j.Slf4j;
10-
import org.springframework.http.ResponseEntity;
11-
import org.springframework.security.core.annotation.AuthenticationPrincipal;
12-
import org.springframework.stereotype.Controller;
13-
import org.springframework.web.bind.annotation.PathVariable;
14-
import org.springframework.web.bind.annotation.PostMapping;
15-
import org.springframework.web.bind.annotation.RequestBody;
16-
import org.springframework.web.bind.annotation.RequestMapping;
11+
import org.springframework.web.bind.annotation.*;
1712
import reactor.core.publisher.Flux;
1813

1914
@Slf4j
2015
@Tag(name = "ChatBot API", description = "챗봇 관련 API")
21-
@Controller
16+
@RestController
2217
@RequiredArgsConstructor
2318
@RequestMapping("/api/chat")
2419
public class ChatBotController {
2520

2621
private final ChatBotService chatBotService;
2722

2823
@Operation(summary = "01. 새로운 채팅", description = "첫 메시지 전송으로 새로운 채팅방을 생성하고 챗봇과 대화를 시작")
29-
@PostMapping("/message")
30-
public ResponseEntity<Flux<ChatResponse>> postNewMessage(
31-
@AuthenticationPrincipal Long memberId,
32-
@RequestBody ChatRequest chatRequest) {
33-
return ResponseEntity.ok(chatBotService.sendMessage(memberId, chatRequest, null));
24+
@PostMapping(value = "/message")
25+
public Flux<ChatResponse> postNewMessage(@RequestBody ChatRequest chatRequest) {
26+
27+
Long memberId = AuthUtil.getAuthenticatedMemberId();
28+
29+
log.info("새로운 채팅 요청: memberId={}", memberId);
30+
31+
return chatBotService.sendMessage(memberId, chatRequest, null);
3432
}
3533

3634
@Operation(summary = "02. 기존 채팅", description = "기존 채팅방에 메시지를 보내고 챗봇과 대화를 이어감")
37-
@PostMapping("{roomId}/message")
38-
public ResponseEntity<Flux<ChatResponse>> postMessage(@AuthenticationPrincipal Long memberId, @RequestBody ChatRequest chatRequest, @PathVariable(value = "roomId", required = false) Long roomId) {
39-
return ResponseEntity.ok(chatBotService.sendMessage(memberId, chatRequest, roomId));
35+
@PostMapping(value = "{roomId}/message")
36+
public Flux<ChatResponse> postMessage(
37+
@RequestBody ChatRequest chatRequest,
38+
@PathVariable(value = "roomId", required = false) Long roomId) {
39+
40+
Long memberId = AuthUtil.getAuthenticatedMemberId();
41+
42+
log.info("기존 채팅 요청: memberId={}, roomId={}", memberId, roomId);
43+
44+
return chatBotService.sendMessage(memberId, chatRequest, roomId);
4045
}
4146

4247
}

backend/src/main/java/com/ai/lawyer/domain/chatbot/entity/History.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.ai.lawyer.domain.chatbot.entity;
22

3-
import com.ai.lawyer.domain.member.entity.Member;
43
import jakarta.persistence.*;
54
import lombok.AllArgsConstructor;
65
import lombok.Builder;
@@ -24,9 +23,10 @@ public class History {
2423
@GeneratedValue(strategy = GenerationType.IDENTITY)
2524
private Long historyId;
2625

27-
@ManyToOne
28-
@JoinColumn(name = "member_id", foreignKey = @ForeignKey(name = "FK_HISTORY_MEMBER"))
29-
private Member memberId;
26+
// Member와 OAuth2Member 모두 지원하기 위해 FK 제약 조건 제거 (ConstraintMode.NO_CONSTRAINT)
27+
// member_id를 직접 저장하고, 애플리케이션 레벨에서 AuthUtil로 참조 무결성 보장
28+
@Column(name = "member_id")
29+
private Long memberId;
3030

3131
@OneToMany(mappedBy = "historyId", cascade = CascadeType.ALL, orphanRemoval = true)
3232
private List<Chat> chats;

backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatLawRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ public interface ChatLawRepository extends JpaRepository<ChatLaw, Long> {
1212

1313
/**
1414
* member_id에 해당하는 모든 ChatLaw 삭제 (회원 탈퇴 시 사용)
15+
* History.memberId가 Long 타입이므로 직접 비교
1516
*/
1617
@Modifying
17-
@Query("DELETE FROM ChatLaw cl WHERE cl.chatId.historyId.memberId.memberId = :memberId")
18+
@Query("DELETE FROM ChatLaw cl WHERE cl.chatId.historyId.memberId = :memberId")
1819
void deleteByMemberIdValue(@Param("memberId") Long memberId);
1920
}

backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatPrecedentRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ public interface ChatPrecedentRepository extends JpaRepository<ChatPrecedent, Lo
1010

1111
/**
1212
* member_id에 해당하는 모든 ChatPrecedent 삭제 (회원 탈퇴 시 사용)
13+
* History.memberId가 Long 타입이므로 직접 비교
1314
*/
1415
@Modifying
15-
@Query("DELETE FROM ChatPrecedent cp WHERE cp.chatId.historyId.memberId.memberId = :memberId")
16+
@Query("DELETE FROM ChatPrecedent cp WHERE cp.chatId.historyId.memberId = :memberId")
1617
void deleteByMemberIdValue(@Param("memberId") Long memberId);
1718
}

backend/src/main/java/com/ai/lawyer/domain/chatbot/repository/ChatRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ public interface ChatRepository extends JpaRepository<Chat, Long> {
1212

1313
/**
1414
* member_id에 해당하는 모든 Chat 삭제 (회원 탈퇴 시 사용)
15+
* History.memberId가 Long 타입이므로 직접 비교
1516
*/
1617
@Modifying
17-
@Query("DELETE FROM Chat c WHERE c.historyId.memberId.memberId = :memberId")
18+
@Query("DELETE FROM Chat c WHERE c.historyId.memberId = :memberId")
1819
void deleteByMemberIdValue(@Param("memberId") Long memberId);
1920
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.ai.lawyer.domain.chatbot.repository;
22

33
import com.ai.lawyer.domain.chatbot.entity.History;
4-
import com.ai.lawyer.domain.member.entity.Member;
54
import org.springframework.data.jpa.repository.JpaRepository;
65
import org.springframework.data.jpa.repository.Modifying;
76
import org.springframework.data.jpa.repository.Query;
@@ -13,16 +12,17 @@
1312
@Repository
1413
public interface HistoryRepository extends JpaRepository<History, Long> {
1514

16-
List<History> findAllByMemberId(Member memberId);
15+
// member_id로 직접 조회 (Member, OAuth2Member 모두 지원)
16+
List<History> findAllByMemberId(Long memberId);
1717

18-
History findByHistoryIdAndMemberId(Long roomId, Member memberId);
18+
History findByHistoryIdAndMemberId(Long roomId, Long memberId);
1919

2020
/**
2121
* member_id로 채팅 히스토리 삭제 (회원 탈퇴 시 사용)
2222
* Member와 OAuth2Member 모두 같은 member_id 공간을 사용하므로 Long 타입으로 삭제
2323
*/
2424
@Modifying
25-
@Query("DELETE FROM History h WHERE h.memberId.memberId = :memberId")
25+
@Query("DELETE FROM History h WHERE h.memberId = :memberId")
2626
void deleteByMemberIdValue(@Param("memberId") Long memberId);
2727

2828
}

backend/src/main/java/com/ai/lawyer/domain/chatbot/service/AsyncPostChatProcessingService.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package com.ai.lawyer.domain.chatbot.service;
22

3+
import com.ai.lawyer.domain.chatbot.dto.ChatDto.ChatHistoryDto;
4+
import com.ai.lawyer.domain.chatbot.dto.ChatDto.ChatLawDto;
5+
import com.ai.lawyer.domain.chatbot.dto.ChatDto.ChatPrecedentDto;
36
import com.ai.lawyer.domain.chatbot.dto.ExtractionDto.KeywordExtractionDto;
47
import com.ai.lawyer.domain.chatbot.dto.ExtractionDto.TitleExtractionDto;
58
import com.ai.lawyer.domain.chatbot.entity.*;
69
import com.ai.lawyer.domain.chatbot.repository.*;
10+
import com.ai.lawyer.infrastructure.redis.service.ChatCacheService;
711
import lombok.RequiredArgsConstructor;
812
import lombok.extern.slf4j.Slf4j;
913
import org.springframework.ai.chat.memory.ChatMemory;
@@ -13,9 +17,11 @@
1317
import org.springframework.ai.chat.messages.MessageType;
1418
import org.springframework.ai.document.Document;
1519
import org.springframework.beans.factory.annotation.Value;
20+
import org.springframework.scheduling.annotation.Async;
1621
import org.springframework.stereotype.Service;
1722
import org.springframework.transaction.annotation.Transactional;
1823

24+
import java.util.ArrayList;
1925
import java.util.List;
2026
import java.util.stream.Collectors;
2127

@@ -27,6 +33,8 @@
2733
public class AsyncPostChatProcessingService {
2834

2935
private final KeywordService keywordService;
36+
private final ChatCacheService chatCacheService;
37+
3038
private final HistoryRepository historyRepository;
3139
private final ChatRepository chatRepository;
3240
private final KeywordRankRepository keywordRankRepository;
@@ -39,7 +47,7 @@ public class AsyncPostChatProcessingService {
3947
@Value("{$custom.ai.keyword-extraction}")
4048
private String keywordExtraction;
4149

42-
//@Async
50+
@Async
4351
@Transactional
4452
public void processHandlerTasks(Long historyId, String userMessage, String fullResponse, List<Document> similarCaseDocuments, List<Document> similarLawDocuments) {
4553
try {
@@ -98,6 +106,9 @@ private void extractAndUpdateKeywordRanks(String message) {
98106
}
99107

100108
private void saveChatWithDocuments(History history, MessageType type, String message, List<Document> similarCaseDocuments, List<Document> similarLawDocuments) {
109+
List<ChatPrecedent> chatPrecedents = new ArrayList<>();
110+
List<ChatLaw> chatLaws = new ArrayList<>();
111+
101112
Chat chat = chatRepository.save(Chat.builder()
102113
.historyId(history)
103114
.type(type)
@@ -107,7 +118,7 @@ private void saveChatWithDocuments(History history, MessageType type, String mes
107118
// Ai 메시지가 저장될 때 관련 문서 저장
108119
if (type == MessageType.ASSISTANT) {
109120
if (similarCaseDocuments != null && !similarCaseDocuments.isEmpty()) {
110-
List<ChatPrecedent> chatPrecedents = similarCaseDocuments.stream()
121+
chatPrecedents = similarCaseDocuments.stream()
111122
.map(doc -> ChatPrecedent.builder()
112123
.chatId(chat)
113124
.precedentContent(doc.getText())
@@ -119,7 +130,7 @@ private void saveChatWithDocuments(History history, MessageType type, String mes
119130
}
120131

121132
if (similarLawDocuments != null && !similarLawDocuments.isEmpty()) {
122-
List<ChatLaw> chatLaws = similarLawDocuments.stream()
133+
chatLaws = similarLawDocuments.stream()
123134
.map(doc -> ChatLaw.builder()
124135
.chatId(chat)
125136
.content(doc.getText())
@@ -129,5 +140,16 @@ private void saveChatWithDocuments(History history, MessageType type, String mes
129140
chatLawRepository.saveAll(chatLaws);
130141
}
131142
}
143+
144+
// Redis 캐시에 DTO 저장
145+
ChatHistoryDto dto = ChatHistoryDto.builder()
146+
.type(type.toString())
147+
.message(message)
148+
.createdAt(chat.getCreatedAt())
149+
.precedent(chatPrecedents.isEmpty() ? null : ChatPrecedentDto.from(chatPrecedents.get(0)))
150+
.law(chatLaws.isEmpty() ? null : ChatLawDto.from(chatLaws.get(0)))
151+
.build();
152+
153+
chatCacheService.cacheChatMessage(history.getHistoryId(), dto);
132154
}
133155
}

0 commit comments

Comments
 (0)