dramaling-vocab-learning/note/usage-limit-implementation-...

17 KiB
Raw Blame History

免費用戶使用限制功能實現報告

項目: DramaLing 英語學習平台 功能模組: 免費用戶使用額度管理系統 檢查日期: 2025-01-18 檢查者: Claude Code

📋 功能概述

免費用戶使用限制功能是一個雙層限制系統,通過前端本地計數和後端資料庫驗證,確保免費用戶在 3 小時內最多只能進行 5 次句子分析,防止濫用 AI 資源。

🔧 當前實現架構

📊 限制參數

// 前端常數 (generate/page.tsx)
const FREE_USER_LIMIT = 5           // 最大分析次數
const TIME_WINDOW = "3小時"          // 時間窗口
const isPremium = false             // 用戶類型

// 後端常數 (UsageTrackingService.cs)
const FREE_USER_ANALYSIS_LIMIT = 5  // 最大分析次數
const FREE_USER_RESET_HOURS = 3     // 3小時重置窗口

🔄 雙層限制實現機制

1 前端限制層 (第一道防線)

檔案位置: frontend/app/generate/page.tsx:42-46

// 前端本地檢查
if (!isPremium && usageCount >= 5) {
  console.log('❌ 使用次數超限')
  alert('❌ 免費用戶 3 小時內只能分析 5 次句子,請稍後再試或升級到付費版本')
  return
}

實現方式:

  • 本地狀態: const [usageCount, setUsageCount] = useState(0)
  • 即時檢查: 每次點擊分析按鈕前驗證
  • 用戶回饋: 立即顯示限制提示無需等待API
  • 計數更新: 成功分析後 setUsageCount(prev => prev + 1)

特點:

  • 🚀 即時回應: 無需API調用立即提示
  • 🎯 用戶友善: 清楚說明限制原因和解決方案
  • 💰 引導付費: 提示升級到付費版本

2 後端驗證層 (權威檢查)

檔案位置: backend/DramaLing.Api/Controllers/AIController.cs:515-531

// 後端權威驗證
var mockUserId = Guid.Parse("00000000-0000-0000-0000-000000000001");
var canUse = await _usageService.CheckUsageLimitAsync(mockUserId, isPremium: false);
if (!canUse)
{
    return StatusCode(429, new
    {
        Success = false,
        Error = "免費用戶使用限制已達上限",
        ErrorCode = "USAGE_LIMIT_EXCEEDED",
        ResetInfo = new
        {
            WindowHours = 3,
            Limit = 5
        }
    });
}

實現方式:

  • 資料庫查詢: 基於 WordQueryUsageStats
  • 時間窗口: 查詢過去3小時的使用記錄
  • 精確計算: SentenceAnalysisCount + LowValueWordClicks
  • 錯誤代碼: 結構化錯誤回應

🗄️ 資料庫實現詳情

使用統計表結構

表名: WordQueryUsageStats

-- 使用統計記錄表
CREATE TABLE WordQueryUsageStats (
    Id UNIQUEIDENTIFIER PRIMARY KEY,
    UserId UNIQUEIDENTIFIER NOT NULL,           -- 用戶ID
    SentenceAnalysisCount INT NOT NULL,         -- 句子分析次數
    HighValueWordClicks INT NOT NULL,           -- 高價值詞彙點擊次數
    LowValueWordClicks INT NOT NULL,            -- 低價值詞彙點擊次數(收費)
    CreatedAt DATETIME2 NOT NULL,               -- 記錄建立時間
    UpdatedAt DATETIME2 NOT NULL                -- 最後更新時間
);

使用統計查詢邏輯

檔案位置: backend/DramaLing.Api/Services/UsageTrackingService.cs:42-47

// 計算過去3小時的使用量
var resetTime = DateTime.UtcNow.AddHours(-FREE_USER_RESET_HOURS);
var recentUsage = await _context.WordQueryUsageStats
    .Where(stats => stats.UserId == userId && stats.CreatedAt >= resetTime)
    .SumAsync(stats => stats.SentenceAnalysisCount + stats.LowValueWordClicks);

var canUse = recentUsage < FREE_USER_ANALYSIS_LIMIT;

