Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
356 changes: 356 additions & 0 deletions docs/채팅서비스_아키텍처.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,356 @@
# 채팅 기능 아키텍처 문서

## 1. 개요

WeGo 애플리케이션의 실시간 채팅 기능입니다. 그룹 채팅(모임 기반)과 1:1 DM 채팅을 지원합니다.

### 주요 기능
- 모임 생성 시 그룹 채팅방 자동 생성
- 모임 참여 시 채팅방 자동 참여
- 1:1 DM 채팅
- 실시간 메시지 송수신 (WebSocket/STOMP)
- 읽음 처리 및 안읽은 메시지 카운트
- 참여자 추방 기능

---

## 2. 패키지 구조

```
chat/
├── application/
│ ├── dto/
│ │ ├── request/
│ │ │ ├── CreateDmRequest.java # DM 채팅방 생성 요청
│ │ │ ├── KickParticipantRequest.java # 참여자 추방 요청
│ │ │ └── SendMessageRequest.java # 메시지 전송 요청 (WebSocket)
│ │ └── response/
│ │ ├── ChatMessagePayload.java # WebSocket 메시지 페이로드
│ │ ├── ChatRoomItemResponse.java # 채팅방 목록 아이템
│ │ ├── ChatRoomListResponse.java # 채팅방 목록
│ │ ├── ChatRoomResponse.java # 채팅방 상세
│ │ ├── KickNotificationPayload.java # 추방 알림 페이로드
│ │ ├── LastMessageResponse.java # 마지막 메시지 정보
│ │ ├── MessageListResponse.java # 메시지 목록 (페이징)
│ │ ├── MessageResponse.java # 메시지 상세
│ │ ├── ParticipantListResponse.java # 참여자 목록
│ │ ├── ParticipantResponse.java # 참여자 정보
│ │ └── ReadStatusResponse.java # 읽음 상태
│ ├── listener/
│ │ └── ChatEventListener.java # 모임 이벤트 리스너
│ └── service/
│ ├── ChatMessageService.java # 메시지 관리 서비스
│ └── ChatRoomService.java # 채팅방 관리 서비스
├── config/
│ ├── ChatProperties.java # 채팅 설정 (application.yml)
│ ├── StompChannelInterceptor.java # WebSocket 인증 인터셉터
│ └── WebSocketConfig.java # WebSocket 설정
├── domain/
│ ├── entity/
│ │ ├── ChatMessage.java # 메시지 엔티티
│ │ ├── ChatParticipant.java # 참여자 엔티티
│ │ ├── ChatRoom.java # 채팅방 엔티티
│ │ ├── ChatType.java # GROUP, DM
│ │ ├── JoinType.java # AUTO, MANUAL
│ │ ├── MessageType.java # TEXT, SYSTEM
│ │ └── ParticipantStatus.java # ACTIVE, LEFT, KICKED
│ ├── exception/
│ │ ├── ChatErrorCode.java # 에러 코드 정의
│ │ └── ChatException.java # 채팅 예외
│ └── repository/
│ ├── ChatMessageRepository.java
│ ├── ChatParticipantRepository.java
│ └── ChatRoomRepository.java
└── presentation/
├── ChatMessageController.java # WebSocket 메시지 핸들러
└── ChatRoomController.java # REST API 컨트롤러
```

---

## 3. 데이터베이스 스키마

### chat_room (채팅방)
| 컬럼명 | 타입 | 설명 |
|--------|------|------|
| chat_room_id | BIGINT | PK |
| chat_type | ENUM('GROUP', 'DM') | 채팅방 타입 |
| group_id | BIGINT | FK (그룹 채팅만) |
| expires_at | TIMESTAMP | 만료 시간 |
| created_at | TIMESTAMP | 생성 시간 |
| updated_at | TIMESTAMP | 수정 시간 |

### chat_participant (참여자)
| 컬럼명 | 타입 | 설명 |
|--------|------|------|
| participant_id | BIGINT | PK |
| chat_room_id | BIGINT | FK |
| user_id | BIGINT | FK |
| joined_at | TIMESTAMP | 참여 시간 |
| join_type | ENUM('AUTO', 'MANUAL') | 참여 방식 |
| last_read_message_id | BIGINT | 마지막 읽은 메시지 |
| status | ENUM('ACTIVE', 'LEFT', 'KICKED') | 참여 상태 |
| updated_at | TIMESTAMP | 수정 시간 |

### chat_message (메시지)
| 컬럼명 | 타입 | 설명 |
|--------|------|------|
| message_id | BIGINT | PK |
| chat_room_id | BIGINT | FK |
| sender_id | BIGINT | FK (시스템 메시지는 NULL) |
| content | TEXT | 메시지 내용 |
| message_type | ENUM('TEXT', 'SYSTEM') | 메시지 타입 |
| created_at | TIMESTAMP | 전송 시간 |

---

## 4. REST API

### 4.1 채팅방 목록 조회
```
GET /api/v1/chat/rooms
Authorization: Bearer {token}
```

**Response:**
```json
{
"status": 200,
"success": true,
"data": {
"chatRooms": [
{
"chatRoomId": 1,
"chatType": "GROUP",
"chatRoomName": "강남 러닝 모임",
"groupId": 10,
"participantCount": 5,
"lastMessage": {
"content": "내일 뵙겠습니다!",
"senderName": "홍길동",
"timestamp": "2025-12-29T10:30:00"
},
"unreadCount": 3,
"updatedAt": "2025-12-29T10:30:00"
}
]
}
}
```

