352 lines
8.2 KiB
Markdown
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 데이터베이스를 다양한 방법으로 조회할 수 있습니다.
|
|
|