特點:

  • 🕐 滑動窗口: 過去3小時內的累計使用量
  • 📊 綜合計算: 句子分析 + 付費詞彙查詢
  • 🔒 權威性: 無法被前端繞過
  • 📈 可擴展: 支援不同用戶類型的限制

🎨 用戶介面實現

UI顯示邏輯

檔案位置: frontend/app/generate/page.tsx:295-312

// 使用次數顯示
<div className="text-center text-sm text-gray-600">
  {isPremium ? (
    <span className="text-green-600">🌟 付費用戶:無限制使用</span>
  ) : (
    <span className={usageCount >= 4 ? 'text-red-600' :
                    usageCount >= 3 ? 'text-yellow-600' : 'text-gray-600'}>
      免費用戶:已使用 {usageCount}/5  (3小時內)
      {usageCount >= 5 && <span className="block text-red-500 mt-1">已達上限,請稍後再試</span>}
    </span>
  )}
</div>

視覺回饋系統

使用次數 顏色 狀態 用戶體驗
0-2次 text-gray-600 正常 無特殊提示
3次 text-yellow-600 警告 黃色提醒剩餘次數
4次 text-red-600 危險 紅色警告即將達限
5次 text-red-500 限制 顯示"已達上限"訊息

按鈕狀態管理

// 分析按鈕禁用邏輯
disabled = isAnalyzing ||                                    // 正在分析中
          (mode === 'manual' && (!textInput || textInput.length > 300)) ||  // 輸入驗證
          (mode === 'screenshot')                            // 截圖模式未開放

注意: 前端按鈕禁用主要基於輸入驗證,使用限制檢查在函數內部進行。

🔍 實現流程分析

句子分析流程

用戶點擊「🔍 分析句子」
         ↓
┌─── 前端檢查 ─────────────────┐
│ 1. 檢查 isPremium 狀態        │
│ 2. 檢查 usageCount >= 5      │
│ 3. 超限則顯示錯誤並 return    │
└─────────────────────────────┘
         ↓ (通過)
┌─── 發送 API 請求 ────────────┐
│ POST /api/ai/analyze-sentence │
└─────────────────────────────┘
         ↓
┌─── 後端驗證 ─────────────────┐
│ 1. 檢查資料庫使用記錄         │
│ 2. 計算過去3小時累計使用量    │
│ 3. 超限則返回 429 錯誤       │
└─────────────────────────────┘
         ↓ (通過)
┌─── 執行分析 ─────────────────┐
│ 1. 調用 Gemini AI           │
│ 2. 處理分析結果              │
│ 3. 存入快取                 │
│ 4. 更新使用統計              │
└─────────────────────────────┘
         ↓
┌─── 前端更新 ─────────────────┐
│ 1. 顯示分析結果              │
│ 2. usageCount + 1           │
│ 3. 更新UI狀態顯示            │
└─────────────────────────────┘

🚨 問題分析:前後端不同步

⚠️ 發現的設計問題

1. 計數邏輯不一致

前端計數:

// 簡單累加,不考慮時間窗口
setUsageCount(prev => prev + 1)

後端計數:

// 基於3小時滑動窗口的資料庫查詢
var recentUsage = await _context.WordQueryUsageStats
    .Where(stats => stats.UserId == userId && stats.CreatedAt >= resetTime)
    .SumAsync(stats => stats.SentenceAnalysisCount + stats.LowValueWordClicks);

2. 時間重置機制

層級 重置機制 問題
前端 無重置 頁面刷新會重置為0但實際限制未重置
後端 3小時滑動窗口 正確實現,但前端不知道何時重置

3. 快取命中對計數的影響

當前行為:

  • 新句子: 消耗1次額度 (正確)
  • 快取命中: 也消耗1次額度 (可能不合理)

問題: 快取命中不應該消耗額度因為沒有調用AI API。

🔧 當前實現的優缺點

優點

  1. 雙重保護: 前端 + 後端雙重驗證
  2. 即時回饋: 前端檢查提供即時用戶體驗
  3. 安全性: 後端驗證防止繞過
  4. 視覺提示: 分級顏色警告系統
  5. 付費引導: 清楚的升級提示