### 4.2 채팅방 상세 조회
```
GET /api/v1/chat/rooms/{roomId}
Authorization: Bearer {token}
```

### 4.3 메시지 이력 조회 (커서 기반 페이징)
```
GET /api/v1/chat/rooms/{roomId}/messages?cursor={messageId}&size=50
Authorization: Bearer {token}
```

**Response:**
```json
{
"status": 200,
"success": true,
"data": {
"messages": [
{
"messageId": 99,
"senderId": 5,
"senderName": "홍길동",
"senderProfileImage": "https://...",
"content": "안녕하세요!",
"messageType": "TEXT",
"createdAt": "2025-12-29T10:30:00"
}
],
"hasNext": true,
"nextCursor": 50
}
}
```

### 4.4 읽음 처리
```
PUT /api/v1/chat/rooms/{roomId}/read
Authorization: Bearer {token}
```

### 4.5 참여자 추방 (방장 전용)
```
POST /api/v1/chat/rooms/{roomId}/kick
Authorization: Bearer {token}
Content-Type: application/json

{
"targetUserId": 7
}
```

### 4.6 채팅방 나가기
```
POST /api/v1/chat/rooms/{roomId}/leave
Authorization: Bearer {token}
```

### 4.7 1:1 채팅방 생성/조회
```
POST /api/v1/chat/dm
Authorization: Bearer {token}
Content-Type: application/json

{
"targetUserId": 5
}
```

### 4.8 참여자 목록 조회
```
GET /api/v1/chat/rooms/{roomId}/participants
Authorization: Bearer {token}
```

---

## 5. WebSocket (STOMP)

### 5.1 연결 정보
- **엔드포인트**: `/ws-chat`
- **SockJS 지원**: `/ws-chat` (withSockJS)

### 5.2 인증
STOMP CONNECT 시 Authorization 헤더에 JWT 토큰 전달:
```
Authorization: Bearer {accessToken}
```

### 5.3 메시지 전송
```
SEND /pub/chat/message
{
"chatRoomId": 1,
"content": "안녕하세요!"
}
```

### 5.4 구독

**채팅방 메시지 구독:**
```
SUBSCRIBE /sub/chat/room/{roomId}
```

**개인 알림 구독 (추방 알림 등):**
```
SUBSCRIBE /sub/user/{userId}
```

### 5.5 메시지 페이로드

**일반 메시지:**
```json
{
"messageId": 123,
"chatRoomId": 1,
"senderId": 5,
"senderName": "홍길동",
"senderProfileImage": "https://...",
"content": "안녕하세요!",
"messageType": "TEXT",
"timestamp": "2025-12-29T10:30:00"
}
```

**시스템 메시지:**
```json
{
"messageId": 124,
"chatRoomId": 1,
"senderId": null,
"senderName": null,
"content": "홍길동님이 입장했습니다",
"messageType": "SYSTEM",
"timestamp": "2025-12-29T10:30:00"
}
```

**추방 알림:**
```json
{
"type": "KICKED",
"chatRoomId": 1,
"message": "채팅방에서 퇴장되었습니다",
"timestamp": "2025-12-29T10:30:00"
}
```

---

## 6. 이벤트 연동

### 6.1 모임 생성 → 채팅방 자동 생성
```
GroupCreatedEvent → ChatEventListener.handleGroupCreated()
→ ChatRoomService.createGroupChatRoomForMeeting()
```

### 6.2 모임 참여 → 채팅방 자동 참여
```
GroupJoinedEvent → ChatEventListener.handleGroupJoined()
→ ChatRoomService.joinChatRoomByGroup()
```

### 6.3 모임 퇴장 → 채팅방 퇴장
```
GroupLeftEvent → ChatEventListener.handleGroupLeft()
→ ChatRoomService.leaveChatRoomByGroup()
```

---

## 7. 설정 (application.yml)

```yaml
chat:
auto-join:
enabled: true # 모임 참여 시 채팅방 자동 참여
dm:
delete-policy: NONE # NONE | INACTIVE_DAYS
inactive-days: 90
websocket:
endpoint: /ws-chat
allowed-origins:
- http://localhost:3000
- http://localhost:8080
message:
max-length: 1000
batch:
delete-expired-chat:
cron: "0 0 * * * *"
```

---

## 8. 에러 코드

| 코드 | HTTP Status | 메시지 |
|------|-------------|--------|
| CHAT_ROOM_NOT_FOUND | 404 | 채팅방을 찾을 수 없습니다 |
| NOT_CHAT_PARTICIPANT | 403 | 채팅방에 참여하지 않았습니다 |
| NOT_CHAT_ROOM_OWNER | 403 | 방장만 사용할 수 있는 기능입니다 |
| CANNOT_KICK_SELF | 400 | 자기 자신을 추방할 수 없습니다 |
| PARTICIPANT_KICKED | 403 | 채팅방에서 추방되었습니다 |
| MESSAGE_TOO_LONG | 400 | 메시지가 너무 깁니다 |
| MESSAGE_EMPTY | 400 | 메시지 내용을 입력해주세요 |
| CANNOT_DM_SELF | 400 | 자기 자신에게 메시지를 보낼 수 없습니다 |

---

## 9. 미구현 기능 (TODO)

- [ ] 만료 채팅방 삭제 배치 작업
- [ ] 메시지 검색
- [ ] 파일/이미지 전송
Loading