dd/SQL_VIEWER_GUIDE.md

352 lines
8.2 KiB
Markdown

# Durable Object SQLite 조회 가이드
Durable Object의 내장 SQLite 데이터베이스를 조회하는 여러 가지 방법을 제공합니다.
## 📋 목차
1. [웹 인터페이스로 조회](#1-웹-인터페이스로-조회)
2. [API 엔드포인트로 조회](#2-api-엔드포인트로-조회)
3. [WebSocket으로 조회](#3-websocket으로-조회)
4. [사용 가능한 테이블](#4-사용-가능한-테이블)
---
## 1. 웹 인터페이스로 조회
가장 간편한 방법입니다. 브라우저에서 바로 SQLite 데이터를 확인할 수 있습니다.
### 접속 방법
```
http://localhost:8788/sql-viewer
```
### 기능
- **📊 DB 정보**: 데이터베이스 크기, 테이블 목록, 레코드 수 등
- **👥 사용자 목록**: 모든 사용자 조회
- **🎲 배팅 목록**: 최근 배팅 내역 조회
- **⚙️ 커스텀 쿼리**: 원하는 SQL 쿼리 직접 실행
### 예시 화면
```
┌─────────────────────────────────────┐
│ 🔍 Durable Object SQLite Viewer │
├─────────────────────────────────────┤
│ [📊 DB 정보] [👥 사용자] [🎲 배팅] │
│ │
│ 결과: │
│ { │
│ "databaseSize": 12345, │
│ "userCount": 10, │
│ "betCount": 50 │
│ } │
└─────────────────────────────────────┘
```
---
## 2. API 엔드포인트로 조회
프로그래밍 방식으로 데이터를 조회할 수 있습니다.
### 2.1 데이터베이스 정보 조회
```bash
# HTTP GET
curl http://localhost:8788/sql-api/info
```
**응답 예시:**
```json
{
"databaseSize": 24576,
"userCount": 3,
"betCount": 15,
"currentGameId": "550e8400-e29b-41d4-a716-446655440000",
"tables": [
{ "name": "user" },
{ "name": "current_bet" }
]
}
```
### 2.2 모든 사용자 조회
```bash
curl http://localhost:8788/sql-api/users
```
**응답 예시:**
```json
[
{
"id": "user-123",
"nickname": "홍길동",
"email": "hong@example.com",
"joinGameCount": 5,
"capital": 15000
}
]
```
### 2.3 배팅 내역 조회
```bash
# 모든 배팅 (최근 100개)
curl http://localhost:8788/sql-api/bets
# 특정 게임의 배팅
curl "http://localhost:8788/sql-api/bets?gameId=550e8400-e29b-41d4-a716-446655440000"
```
**응답 예시:**
```json
[
{
"id": 1,
"gameId": "550e8400-e29b-41d4-a716-446655440000",
"diceNum": 12,
"userId": "user-123",
"betType": "odd",
"amount": 1000,
"isWin": 1,
"reward": 2000
}
]
```
### 2.4 커스텀 SQL 쿼리 실행
```bash
curl -X POST http://localhost:8788/sql-api/query \
-H "Content-Type: application/json" \
-d '{"query": "SELECT * FROM user WHERE capital > 10000"}'
```
---
## 3. WebSocket으로 조회
실시간 게임 중에 WebSocket 연결을 통해 SQL을 조회할 수 있습니다.
### JavaScript 예시
```javascript
// WebSocket 연결
const ws = new WebSocket('ws://localhost:8788/api/counter');
// DB 정보 조회
ws.send(JSON.stringify({
type: 'sqlQuery',
query: 'info'
}));
// 모든 사용자 조회
ws.send(JSON.stringify({
type: 'sqlQuery',
query: 'users'
}));
// 커스텀 쿼리
ws.send(JSON.stringify({
type: 'sqlQuery',
query: 'SELECT * FROM current_bet WHERE amount > 5000'
}));
// 결과 수신
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'sqlResult') {
console.log('SQL 결과:', data.data);
} else if (data.type === 'sqlError') {
console.error('SQL 에러:', data.error);
}
};
```
---
## 4. 사용 가능한 테이블
### 4.1 `user` 테이블
사용자 정보를 저장합니다.
| 컬럼 | 타입 | 설명 |
|------|------|------|
| `id` | TEXT | 사용자 고유 ID (PRIMARY KEY) |
| `nickname` | TEXT | 닉네임 |
| `email` | TEXT | 이메일 (UNIQUE) |
| `joinGameCount` | INTEGER | 참여한 게임 횟수 |
| `capital` | INTEGER | 현재 자본금 |
**예제 쿼리:**
```sql
-- 모든 사용자 조회
SELECT * FROM user;
-- 자본금이 10000 이상인 사용자
SELECT * FROM user WHERE capital >= 10000;
-- 가장 많이 참여한 사용자
SELECT * FROM user ORDER BY joinGameCount DESC LIMIT 10;
```
### 4.2 `current_bet` 테이블
배팅 정보를 저장합니다.
| 컬럼 | 타입 | 설명 |
|------|------|------|
| `id` | INTEGER | 배팅 고유 ID (PRIMARY KEY, AUTO INCREMENT) |
| `gameId` | TEXT | 게임 고유 ID |
| `diceNum` | INTEGER | 주사위 합계 |
| `userId` | TEXT | 사용자 ID (FOREIGN KEY) |
| `betType` | TEXT | 배팅 타입 (odd/even/big/small) |
| `amount` | INTEGER | 배팅 금액 |
| `isWin` | INTEGER | 승리 여부 (0=패배, 1=승리) |
| `reward` | INTEGER | 보상 금액 |
**예제 쿼리:**
```sql
-- 모든 배팅 조회
SELECT * FROM current_bet;
-- 특정 게임의 배팅
SELECT * FROM current_bet WHERE gameId = 'xxx';
-- 사용자별 총 배팅액
SELECT userId, SUM(amount) as totalBet
FROM current_bet
GROUP BY userId;
-- 승리한 배팅만 조회
SELECT * FROM current_bet WHERE isWin = 1;
-- 게임별 통계
SELECT
gameId,
COUNT(*) as betCount,
SUM(amount) as totalAmount,
SUM(CASE WHEN isWin = 1 THEN 1 ELSE 0 END) as winCount
FROM current_bet
GROUP BY gameId;
```
---
## 5. 고급 쿼리 예제
### 5.1 사용자별 승률 계산
```sql
SELECT
u.nickname,
COUNT(cb.id) as totalBets,
SUM(CASE WHEN cb.isWin = 1 THEN 1 ELSE 0 END) as wins,
CAST(SUM(CASE WHEN cb.isWin = 1 THEN 1 ELSE 0 END) AS FLOAT) / COUNT(cb.id) * 100 as winRate
FROM user u
LEFT JOIN current_bet cb ON u.id = cb.userId
GROUP BY u.id, u.nickname
HAVING COUNT(cb.id) > 0;
```
### 5.2 배팅 타입별 통계
```sql
SELECT
betType,
COUNT(*) as count,
SUM(amount) as totalAmount,
AVG(amount) as avgAmount,
SUM(CASE WHEN isWin = 1 THEN 1 ELSE 0 END) as winCount
FROM current_bet
GROUP BY betType;
```
### 5.3 최근 게임 분석
```sql
SELECT
gameId,
diceNum,
COUNT(*) as betCount,
SUM(amount) as totalBet,
SUM(reward) as totalReward
FROM current_bet
WHERE gameId = (SELECT gameId FROM current_bet ORDER BY id DESC LIMIT 1)
GROUP BY gameId, diceNum;
```
---
## 6. 주의사항
### 보안
- **프로덕션 환경**: `/sql/query` 엔드포인트는 개발용입니다. 프로덕션에서는 제거하거나 인증을 추가하세요.
- **SQL Injection**: 사용자 입력을 직접 쿼리에 넣지 마세요. 항상 파라미터 바인딩을 사용하세요.
### 성능
- **인덱스 활용**: `gameId``userId`에 인덱스가 설정되어 있습니다.
- **LIMIT 사용**: 큰 테이블 조회 시 항상 `LIMIT`를 사용하세요.
### 비용
- Cloudflare Durable Objects의 SQLite 사용 시 [요금 정책](https://developers.cloudflare.com/durable-objects/platform/pricing/#sqlite-storage-backend)을 확인하세요.
- Row read/write에 따라 비용이 발생합니다.
---
## 7. 문제 해결
### "COUNTER binding not found" 에러
- `wrangler.jsonc`에 Durable Object 바인딩이 올바르게 설정되어 있는지 확인하세요.
### 빈 결과가 반환됨
- 아직 사용자나 배팅이 생성되지 않았을 수 있습니다.
- 게임에 접속하여 배팅을 해보세요.
### WebSocket 연결 실패
- 개발 서버가 실행 중인지 확인하세요: `pnpm run dev`
---
## 8. 실전 예제
### 게임 시작 시 사용자 생성
```javascript
// counter-do.ts의 fetch 메서드에서
this.createOrUpdateUser(
session.id,
'Player1',
'player1@example.com',
10000
);
```
### 배팅 저장
```javascript
// startNoMoreBetPeriod 메서드에서 주사위를 굴린 후
const diceSum = this.dice1! + this.dice2! + this.dice3!;
this.sessions.forEach((session) => {
if (session.oddBet > 0) {
this.saveBet({
gameId: this.gameId!,
diceNum: diceSum,
userId: session.id,
betType: 'odd',
amount: session.oddBet,
isWin: diceSum % 2 === 1 ? 1 : 0,
reward: diceSum % 2 === 1 ? session.oddBet * 2 : 0
});
}
});
```
---
완성! 🎉
이제 Durable Object의 SQLite 데이터베이스를 다양한 방법으로 조회할 수 있습니다.