問題

  1. 不同步: 前後端計數邏輯不一致
  2. 無時間重置: 前端不知道何時重置額度
  3. 快取誤計: 快取命中也消耗額度
  4. 頁面重置: 刷新頁面會重置前端計數器
  5. 無持久化: 前端計數器無法跨頁面保持

💡 建議改善方案

🎯 短期修復

1. 修復快取計數問題

// 修改前端:快取命中不增加計數
if (result.success) {
  // ... 其他處理

  // 只有非快取結果才增加計數
  if (!result.cached) {
    setUsageCount(prev => prev + 1)
  }
}

2. 前端計數同步

// 添加從後端獲取實際使用量的功能
const fetchUsageStats = async () => {
  const response = await fetch('/api/ai/usage-stats')
  const stats = await response.json()
  setUsageCount(stats.data.recentUsage)
}

// 在組件初始化時同步
useEffect(() => {
  fetchUsageStats()
}, [])

3. 時間重置提示

// 添加重置時間顯示
const nextResetTime = new Date(Date.now() + (3 * 60 * 60 * 1000))
<span className="text-xs text-gray-500 block">
  額度將於 {nextResetTime.toLocaleTimeString()} 重置
</span>

🚀 中期改善

1. 統一計數系統

  • 移除前端計數器
  • 完全依賴後端API提供的使用統計
  • 每次操作後同步最新狀態

2. 智能快取策略

  • 快取命中不消耗額度
  • 高價值詞彙查詢永遠免費
  • 只有實際AI調用才計費

3. 增強的用戶體驗

  • 實時剩餘額度顯示
  • 重置時間倒計時
  • 使用歷史記錄

📊 當前實現狀態評估

運作正常的部分

  1. 基本限制機制: 確實能防止超量使用
  2. 視覺回饋系統: 用戶能清楚看到使用狀態
  3. 後端安全驗證: 無法繞過的伺服器端檢查
  4. 付費用戶支援: 正確識別並給予無限制使用

⚠️ 存在問題的部分

  1. 前後端不同步:

    • 前端: 簡單累加計數器
    • 後端: 3小時滑動窗口計算
  2. 快取計數邏輯:

    • 快取命中仍消耗前端計數器
    • 實際上沒有消耗AI資源
  3. 頁面狀態持久性:

    • 頁面刷新會重置前端計數器
    • 用戶可能誤以為額度重置

🔍 技術債務

  1. 資料一致性: 需要統一前後端計數邏輯
  2. 狀態管理: 需要持久化前端狀態
  3. 用戶體驗: 需要更準確的額度資訊

🧪 測試用例分析

目前的實現問題測試

測試案例 1: 快取計數問題

步驟:
1. 分析句子A (usageCount = 1)
2. 返回並重新分析句子A (快取命中)
預期: usageCount 應該保持 1
實際: usageCount 變成 2 ❌

問題: 快取命中不應該消耗額度

測試案例 2: 頁面刷新問題

步驟:
1. 分析5次句子 (usageCount = 5)
2. 刷新頁面
預期: 仍然顯示限制狀態
實際: usageCount 重置為 0可以繼續使用 ❌

問題: 前端狀態沒有持久化

測試案例 3: 後端驗證

步驟:
1. 繞過前端檢查直接調用API
2. 在3小時內調用超過5次
預期: 後端應該返回 429 錯誤
實際: 需要驗證 ⚠️

狀態: 邏輯存在,但需要實際測試

📈 效能影響分析

前端效能

  • 狀態管理: 輕量級 (useState)
  • 檢查成本: O(1) 常數時間
  • 記憶體使用: 微量 (單一整數值)

後端效能

  • 資料庫查詢: 每次分析需要查詢使用統計
  • 索引需求: UserId + CreatedAt 複合索引
  • 查詢複雜度: 簡單時間範圍查詢

網路效能

  • 額外API調用: 可能需要獨立的使用統計API
  • 回應大小: 增加使用統計資訊

🎯 功能完整性評估

已實現功能

  1. 基本限制: 5次/3小時限制正確執行
  2. 付費區分: 付費用戶無限制使用
  3. 視覺提示: 清楚的使用狀態顯示
  4. 錯誤處理: 適當的錯誤訊息和引導

缺失功能

  1. 狀態同步: 前後端計數器不一致
  2. 時間重置: 用戶不知道何時重置
  3. 快取優化: 快取命中仍計費
  4. 歷史記錄: 無使用歷史追蹤
  5. 統計面板: 無詳細使用統計展示

