2026-03-18 11:36:31 +09:00
|
|
|
# 이용현황 + 구독관리 통합 개선 계획
|
|
|
|
|
|
|
|
|
|
> **작성일**: 2026-03-18
|
|
|
|
|
> **상태**: 계획 수립
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 1. 개요
|
|
|
|
|
|
|
|
|
|
### 1.1 목적
|
|
|
|
|
|
|
|
|
|
React 서비스의 **이용현황**(`/usage`)과 **구독관리**(`/subscription`) 메뉴를 하나로 통합하고, 현재 mock/하드코딩 데이터를 실제 DB 기반으로 개선한다.
|
|
|
|
|
|
|
|
|
|
### 1.2 배경
|
|
|
|
|
|
|
|
|
|
- `/usage` 페이지는 EmptyPage(미구현)
|
|
|
|
|
- `/subscription` 페이지는 구현되어 있으나, 일부 데이터가 하드코딩(API 호출 상한 10,000)
|
|
|
|
|
- 사용자가 원하는 핵심 정보: **AI 토큰 사용량** + **서버 저장공간**
|
|
|
|
|
- 메뉴가 분산되어 있어 하나로 통합
|
|
|
|
|
|
|
|
|
|
### 1.3 핵심 원칙
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
✅ 하나의 메뉴(이용현황)에 구독 + 사용량 통합 표시
|
|
|
|
|
✅ 모든 데이터는 실제 DB에서 조회
|
|
|
|
|
✅ AI 토큰 사용량은 ai_token_usages 테이블 기반
|
|
|
|
|
✅ 저장공간은 tenant.storage_used/limit 기반
|
|
|
|
|
❌ 하드코딩된 상한값 제거
|
|
|
|
|
❌ 별도 구독관리 메뉴 유지 (통합)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 2. 현재 상태 분석
|
|
|
|
|
|
|
|
|
|
### 2.1 기존 데이터 흐름
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
React (/subscription)
|
|
|
|
|
↓
|
|
|
|
|
GET /api/v1/subscriptions/current → subscriptions 테이블 (실제 데이터)
|
|
|
|
|
GET /api/v1/subscriptions/usage → 혼합 (실제 + 하드코딩)
|
|
|
|
|
↓
|
|
|
|
|
SubscriptionManagement 컴포넌트 렌더링
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2.2 현재 usage API 반환 항목
|
|
|
|
|
|
|
|
|
|
| 항목 | 데이터 소스 | 실제/Mock | 문제점 |
|
|
|
|
|
|------|-----------|:---------:|--------|
|
|
|
|
|
| 사용자 수 | `tenant.users.count()` / `tenant.max_users` | 실제 | `max_users` 미설정 시 0 |
|
|
|
|
|
| 저장공간 | `tenant.storage_used` / `tenant.storage_limit` | 실제 | 파일 업로드 시 자동 갱신됨 |
|
|
|
|
|
| API 호출 수 | `ApiRequestLog` count (일간) / 10,000 | **반만 실제** | 상한 10,000 하드코딩, 1일만 보관 |
|
|
|
|
|
| 구독 정보 | `subscriptions` 테이블 | 실제 | plan 연결 구조만 존재 |
|
|
|
|
|
|
|
|
|
|
### 2.3 AI 토큰 추적 현황
|
|
|
|
|
|
|
|
|
|
| 항목 | 상태 | 위치 |
|
|
|
|
|
|------|------|------|
|
|
|
|
|
| `ai_token_usages` 테이블 | ✅ 존재 | API 마이그레이션 |
|
|
|
|
|
| AI 호출 시 기록 | ✅ 동작 중 | MNG에서 AI 기능 사용 시 자동 기록 |
|
|
|
|
|
| 토큰별 비용 계산 | ✅ 동작 중 | `ai_pricing_configs` 테이블 기반 |
|
|
|
|
|
| **서비스(React) API 제공** | ❌ 없음 | API 프로젝트에 엔드포인트 미구현 |
|
|
|
|
|
|
|
|
|
|
### 2.4 기존 테이블 구조
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
tenants
|
|
|
|
|
├── storage_used (bigint, bytes) ← 실제 추적 중
|
|
|
|
|
├── storage_limit (bigint, bytes) ← 기본 10GB
|
|
|
|
|
├── max_users (int, nullable)
|
|
|
|
|
│
|
|
|
|
|
├── subscriptions (1:N)
|
|
|
|
|
│ ├── plan (varchar 50, 'Business' 등)
|
|
|
|
|
│ ├── monthly_fee (bigint)
|
|
|
|
|
│ ├── start_date / next_billing
|
|
|
|
|
│ └── status (active/cancelled/...)
|
|
|
|
|
│
|
|
|
|
|
└── ai_token_usages (1:N)
|
|
|
|
|
├── model (varchar, 'gemini-2.0-flash' 등)
|
|
|
|
|
├── menu_name (varchar, 기능명)
|
|
|
|
|
├── prompt_tokens / completion_tokens / total_tokens
|
|
|
|
|
├── cost_usd / cost_krw
|
|
|
|
|
└── created_at
|
|
|
|
|
|
|
|
|
|
ai_pricing_configs
|
|
|
|
|
├── provider (gemini/claude/google-stt/google-gcs)
|
|
|
|
|
├── model_name
|
|
|
|
|
├── input_price_per_million / output_price_per_million
|
|
|
|
|
└── exchange_rate
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 3. 목표 설계
|
|
|
|
|
|
2026-03-18 12:13:31 +09:00
|
|
|
### 3.1 과금 정책 기준 (customer-pricing.md 6장)
|
|
|
|
|
|
|
|
|
|
> **출처**: `rules/customer-pricing.md` — 사용량 기반 추가 과금
|
|
|
|
|
|
|
|
|
|
| 항목 | 기본 제공 | 초과 과금 | 비고 |
|
|
|
|
|
|------|----------|----------|------|
|
|
|
|
|
| 파일 저장 공간 | **100GB** | 100GB당 **5만원/월** | `tenant.storage_limit` 기본값 반영 |
|
|
|
|
|
| AI 토큰 | **월 100만 토큰** | 1,000토큰 단위 **실비 과금** | 매월 1일 리셋, 이월 없음 |
|
|
|
|
|
|
|
|
|
|
**알림 정책**:
|
|
|
|
|
- 기본 제공량의 **80%** 소진 시 → 경고 알림
|
|
|
|
|
- 기본 제공량의 **100%** 소진 시 → 한도 초과 알림
|
|
|
|
|
- 미사용 잔여 토큰은 이월되지 않음 (매월 1일 갱신)
|
|
|
|
|
|
|
|
|
|
### 3.2 통합 페이지 구성
|
2026-03-18 11:36:31 +09:00
|
|
|
|
|
|
|
|
```
|
|
|
|
|
이용현황 (/usage)
|
|
|
|
|
┌─────────────────────────────────────────────────┐
|
|
|
|
|
│ │
|
|
|
|
|
│ [1] 구독 정보 카드 │
|
|
|
|
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
|
|
|
│ │ 요금제 │ │ 구독상태 │ │ 다음결제 │ │
|
|
|
|
|
│ │ Business │ │ 활성 │ │ 04-01 │ │
|
|
|
|
|
│ └──────────┘ └──────────┘ └──────────┘ │
|
|
|
|
|
│ │
|
|
|
|
|
│ [2] 리소스 사용량 │
|
|
|
|
|
│ ┌──────────────────────────────────────┐ │
|
|
|
|
|
│ │ 사용자 ████████░░ 5 / 10명 │ │
|
2026-03-18 12:13:31 +09:00
|
|
|
│ │ 저장공간 ███░░░░░░░ 1.2GB / 100GB │ │
|
2026-03-18 11:36:31 +09:00
|
|
|
│ └──────────────────────────────────────┘ │
|
|
|
|
|
│ │
|
2026-03-18 12:13:31 +09:00
|
|
|
│ [3] AI 토큰 사용량 (2026년 3월) │
|
2026-03-18 11:36:31 +09:00
|
|
|
│ ┌──────────────────────────────────────┐ │
|
2026-03-18 12:13:31 +09:00
|
|
|
│ │ ██████░░░░ 620,000 / 1,000,000 │ │
|
|
|
|
|
│ │ 총 비용 ₩1,234 │ │
|
2026-03-18 11:36:31 +09:00
|
|
|
│ │ 모델별: Gemini 80% | Claude 20% │ │
|
2026-03-18 12:13:31 +09:00
|
|
|
│ │ ⚠️ 80% 소진 시 경고, 100% 초과 시 실비│ │
|
|
|
|
|
│ │ [상세 내역 보기 →] │ │
|
2026-03-18 11:36:31 +09:00
|
|
|
│ └──────────────────────────────────────┘ │
|
|
|
|
|
│ │
|
|
|
|
|
│ [4] 서비스 관리 버튼 │
|
|
|
|
|
│ [ 자료 내보내기 ] [ 서비스 해지 ] │
|
|
|
|
|
│ │
|
|
|
|
|
└─────────────────────────────────────────────────┘
|
|
|
|
|
```
|
|
|
|
|
|
2026-03-18 12:13:31 +09:00
|
|
|
### 3.3 통합 API 설계
|
2026-03-18 11:36:31 +09:00
|
|
|
|
|
|
|
|
기존 `/subscriptions/usage` API를 확장하여 AI 토큰 정보를 포함한다.
|
|
|
|
|
|
|
|
|
|
#### `GET /api/v1/subscriptions/usage` (개선)
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"users": {
|
|
|
|
|
"used": 5,
|
|
|
|
|
"limit": 10,
|
|
|
|
|
"percentage": 50.0
|
|
|
|
|
},
|
|
|
|
|
"storage": {
|
|
|
|
|
"used": 1288490189,
|
|
|
|
|
"used_formatted": "1.2 GB",
|
2026-03-18 12:13:31 +09:00
|
|
|
"limit": 107374182400,
|
|
|
|
|
"limit_formatted": "100 GB",
|
|
|
|
|
"percentage": 1.2
|
2026-03-18 11:36:31 +09:00
|
|
|
},
|
|
|
|
|
"ai_tokens": {
|
|
|
|
|
"period": "2026-03",
|
|
|
|
|
"total_requests": 156,
|
2026-03-18 12:13:31 +09:00
|
|
|
"total_tokens": 620000,
|
|
|
|
|
"prompt_tokens": 412000,
|
|
|
|
|
"completion_tokens": 208000,
|
|
|
|
|
"limit": 1000000,
|
|
|
|
|
"percentage": 62.0,
|
2026-03-18 11:36:31 +09:00
|
|
|
"cost_usd": 0.95,
|
|
|
|
|
"cost_krw": 1234,
|
2026-03-18 12:13:31 +09:00
|
|
|
"warning_threshold": 80,
|
|
|
|
|
"is_over_limit": false,
|
2026-03-18 11:36:31 +09:00
|
|
|
"by_model": [
|
|
|
|
|
{
|
|
|
|
|
"model": "gemini-2.0-flash",
|
|
|
|
|
"requests": 120,
|
2026-03-18 12:13:31 +09:00
|
|
|
"total_tokens": 496000,
|
2026-03-18 11:36:31 +09:00
|
|
|
"cost_krw": 987
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"model": "claude-3-haiku",
|
|
|
|
|
"requests": 36,
|
2026-03-18 12:13:31 +09:00
|
|
|
"total_tokens": 124000,
|
2026-03-18 11:36:31 +09:00
|
|
|
"cost_krw": 247
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"subscription": {
|
|
|
|
|
"plan": "Business",
|
|
|
|
|
"monthly_fee": 50000,
|
|
|
|
|
"status": "active",
|
|
|
|
|
"start_date": "2026-01-01",
|
|
|
|
|
"next_billing": "2026-04-01",
|
|
|
|
|
"remaining_days": 14
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**변경점**:
|
|
|
|
|
- `api_calls` 섹션 → `ai_tokens` 섹션으로 교체
|
2026-03-18 12:13:31 +09:00
|
|
|
- `ai_tokens.limit`: 월 100만 토큰 (정책 기반)
|
|
|
|
|
- `ai_tokens.percentage`: 한도 대비 사용율
|
|
|
|
|
- `ai_tokens.warning_threshold`: 경고 기준 80%
|
|
|
|
|
- `ai_tokens.is_over_limit`: 한도 초과 여부
|
|
|
|
|
- `ai_tokens.cost_krw`: 초과 시 실비 과금 금액 계산에 활용
|
|
|
|
|
- `storage.limit`: 100GB (정책 기본값)
|
|
|
|
|
- `subscription.monthly_fee` 추가
|
2026-03-18 11:36:31 +09:00
|
|
|
- 하드코딩 제거 (10,000 상한 등)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 4. 작업 범위
|
|
|
|
|
|
|
|
|
|
### 4.1 API 프로젝트 (`sam/api`)
|
|
|
|
|
|
|
|
|
|
| 작업 | 파일 | 설명 |
|
|
|
|
|
|------|------|------|
|
|
|
|
|
| R1 | `SubscriptionService.php` | `usage()` 메서드에 AI 토큰 집계 추가 |
|
|
|
|
|
| R2 | `SubscriptionService.php` | `api_calls` 섹션을 `ai_tokens`로 교체 |
|
|
|
|
|
| R3 | `SubscriptionService.php` | `subscription` 섹션에 `monthly_fee` 추가 |
|
|
|
|
|
| R4 | `SubscriptionController.php` | 변경 없음 (Service만 수정) |
|
|
|
|
|
|
|
|
|
|
#### R1 상세: AI 토큰 집계 쿼리
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
// SubscriptionService::usage() 내부
|
|
|
|
|
$currentMonth = now()->format('Y-m');
|
|
|
|
|
|
|
|
|
|
$aiUsage = AiTokenUsage::where('tenant_id', $tenantId)
|
|
|
|
|
->whereRaw("DATE_FORMAT(created_at, '%Y-%m') = ?", [$currentMonth])
|
|
|
|
|
->selectRaw("
|
|
|
|
|
COUNT(*) as total_requests,
|
|
|
|
|
COALESCE(SUM(prompt_tokens), 0) as prompt_tokens,
|
|
|
|
|
COALESCE(SUM(completion_tokens), 0) as completion_tokens,
|
|
|
|
|
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
|
|
|
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
|
|
|
COALESCE(SUM(cost_krw), 0) as cost_krw
|
|
|
|
|
")
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
$aiByModel = AiTokenUsage::where('tenant_id', $tenantId)
|
|
|
|
|
->whereRaw("DATE_FORMAT(created_at, '%Y-%m') = ?", [$currentMonth])
|
|
|
|
|
->selectRaw("
|
|
|
|
|
model,
|
|
|
|
|
COUNT(*) as requests,
|
|
|
|
|
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
|
|
|
COALESCE(SUM(cost_krw), 0) as cost_krw
|
|
|
|
|
")
|
|
|
|
|
->groupBy('model')
|
|
|
|
|
->orderByDesc('total_tokens')
|
|
|
|
|
->get();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4.2 React 프로젝트 (`sam/react`)
|
|
|
|
|
|
|
|
|
|
| 작업 | 파일 | 설명 |
|
|
|
|
|
|------|------|------|
|
|
|
|
|
| R5 | `types.ts` | `UsageApiData` 타입에 `ai_tokens` 추가 |
|
|
|
|
|
| R6 | `actions.ts` | 기존 `getUsage()` 유지 (API 응답 구조만 변경) |
|
|
|
|
|
| R7 | `SubscriptionManagement.tsx` | AI 토큰 섹션 UI 추가 |
|
|
|
|
|
| R8 | `utils.ts` | `transformApiToFrontend()` AI 토큰 변환 추가 |
|
|
|
|
|
| R9 | `/usage/page.tsx` 생성 | 새 페이지 (SubscriptionManagement 재사용) |
|
|
|
|
|
| R10 | `/subscription/page.tsx` | `/usage`로 리다이렉트 처리 |
|
|
|
|
|
|
|
|
|
|
### 4.3 메뉴 변경
|
|
|
|
|
|
|
|
|
|
| 작업 | 대상 | 설명 |
|
|
|
|
|
|------|------|------|
|
|
|
|
|
| R11 | `menus` 테이블 | 기존 `구독관리`(`/subscription`) 메뉴 → URL을 `/usage`로 변경하거나 비활성화 |
|
|
|
|
|
| R12 | `menus` 테이블 | `이용현황`(`/usage`) 메뉴 유지 |
|
|
|
|
|
|
|
|
|
|
> **결과**: 사이드바에 `이용현황` 하나만 표시, 클릭 시 통합 페이지
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 5. 제거 대상
|
|
|
|
|
|
|
|
|
|
| 항목 | 사유 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| `api_calls` 섹션 (usage API) | 일반 API 호출 횟수는 무의미 → AI 토큰으로 대체 |
|
|
|
|
|
| 하드코딩 `apiCallsLimit: 10000` | 실제 제한이 아닌 의미없는 수치 |
|
|
|
|
|
| `ApiRequestLog` 기반 사용량 | 1일만 보관, 통계 목적 부적합 |
|
|
|
|
|
| `/subscription` 별도 메뉴 | `/usage`로 통합 |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 6. 작업 순서
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
Phase 1: API 개선 (api)
|
|
|
|
|
└── R1~R3: usage() 메서드 수정 (AI 토큰 집계 추가)
|
|
|
|
|
|
|
|
|
|
Phase 2: React 페이지 통합 (react)
|
|
|
|
|
└── R5~R8: 타입/컴포넌트 수정 (AI 토큰 UI 추가)
|
|
|
|
|
└── R9~R10: /usage 페이지 생성, /subscription 리다이렉트
|
|
|
|
|
|
|
|
|
|
Phase 3: 메뉴 정리
|
|
|
|
|
└── R11~R12: 메뉴 통합 (tinker로 직접 수정)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
2026-03-18 12:13:31 +09:00
|
|
|
## 7. 정책 기반 확정 사항
|
|
|
|
|
|
|
|
|
|
`rules/customer-pricing.md` 6장에 따라 확정:
|
|
|
|
|
|
|
|
|
|
| # | 항목 | 확정 내용 |
|
|
|
|
|
|---|------|----------|
|
|
|
|
|
| D1 | AI 토큰 월별 한도 | **100만 토큰/월** (매월 1일 리셋, 이월 없음) |
|
|
|
|
|
| D2 | 초과 과금 | 1,000토큰 단위 **실비** (ai_pricing_configs 단가 기반) |
|
|
|
|
|
| D3 | 알림 기준 | 80% 경고, 100% 한도 초과 알림 |
|
|
|
|
|
| D4 | 저장공간 기본 한도 | **100GB** (초과 시 100GB당 5만원/월) |
|
|
|
|
|
| D5 | 비용 노출 | 토큰 비용(KRW) 고객에게 표시 (customer-pricing에 명시) |
|
2026-03-18 11:36:31 +09:00
|
|
|
|
2026-03-18 12:18:27 +09:00
|
|
|
### 7.1 확정 사항 (2026-03-18 추가)
|
|
|
|
|
|
|
|
|
|
| # | 항목 | 결정 |
|
|
|
|
|
|---|------|------|
|
|
|
|
|
| Q1 | AI 토큰 한도 관리 | `tenant.ai_token_limit` 컬럼 추가 (기본값 100만). 플랜별 차등 아닌 테넌트별 개별 기록 |
|
|
|
|
|
| Q4 | 저장공간 기본값 | `tenant.storage_limit` 기본값 10GB → **100GB**로 변경 (마이그레이션) |
|
|
|
|
|
|
|
|
|
|
### 7.2 추가 확인 필요 사항
|
2026-03-18 11:36:31 +09:00
|
|
|
|
|
|
|
|
| # | 질문 | 영향 |
|
|
|
|
|
|---|------|------|
|
2026-03-18 12:13:31 +09:00
|
|
|
| Q2 | 이용현황 페이지 접근 권한은? (관리자만 vs 모든 사용자) | 권한 설정 |
|
|
|
|
|
| Q3 | 저장공간의 실제 사용량이 정확히 갱신되고 있는가? | 파일 업로드/삭제 시 `incrementStorage`/`decrementStorage` 호출 여부 |
|
2026-03-18 11:36:31 +09:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 관련 문서
|
|
|
|
|
|
|
|
|
|
| 문서 | 경로 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| API 개발 규칙 | `dev/standards/api-rules.md` |
|
|
|
|
|
| options 정책 | `standards/options-column-policy.md` |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
**최종 업데이트**: 2026-03-18
|