Skip to content

Commit b296e8f

Browse files
authored
Merge pull request #318 from prgrms-web-devcourse-final-project/develop
개발 서버 배포
2 parents 0abd62b + c78b000 commit b296e8f

File tree

82 files changed

+2778
-870
lines changed

Some content is hidden

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

82 files changed

+2778
-870
lines changed

backend/.env.default

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,5 @@ CUSTOM_CORS_ALLOWED_ORIGINS=NEED_TO_SET
7777
CUSTOM_OAUTH2_REDIRECT_URL=NEED_TO_SET
7878
CUSTOM_OAUTH2_FAILURE_URL=NEED_TO_SET
7979
CUSTOM_FRONTEND_URL=NEED_TO_SET
80+
PROD_COOKIE_DOMAIN=NEED_TO_SET
81+
DEV_COOKIE_DOMAIN=NEED_TO_SET

backend/build.gradle

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ 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'
45+
testImplementation 'org.springframework.kafka:spring-kafka-test'
4446

4547
// API Documentation (문서화)
4648
implementation 'org.apache.commons:commons-lang3:3.18.0'
@@ -60,8 +62,8 @@ dependencies {
6062
implementation 'org.springframework.session:spring-session-data-redis'
6163

6264
// Logging & Monitoring (로깅/모니터링)
63-
implementation 'io.sentry:sentry-spring-boot-starter:7.16.0'
64-
implementation 'io.sentry:sentry-logback:7.16.0'
65+
implementation 'io.sentry:sentry-spring-boot-starter:7.18.0'
66+
implementation 'io.sentry:sentry-logback:7.18.0'
6567

6668
// Development Tools (개발 도구)
6769
compileOnly 'org.projectlombok:lombok'

backend/docker-compose.yml

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ services:
1717
volumes:
1818
- mysql-data:/var/lib/mysql
1919
- ./sql:/sql # 호스트 ./sql 폴더를 컨테이너 /sql에 마운트
20-
command: >
20+
command: >
2121
--character-set-server=utf8mb4
2222
--collation-server=utf8mb4_0900_ai_ci
2323
--default-time-zone=Asia/Seoul
2424
--skip-log-bin
2525
--lower-case-table-names=1
2626
healthcheck:
27-
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-p${DEV_DATASOURCE_PASSWORD}"]
27+
test: [ "CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-p${DEV_DATASOURCE_PASSWORD}" ]
2828
interval: 10s
2929
timeout: 5s
3030
retries: 10
@@ -40,7 +40,7 @@ services:
4040
command: >
4141
redis-server --appendonly yes
4242
healthcheck:
43-
test: ["CMD", "redis-cli", "ping"]
43+
test: [ "CMD", "redis-cli", "ping" ]
4444
interval: 10s
4545
timeout: 5s
4646
retries: 10
@@ -62,6 +62,10 @@ services:
6262
image: ollama/ollama:latest
6363
container_name: ollama
6464
restart: unless-stopped
65+
deploy:
66+
resources:
67+
limits:
68+
memory: 2g
6569
ports:
6670
- "11434:11434"
6771
volumes:
@@ -78,8 +82,40 @@ services:
7882
timeout: 5s
7983
retries: 10
8084

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+
81116
volumes:
82117
mysql-data:
83118
redis-data:
84119
qdrant-data:
85-
ollama-data:
120+
ollama-data:
121+
kafka-data:

backend/sql/member_id_sequence.sql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- ============================================
2+
-- 공유 member_id 시퀀스 테이블 생성
3+
-- ============================================
4+
-- 주의: 기존 배포 환경에 데이터가 있다면
5+
-- backend/sql/migrate_member_id_sequence.sql을 먼저 실행해야 합니다!
6+
-- ============================================
7+
8+
CREATE TABLE IF NOT EXISTS member_id_sequence (
9+
sequence_name VARCHAR(255) NOT NULL PRIMARY KEY,
10+
next_val BIGINT NOT NULL
11+
);
12+
13+
-- 초기값 설정: 신규 설치 시 1부터 시작
14+
-- 기존 환경은 마이그레이션 스크립트로 처리
15+
INSERT INTO member_id_sequence (sequence_name, next_val)
16+
VALUES ('member_id_seq', 1)
17+
ON DUPLICATE KEY UPDATE sequence_name = sequence_name;
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
-- ============================================
2+
-- member_id 중복 해결 및 공유 시퀀스 설정 마이그레이션
3+
-- ============================================
4+
-- 실행 전 주의사항:
5+
-- 1. 반드시 데이터베이스 백업을 먼저 수행하세요!
6+
-- 2. 서비스를 중단한 상태에서 실행하세요 (데이터 정합성)
7+
-- 3. 실행 후 애플리케이션을 재시작하세요
8+
-- ============================================
9+
10+
-- 1단계: 현재 상태 확인
11+
SELECT '=== 현재 member 테이블 ===' as info;
12+
SELECT COUNT(*) as count, MIN(member_id) as min_id, MAX(member_id) as max_id FROM member;
13+
14+
SELECT '=== 현재 oauth2_member 테이블 ===' as info;
15+
SELECT COUNT(*) as count, MIN(member_id) as min_id, MAX(member_id) as max_id FROM oauth2_member;
16+
17+
SELECT '=== 중복 확인 ===' as info;
18+
SELECT m.member_id, 'BOTH' as status
19+
FROM member m
20+
INNER JOIN oauth2_member o ON m.member_id = o.member_id;
21+
22+
-- 2단계: oauth2_member의 member_id를 재할당
23+
-- member 테이블의 최대값 이후부터 시작
24+
SET @max_member_id := (SELECT COALESCE(MAX(member_id), 0) FROM member);
25+
26+
SELECT CONCAT('member 테이블 최대 ID: ', @max_member_id) as info;
27+
28+
-- 임시 테이블로 매핑 생성
29+
CREATE TEMPORARY TABLE IF NOT EXISTS oauth2_member_id_mapping (
30+
old_member_id BIGINT,
31+
new_member_id BIGINT,
32+
PRIMARY KEY (old_member_id)
33+
);
34+
35+
-- 매핑 데이터 생성
36+
INSERT INTO oauth2_member_id_mapping (old_member_id, new_member_id)
37+
SELECT
38+
member_id as old_member_id,
39+
@max_member_id + ROW_NUMBER() OVER (ORDER BY member_id) as new_member_id
40+
FROM oauth2_member
41+
ORDER BY member_id;
42+
43+
SELECT '=== 매핑 테이블 ===' as info;
44+
SELECT * FROM oauth2_member_id_mapping;
45+
46+
-- 3단계: 외래키 제약 확인 (있다면 비활성화)
47+
SET FOREIGN_KEY_CHECKS = 0;
48+
49+
-- 4단계: 연관 테이블 업데이트 (member_id를 외래키로 가진 테이블만)
50+
-- Post 테이블 (FK: member_id)
51+
UPDATE post p
52+
INNER JOIN oauth2_member_id_mapping m ON p.member_id = m.old_member_id
53+
SET p.member_id = m.new_member_id;
54+
55+
-- PollVote 테이블 (FK: member_id)
56+
UPDATE poll_vote pv
57+
INNER JOIN oauth2_member_id_mapping m ON pv.member_id = m.old_member_id
58+
SET pv.member_id = m.new_member_id;
59+
60+
-- History 테이블 (FK: member_id)
61+
UPDATE history h
62+
INNER JOIN oauth2_member_id_mapping m ON h.member_id = m.old_member_id
63+
SET h.member_id = m.new_member_id;
64+
65+
-- 5단계: oauth2_member 테이블 업데이트
66+
UPDATE oauth2_member o
67+
INNER JOIN oauth2_member_id_mapping m ON o.member_id = m.old_member_id
68+
SET o.member_id = m.new_member_id;
69+
70+
-- 6단계: 외래키 제약 다시 활성화
71+
SET FOREIGN_KEY_CHECKS = 1;
72+
73+
-- 7단계: 시퀀스 테이블 생성
74+
CREATE TABLE IF NOT EXISTS member_id_sequence (
75+
sequence_name VARCHAR(255) NOT NULL PRIMARY KEY,
76+
next_val BIGINT NOT NULL
77+
);
78+
79+
-- 8단계: 초기값 설정 (재할당 후 최대값 + 1)
80+
INSERT INTO member_id_sequence (sequence_name, next_val)
81+
SELECT 'member_id_seq', COALESCE(MAX(max_id), 0) + 1
82+
FROM (
83+
SELECT COALESCE(MAX(member_id), 0) as max_id FROM member
84+
UNION ALL
85+
SELECT COALESCE(MAX(member_id), 0) as max_id FROM oauth2_member
86+
) as max_values
87+
ON DUPLICATE KEY UPDATE next_val = VALUES(next_val);
88+
89+
-- 9단계: 최종 상태 확인
90+
SELECT '=== 마이그레이션 후 member 테이블 ===' as info;
91+
SELECT COUNT(*) as count, MIN(member_id) as min_id, MAX(member_id) as max_id FROM member;
92+
93+
SELECT '=== 마이그레이션 후 oauth2_member 테이블 ===' as info;
94+
SELECT COUNT(*) as count, MIN(member_id) as min_id, MAX(member_id) as max_id FROM oauth2_member;
95+
96+
SELECT '=== 중복 확인 (0건이어야 함) ===' as info;
97+
SELECT m.member_id, 'BOTH' as status
98+
FROM member m
99+
INNER JOIN oauth2_member o ON m.member_id = o.member_id;
100+
101+
SELECT '=== 시퀀스 초기값 ===' as info;
102+
SELECT * FROM member_id_sequence;
103+
104+
-- 임시 테이블 삭제
105+
DROP TEMPORARY TABLE IF EXISTS oauth2_member_id_mapping;
106+
107+
SELECT '=== 마이그레이션 완료! ===' as info;

backend/src/main/java/com/ai/lawyer/BackendApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
55
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
66
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
7+
import org.springframework.scheduling.annotation.EnableAsync;
78

9+
@EnableAsync
810
@SpringBootApplication
911
@EnableJpaAuditing
1012
@ConfigurationPropertiesScan

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.ai.lawyer.domain.chatbot.dto.ChatDto.ChatHistoryDto;
44
import com.ai.lawyer.domain.chatbot.dto.HistoryDto;
5-
import com.ai.lawyer.domain.chatbot.service.ChatService;
65
import com.ai.lawyer.domain.chatbot.service.HistoryService;
76
import io.swagger.v3.oas.annotations.Operation;
87
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -21,7 +20,6 @@
2120
public class HistoryController {
2221

2322
private final HistoryService historyService;
24-
private final ChatService chatService;
2523

2624
@Operation(summary = "채팅방 제목 목록 조회")
2725
@GetMapping("/")
@@ -32,7 +30,7 @@ public ResponseEntity<List<HistoryDto>> getHistoryTitles(@AuthenticationPrincipa
3230
@Operation(summary = "채팅 조회")
3331
@GetMapping("/{historyId}")
3432
public ResponseEntity<List<ChatHistoryDto>> getChatHistory(@AuthenticationPrincipal Long memberId, @PathVariable("historyId") Long roomId) {
35-
return chatService.getChatHistory(memberId, roomId);
33+
return historyService.getChatHistory(memberId, roomId);
3634
}
3735

3836
@Operation(summary = "채팅방 삭제")

backend/src/main/java/com/ai/lawyer/domain/chatbot/dto/ChatDto.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ public static class ChatResponse {
3434
@Schema(description = "채팅방 ID", example = "1")
3535
private Long roomId;
3636

37-
@Schema(description = "History 방 제목", example = "손해배상 청구 관련 문의")
38-
private String title;
39-
4037
@Schema(description = "AI 챗봇의 응답 메시지", example = "네, 관련 법령과 판례를 바탕으로 답변해 드리겠습니다.")
4138
private String message;
4239

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ public class Chat {
3333
@Lob
3434
private String message;
3535

36-
@OneToMany(mappedBy = "chatId")
36+
@OneToMany(mappedBy = "chatId", cascade = CascadeType.ALL, orphanRemoval = true)
3737
private List<ChatPrecedent> chatPrecedents;
3838

39-
@OneToMany(mappedBy = "chatId")
39+
@OneToMany(mappedBy = "chatId", cascade = CascadeType.ALL, orphanRemoval = true)
4040
private List<ChatLaw> chatLaws;
4141

4242
@CreationTimestamp

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class History {
2525
private Long historyId;
2626

2727
@ManyToOne
28-
@JoinColumn(name = "member_id")
28+
@JoinColumn(name = "member_id", foreignKey = @ForeignKey(name = "FK_HISTORY_MEMBER"))
2929
private Member memberId;
3030

3131
@OneToMany(mappedBy = "historyId", cascade = CascadeType.ALL, orphanRemoval = true)

0 commit comments

Comments
 (0)