⚠️ 部分功能

  1. 後端驗證: 邏輯存在但需要實際測試
  2. 錯誤處理: 基本實現,可更完善
  3. 用戶體驗: 功能性足夠,體驗可優化

🚀 改善優先級建議

高優先級 (立即修復)

  1. 修復快取計數: 快取命中不消耗額度
  2. 前端狀態同步: 從後端獲取實際使用量
  3. 頁面刷新處理: 持久化或重新獲取狀態

中優先級 (1-2週內)

  1. 時間重置提示: 顯示下次重置時間
  2. 使用統計API: 獨立的使用統計端點
  3. 增強錯誤處理: 更友善的錯誤訊息

低優先級 (未來功能)

  1. 使用歷史記錄: 詳細的使用歷史
  2. 彈性限制: 基於用戶行為的動態限制
  3. 統計儀表板: 管理員使用統計面板

🏁 結論

當前狀態: ⚠️ 基本可用,存在改善空間

功能性: 基本限制機制運作正常 用戶體驗: ⚠️ 可用但有混亂點 (快取計數、頁面重置) 技術實現: ⚠️ 雙層保護好,但同步性有問題 商業價值: 有效防止濫用,引導付費

關鍵改善點

  1. 統一計數邏輯: 前後端使用相同的計算方式
  2. 快取計數修復: 快取命中不應消耗額度
  3. 狀態持久化: 解決頁面刷新重置問題
  4. 時間透明度: 讓用戶知道重置時間

建議實施

第一階段: 修復快取計數和狀態同步問題 第二階段: 增加時間重置提示和統計API 第三階段: 完整的使用歷史和管理功能


🔄 系統暫時關閉記錄

執行時間: 2025-01-18 15:54 執行者: Claude Code 狀態: 使用限制系統已暫時關閉

📝 修改記錄

1. 前端修改

檔案: frontend/app/generate/page.tsx 位置: 第24行 修改內容:

// 修改前
const [isPremium] = useState(false)

// 修改後
const [isPremium] = useState(true)

2. 後端修改

檔案: backend/DramaLing.Api/Controllers/AIController.cs 位置: 第517行 修改內容:

// 修改前
var canUse = await _usageService.CheckUsageLimitAsync(mockUserId, isPremium: false);

// 修改後
var canUse = await _usageService.CheckUsageLimitAsync(mockUserId, isPremium: true);

3. 編譯問題修復

檔案: backend/DramaLing.Api/Controllers/AIController.cs 位置: 第7行 (using statements) 修改內容:

// 新增
using System.Text.Json;

🧪 測試結果

多次調用測試通過:

  • 第1次調用: 成功
  • 第6次調用: 成功 (原本第6次會被限制)
  • API無限制調用確認成功

UI顯示效果:

🌟 付費用戶:無限制使用

🔄 復原使用限制的步驟

當需要重新啟用使用限制時,請執行以下步驟:

步驟1: 復原前端設定

# 編輯檔案: frontend/app/generate/page.tsx
# 第24行改回:
const [isPremium] = useState(false)

步驟2: 復原後端設定

# 編輯檔案: backend/DramaLing.Api/Controllers/AIController.cs
# 第517行改回:
var canUse = await _usageService.CheckUsageLimitAsync(mockUserId, isPremium: false);

步驟3: 重新啟動服務

# 重新啟動後端
cd backend/DramaLing.Api
dotnet run --urls http://localhost:5000

# 前端會自動重新編譯

步驟4: 驗證復原

復原後應該看到:

免費用戶:已使用 0/5 次 (3小時內)

⚠️ 重要提醒

  1. 資料庫統計: 後端的使用統計仍在記錄,復原限制後會基於實際使用記錄計算
  2. 快取清理: 如需要完全重置統計,可考慮清理 WordQueryUsageStats
  3. 前端狀態: 前端計數器會在頁面刷新後重置,但後端限制基於資料庫記錄

報告生成時間: 2025-01-18 最後更新時間: 2025-01-18 15:54 分析範圍: 前端 + 後端 + 資料庫 功能狀態: 🔄 暫時關閉,隨時可復原