- plans: 완료 상태로 정리 - troubleshooting: 전체 과정과 실수 상세 기록 - sequences: JWT → Gateway → rb8001 → DB 플로우 문서화 - 정확한 정보만 남기고 추측/틀린 내용 모두 명시
222 lines
5.1 KiB
Markdown
222 lines
5.1 KiB
Markdown
# 대화 히스토리 조회 플로우
|
|
|
|
## 작성일: 2025-09-02
|
|
## 대상: rb8001/rb10508_micro 대화 히스토리
|
|
## 상태: ✅ 구현 완료
|
|
|
|
---
|
|
|
|
## 1. 전체 플로우
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant F as Frontend
|
|
participant G as Gateway
|
|
participant R as rb8001
|
|
participant DB as PostgreSQL
|
|
|
|
F->>F: localStorage에서 JWT 토큰 가져오기
|
|
F->>F: JWT decode하여 user_id 추출
|
|
F->>G: GET /gateway/api/history?limit=30<br/>Authorization: Bearer {JWT}
|
|
G->>G: JWT 검증 (서명, 만료시간)
|
|
G->>G: JWT에서 sub(UUID) 추출
|
|
G->>G: workspace_members 테이블 조회<br/>robeing_id 확인 (rb8001)
|
|
G->>R: GET /api/history?limit=30<br/>Authorization: Bearer {JWT}<br/>X-User-Id: {UUID}
|
|
R->>R: JWT 검증 및 user_id 추출
|
|
R->>DB: SELECT * FROM conversation_logs<br/>WHERE user_id = (:user_id)::uuid
|
|
DB-->>R: 대화 기록 반환
|
|
R->>R: DB row를 Frontend 형식으로 변환<br/>(user/robeing 메시지 분리)
|
|
R-->>G: {"messages": [...], "has_more": true}
|
|
G-->>F: 응답 전달
|
|
F->>F: 화면에 메시지 렌더링
|
|
```
|
|
|
|
---
|
|
|
|
## 2. JWT 토큰 구조
|
|
|
|
### 2.1 auth-server 발급 토큰
|
|
```json
|
|
{
|
|
"sub": "1e16e9d5-59f3-54da-a661-8abeabff4230", // UUID
|
|
"email": "goeun2dc@gmail.com",
|
|
"name": "김종태",
|
|
"username": "happybell80",
|
|
"picture": "https://...",
|
|
"exp": 1759046456
|
|
}
|
|
```
|
|
|
|
### 2.2 중요 필드
|
|
- `sub`: 사용자 UUID (users.id)
|
|
- `username`: 사용자명
|
|
- `exp`: 만료 시간 (Unix timestamp)
|
|
|
|
---
|
|
|
|
## 3. Gateway 처리
|
|
|
|
### 3.1 JWT 검증
|
|
```python
|
|
# Gateway main.py
|
|
def verify_jwt_token(token: str):
|
|
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=["HS256"])
|
|
return payload["sub"] # UUID 반환
|
|
```
|
|
|
|
### 3.2 robeing 라우팅
|
|
```python
|
|
# workspace_members 테이블 조회
|
|
SELECT robeing_id FROM workspace_members
|
|
WHERE user_id = :user_id
|
|
|
|
# 결과: rb8001 또는 rb10508_micro
|
|
```
|
|
|
|
### 3.3 헤더 전달
|
|
```python
|
|
headers = {
|
|
"Authorization": request.headers.get("Authorization"), # JWT 전달
|
|
"X-User-Id": user_id # UUID 전달
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. rb8001 처리
|
|
|
|
### 4.1 JWT 인증
|
|
```python
|
|
# rb8001 main.py
|
|
async def get_current_user(authorization: str = Header(None)):
|
|
token = authorization.replace("Bearer ", "")
|
|
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=["HS256"])
|
|
return payload["sub"] # UUID
|
|
```
|
|
|
|
### 4.2 DB 조회
|
|
```python
|
|
# database.py
|
|
async def get_paginated_conversations(user_id: str, before: float = None, limit: int = 30):
|
|
query = """
|
|
SELECT id, message, response, timestamp
|
|
FROM conversation_logs
|
|
WHERE user_id = (:user_id)::uuid
|
|
AND robeing_id = 'rb8001'
|
|
AND (:before::timestamp IS NULL OR timestamp < :before)
|
|
ORDER BY timestamp DESC
|
|
LIMIT :limit
|
|
"""
|
|
```
|
|
|
|
### 4.3 응답 변환
|
|
```python
|
|
# DB row → Frontend 형식
|
|
messages = []
|
|
for row in results:
|
|
# User 메시지
|
|
messages.append({
|
|
"id": f"{row['id']}_user",
|
|
"text": row["message"],
|
|
"sender": "user",
|
|
"timestamp": row["timestamp"].isoformat()
|
|
})
|
|
# Robeing 응답
|
|
messages.append({
|
|
"id": f"{row['id']}_robeing",
|
|
"text": row["response"],
|
|
"sender": "robeing",
|
|
"timestamp": row["timestamp"].isoformat()
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Frontend 처리
|
|
|
|
### 5.1 API 호출
|
|
```typescript
|
|
// robeing-api.ts
|
|
export async function getHistory(before?: string, limit: number = 30) {
|
|
const params = new URLSearchParams({ limit: limit.toString() });
|
|
if (before) params.append('before', before);
|
|
|
|
const response = await fetch(`${API_URL}/api/history?${params}`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
}
|
|
});
|
|
return response.json();
|
|
}
|
|
```
|
|
|
|
### 5.2 무한 스크롤
|
|
```typescript
|
|
// Intersection Observer로 스크롤 감지
|
|
const observer = new IntersectionObserver((entries) => {
|
|
if (entries[0].isIntersecting && hasMore) {
|
|
const oldestMessage = messages[0];
|
|
loadMoreMessages(oldestMessage.timestamp);
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 6. 데이터베이스 스키마
|
|
|
|
### 6.1 users 테이블
|
|
```sql
|
|
CREATE TABLE users (
|
|
id UUID PRIMARY KEY,
|
|
email VARCHAR(255) UNIQUE,
|
|
username VARCHAR(50) UNIQUE,
|
|
name VARCHAR(255)
|
|
);
|
|
```
|
|
|
|
### 6.2 workspace_members 테이블
|
|
```sql
|
|
CREATE TABLE workspace_members (
|
|
user_id UUID REFERENCES users(id),
|
|
workspace_id UUID,
|
|
robeing_id VARCHAR(50) -- rb8001, rb10508_micro 등
|
|
);
|
|
```
|
|
|
|
### 6.3 conversation_logs 테이블
|
|
```sql
|
|
CREATE TABLE conversation_logs (
|
|
id INTEGER PRIMARY KEY,
|
|
robeing_id VARCHAR,
|
|
message VARCHAR,
|
|
response VARCHAR,
|
|
timestamp TIMESTAMP,
|
|
user_id UUID REFERENCES users(id)
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## 7. 환경변수 설정
|
|
|
|
### 7.1 Gateway
|
|
```env
|
|
JWT_SECRET_KEY=9cc562b6296b87b02dd89045a2e7e11c249713a59a5ac0160d852121f1289664
|
|
DEFAULT_ROBEING_HOST=192.168.219.52
|
|
DEFAULT_ROBEING_PORT=8001
|
|
```
|
|
|
|
### 7.2 rb8001
|
|
```env
|
|
JWT_SECRET_KEY=9cc562b6296b87b02dd89045a2e7e11c249713a59a5ac0160d852121f1289664
|
|
MESSAGE_BATCH_SIZE=30
|
|
MAX_MESSAGES_IN_DOM=200
|
|
```
|
|
|
|
---
|
|
|
|
## 8. 트러블슈팅 참고
|
|
|
|
자세한 문제 해결 과정은 다음 문서 참조:
|
|
- troubleshooting/250901_rb8001_chat_history_implementation_issues.md |