Skip to content

Waynting/Spotify_Statistic

Repository files navigation

🎵 Spotify Crate - 開源音樂數據分析應用

一個完整的 Spotify 音樂聆聽分析工具,支援數據同步和歷史記錄追蹤。

✨ 功能特色

  • 📊 音樂數據分析:專輯、單曲、藝人、曲風、時段分析
  • 📸 數據快照:一鍵生成分享圖片
  • 🎯 時間篩選:7天/30天/90天/180天/365天 多維度分析
  • 💾 歷史記錄:自動同步播放記錄到資料庫
  • 🌓 純黑設計:OLED 友好的純黑背景
  • 📱 響應式:完美支援桌面和移動設備

🏗️ 系統架構說明

本專案採用 Next.js 14 (App Router) 架構,結合 PostgreSQL 資料庫來儲存歷史播放記錄。以下是系統如何運作的詳細說明。

📐 整體架構圖

┌─────────────────────────────────────────────────────────────┐
│                        前端層 (Client)                        │
├─────────────────────────────────────────────────────────────┤
│  React Components (Analytics, Albums, etc.)                  │
│  ├── Zustand Store (認證狀態、播放器狀態)                      │
│  ├── TanStack Query (數據獲取與快取)                          │
│  └── Spotify Web API Client (前端 API 封裝)                  │
└─────────────────────────────────────────────────────────────┘
                            ↕ HTTP/API
┌─────────────────────────────────────────────────────────────┐
│                    Next.js API Routes                         │
├─────────────────────────────────────────────────────────────┤
│  /api/auth/spotify    - 用戶認證與 token 儲存                 │
│  /api/sync            - 手動觸發數據同步                      │
│  /api/cron/sync       - 定時任務觸發同步                      │
│  /api/analytics/*     - 分析數據 API                          │
│  /api/users/me        - 獲取當前用戶資訊                      │
└─────────────────────────────────────────────────────────────┘
                            ↕
┌─────────────────────────────────────────────────────────────┐
│                      服務層 (Server)                           │
├─────────────────────────────────────────────────────────────┤
│  Sync Service        - 從 Spotify API 同步播放記錄           │
│  Data Service        - 合併即時與歷史數據                     │
│  Database Models     - 資料庫操作封裝                         │
└─────────────────────────────────────────────────────────────┘
                            ↕
┌─────────────────────────────────────────────────────────────┐
│                    PostgreSQL 資料庫                          │
├─────────────────────────────────────────────────────────────┤
│  users              - 用戶資訊與 OAuth tokens                 │
│  play_history       - 歷史播放記錄                            │
│  daily_stats        - 每日統計摘要                            │
└─────────────────────────────────────────────────────────────┘
                            ↕
┌─────────────────────────────────────────────────────────────┐
│                    Spotify Web API                            │
└─────────────────────────────────────────────────────────────┘

🔄 核心流程說明

1. 用戶認證流程 (OAuth 2.0 with PKCE)

用戶點擊登入
    ↓
生成 PKCE code_verifier 和 code_challenge
    ↓
重定向到 Spotify 授權頁面
    ↓
用戶授權後,Spotify 重定向回 /callback
    ↓
用 code 和 code_verifier 交換 access_token 和 refresh_token
    ↓
將 tokens 儲存到:
  - localStorage (前端,用於即時 API 調用)
  - PostgreSQL (後端,用於定時同步)
    ↓
認證完成,進入應用

關鍵檔案:

  • app/lib/spotify-web-api.ts - 前端 Spotify API 封裝,處理 PKCE 流程
  • app/components/SpotifyCallback.tsx - 處理 OAuth callback
  • app/api/auth/spotify/route.ts - 後端 API,儲存用戶 tokens 到資料庫

2. 數據同步流程

系統採用雙重數據來源策略:

A. 即時數據 (Real-time Data)

  • 來源:Spotify Web API (/v1/me/player/recently-played)
  • 獲取時機:用戶瀏覽分析頁面時即時調用
  • 限制:Spotify API 最多返回約 50 筆最近播放記錄
  • 儲存位置:不儲存,僅用於即時顯示

B. 歷史數據 (Historical Data)

  • 來源:PostgreSQL 資料庫 (play_history 表)
  • 獲取時機:透過定時任務或手動同步
  • 同步機制:
    1. 首次登入:自動觸發同步
    2. 定時任務:每天 UTC 00:00 自動同步所有用戶
    3. 手動同步:用戶可在設定頁面手動觸發

同步流程:

觸發同步 (/api/sync 或 cron job)
    ↓
從資料庫獲取用戶的 refresh_token
    ↓
使用 refresh_token 刷新 access_token
    ↓
調用 Spotify API 獲取最近播放記錄
    ↓
過濾已同步的記錄(基於 last_sync_at)
    ↓
將新記錄插入 play_history 表(自動去重)
    ↓
更新用戶的 last_sync_at

關鍵檔案:

  • app/server/services/sync-service.ts - 同步服務核心邏輯
  • app/api/sync/route.ts - 手動同步 API
  • app/api/cron/sync/route.ts - 定時任務 API
  • app/server/models/PlayHistory.ts - 播放記錄資料模型

3. 數據合併與分析流程

當用戶查看分析頁面時,系統會:

1. 獲取即時數據
   └─> spotifyWebAPI.getRecentlyPlayedMultiple()
       (從 Spotify API 獲取最近播放記錄)

2. 獲取歷史數據
   └─> backendAPI.getHistoricalTracks()
       (從 PostgreSQL 獲取指定時間範圍的播放記錄)

3. 數據合併 (DataMerger)
   └─> 合併兩組數據,基於 (trackId, playedAt) 去重
   └─> 過濾指定時間範圍內的記錄

4. 數據聚合
   ├─> aggregateByTrack()   - 按單曲聚合
   ├─> aggregateByAlbum()   - 按專輯聚合
   ├─> aggregateByArtist()  - 按藝人聚合
   └─> 時段分析              - 按播放時段分組

5. 返回分析結果
   └─> 顯示在 Analytics 組件中

關鍵檔案:

  • app/lib/data-service.ts - 數據服務,包含 DataMerger 類別
  • app/lib/backend-api.ts - 後端 API 客戶端
  • app/components/Analytics.tsx - 分析頁面主組件

📁 專案結構詳解

app/
├── api/                          # Next.js API Routes
│   ├── auth/
│   │   └── spotify/route.ts      # 處理 OAuth tokens 儲存
│   ├── sync/route.ts             # 手動觸發數據同步
│   ├── cron/sync/route.ts        # 定時任務觸發同步
│   ├── analytics/
│   │   ├── tracks/route.ts       # 單曲分析 API
│   │   └── artists/route.ts      # 藝人分析 API
│   └── users/me/route.ts         # 獲取當前用戶資訊
│
├── components/                   # React 組件
│   ├── Analytics.tsx             # 分析頁面主組件
│   ├── analytics/                # 分析相關子組件
│   │   ├── TracksAnalysis.tsx    # 單曲分析
│   │   ├── ArtistsAnalysis.tsx   # 藝人分析
│   │   ├── AlbumsAnalysis.tsx    # 專輯分析
│   │   ├── GenresAnalysis.tsx    # 曲風分析
│   │   └── TimeSegmentAnalysis.tsx # 時段分析
│   ├── Login.tsx                 # 登入頁面
│   ├── SpotifyCallback.tsx       # OAuth callback 處理
│   └── ...
│
├── lib/                          # 核心工具庫
│   ├── spotify-web-api.ts        # Spotify API 前端封裝
│   ├── data-service.ts           # 數據服務(合併、聚合邏輯)
│   ├── backend-api.ts            # 後端 API 客戶端
│   ├── cache-manager.ts          # 數據快取管理
│   └── config.ts                 # 配置檔案
│
├── server/                       # 後端服務層
│   ├── models/                   # 資料庫模型
│   │   ├── User.ts               # 用戶模型
│   │   ├── PlayHistory.ts        # 播放記錄模型
│   │   └── DailyStats.ts         # 每日統計模型
│   ├── services/
│   │   └── sync-service.ts       # 同步服務核心邏輯
│   ├── config/
│   │   └── database.ts           # 資料庫連接配置
│   └── migrations/               # 資料庫遷移腳本
│
├── store/                        # Zustand 狀態管理
│   ├── useAuthStore.ts           # 認證狀態
│   └── usePlayerStore.ts         # 播放器狀態
│
├── hooks/                        # React Hooks
│   ├── useAuth.ts                # 認證相關 Hook
│   └── useSpotifyPlayer.ts       # Spotify 播放器 Hook
│
└── types/                        # TypeScript 類型定義
    ├── index.ts                  # 通用類型
    └── spotify.ts                # Spotify API 類型

🗄️ 資料庫架構

表結構

1. users

儲存用戶資訊和 OAuth tokens。

CREATE TABLE users (
  spotify_user_id VARCHAR(255) PRIMARY KEY,
  email VARCHAR(255),
  display_name VARCHAR(255),
  access_token TEXT,              -- Spotify access token
  refresh_token TEXT,              -- Spotify refresh token
  token_expires_at TIMESTAMP,      -- Token 過期時間
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  last_sync_at TIMESTAMP           -- 最後同步時間
);

用途:

  • 儲存用戶的 Spotify OAuth tokens
  • 追蹤最後同步時間,避免重複同步
  • 支援 token 自動刷新

2. play_history

儲存每筆播放記錄。

CREATE TABLE play_history (
  id SERIAL PRIMARY KEY,
  user_id VARCHAR(255) NOT NULL,
  track_id VARCHAR(255) NOT NULL,
  track_name VARCHAR(500) NOT NULL,
  artist_id VARCHAR(255),
  artist_name VARCHAR(500),
  album_id VARCHAR(255),
  album_name VARCHAR(500),
  played_at TIMESTAMP NOT NULL,   -- 播放時間
  duration_ms INTEGER,             -- 歌曲長度(毫秒)
  popularity INTEGER,              -- 歌曲熱門度
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE(user_id, track_id, played_at)  -- 防止重複記錄
);

用途:

  • 儲存所有歷史播放記錄
  • 支援長時間範圍的分析(超過 Spotify API 限制)
  • 透過 UNIQUE 約束自動去重

3. daily_stats

儲存每日統計摘要(用於快速查詢)。

CREATE TABLE daily_stats (
  id SERIAL PRIMARY KEY,
  user_id VARCHAR(255) NOT NULL,
  stat_date DATE NOT NULL,
  total_plays INTEGER DEFAULT 0,
  total_minutes DECIMAL(10,2) DEFAULT 0,
  unique_tracks INTEGER DEFAULT 0,
  unique_artists INTEGER DEFAULT 0,
  unique_albums INTEGER DEFAULT 0,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE(user_id, stat_date)
);

用途:

  • 預先計算每日統計,提升查詢效能
  • 支援快速獲取總體統計數據

🔑 核心模組說明

1. Spotify Web API 封裝 (app/lib/spotify-web-api.ts)

職責:

  • 處理 OAuth 2.0 PKCE 流程
  • 管理 access_token 和 refresh_token
  • 封裝 Spotify API 調用(最近播放、Top Tracks/Artists 等)
  • 自動處理 token 刷新

主要方法:

class SpotifyWebAPI {
  // 認證相關
  authorize(): void                    // 發起 OAuth 授權
  handleCallback(code: string): Promise<void>  // 處理 callback
  refreshAccessToken(): Promise<void>  // 刷新 token
  
  // 數據獲取
  getRecentlyPlayedMultiple(limit: number): Promise<...>
  getTopTracksMultiple(timeRange, limit): Promise<...>
  getTopArtistsMultiple(timeRange, limit): Promise<...>
  getCurrentUser(): Promise<SpotifyUser>
}

Token 儲存策略:

  • 前端:localStorage(用於即時 API 調用)
  • 後端:PostgreSQL(用於定時同步任務)

2. 數據服務 (app/lib/data-service.ts)

職責:

  • 合併即時數據和歷史數據
  • 按不同維度聚合數據(單曲、專輯、藝人、時段)
  • 處理時間範圍過濾

核心類別:

DataMerger

負責合併和去重:

class DataMerger {
  mergePlayRecords(
    realtimeRecords: SpotifyRecentlyPlayedTrack[],
    historicalRecords: BackendTrack[],
    window: string
  ): MergedPlayRecord[]
  
  aggregateByTrack(records): AnalyticsTrackData[]
  aggregateByAlbum(records): AlbumRow[]
  aggregateByArtist(records): AnalyticsArtistData[]
}

合併邏輯:

  1. 使用 (trackId, playedAt) 作為唯一鍵去重
  2. 過濾指定時間範圍外的記錄
  3. 合併後按 playedAt 降序排序

DataService

提供統一的數據獲取接口:

class DataService {
  getAnalyticsData(window, analysisType): Promise<AnalyticsResponse>
  getTopAlbums(window): Promise<AlbumRow[]>
  getTimeSegmentAnalysis(window): Promise<...>
}

3. 同步服務 (app/server/services/sync-service.ts)

職責:

  • 從 Spotify API 獲取播放記錄
  • 將新記錄插入資料庫
  • 處理 token 刷新
  • 避免重複同步

同步策略:

async function syncUserData(spotifyUserId: string) {
  // 1. 獲取用戶 tokens(必要時刷新)
  // 2. 查詢資料庫中最後同步時間
  // 3. 從 Spotify API 獲取最近播放記錄
  // 4. 過濾已同步的記錄(基於 played_at > last_sync_at)
  // 5. 批量插入新記錄(自動去重)
  // 6. 更新 last_sync_at
}

去重機制:

  • 資料庫層:UNIQUE(user_id, track_id, played_at) 約束
  • 應用層:只同步 played_at > last_sync_at 的記錄

4. 狀態管理

Zustand Stores

useAuthStore (app/store/useAuthStore.ts)

  • 管理認證狀態(isAuthenticated, user
  • spotifyWebAPI 同步狀態

usePlayerStore (app/store/usePlayerStore.ts)

  • 管理 Spotify 播放器狀態
  • 控制播放、暫停、切換歌曲

TanStack Query

用於數據獲取和快取:

  • 自動處理 loading/error 狀態
  • 智能快取和重新驗證
  • 支援樂觀更新

🔄 數據流向圖

分析頁面數據流

用戶打開 /analytics 頁面
    ↓
Analytics 組件載入
    ↓
useQuery 觸發數據獲取
    ↓
DataService.getAnalyticsData()
    ├─> spotifyWebAPI.getRecentlyPlayedMultiple()
    │   └─> 調用 Spotify API (使用 localStorage 中的 token)
    │
    └─> backendAPI.getHistoricalTracks()
        └─> 調用 /api/analytics/tracks
            └─> 查詢 PostgreSQL play_history 表
    ↓
DataMerger.mergePlayRecords()
    └─> 合併、去重、過濾時間範圍
    ↓
DataMerger.aggregateByTrack/Album/Artist()
    └─> 按維度聚合數據
    ↓
返回分析結果
    ↓
React 組件渲染圖表和列表

同步流程數據流

定時任務觸發 /api/cron/sync
    ↓
驗證 CRON_SECRET
    ↓
syncAllUsers()
    ├─> 從資料庫獲取所有活躍用戶
    └─> 對每個用戶執行 syncUserData()
        ├─> 從資料庫獲取 refresh_token
        ├─> 刷新 access_token(如需要)
        ├─> 調用 Spotify API 獲取最近播放
        ├─> 查詢資料庫 last_sync_at
        ├─> 過濾新記錄
        ├─> 批量插入 play_history
        └─> 更新 last_sync_at

🚀 快速開始

前置需求

  • Node.js 18+
  • PostgreSQL 資料庫(或使用 Supabase、Neon 等託管服務)
  • Spotify Developer Account

安裝步驟

# 1. 複製專案
git clone https://github.com/Waynting/Spotify_Statistic.git
cd Spotify_Statistic

# 2. 安裝依賴
npm install

# 3. 設定環境變數
cp .env.example .env
# 編輯 .env 填入您的配置(見下方說明)

# 4. 執行資料庫遷移
npx tsx app/server/migrations/run-migrations.ts

# 5. 啟動開發環境
npm run dev

訪問 http://localhost:3000 開始使用!


🔑 Spotify 應用設定

詳細的 Spotify 應用設定步驟請參考 docs/SPOTIFY_SETUP.md

快速設定

  1. 前往 Spotify Dashboard
  2. 創建新應用
  3. 設定 Redirect URI:
    • 開發環境:http://localhost:3000/callback
    • 生產環境:https://your-domain.com/callback
  4. 複製 Client ID 和 Client Secret
  5. .env 中設定環境變數

⚠️ 重要限制

用戶白名單限制

  • 非企業 Spotify 帳號最多只能添加 25 個用戶 到白名單
  • 個人使用或小範圍測試通常足夠
  • 公開應用需要申請 Spotify 企業帳號

⚙️ 環境變數設定

創建 .env 檔案並填入以下變數:

# Spotify OAuth
SPOTIFY_CLIENT_ID=你的_client_id
SPOTIFY_CLIENT_SECRET=你的_client_secret
NEXT_PUBLIC_SPOTIFY_REDIRECT_URI=http://localhost:3000/callback

# 資料庫連接(PostgreSQL)
DATABASE_URL=postgresql://user:password@host:port/database

# Cron Job 密鑰(用於自動同步)
CRON_SECRET=隨機生成的密鑰字串

詳細說明請參考 .env.example


📦 部署

Vercel 部署(推薦)

詳細部署步驟請參考 docs/DEPLOYMENT.md

快速部署

  1. Fork 本專案到您的 GitHub
  2. Vercel 導入專案
  3. 設定環境變數(見上方)
  4. 設定 PostgreSQL 資料庫(可使用 Vercel Postgres)
  5. 設定 Vercel Cron Jobs(用於定時同步)
  6. 部署!

其他平台

  • Netlify: 需要設定 Next.js 運行時
  • Railway: 支援 PostgreSQL 和 Next.js
  • 自託管: 需要 Node.js 環境和 PostgreSQL

🛠️ 開發指南

本地開發

# 開發模式
npm run dev

# 建置生產版本
npm run build

# 啟動生產伺服器
npm start

# 執行資料庫遷移
npx tsx app/server/migrations/run-migrations.ts

# 驗證資料庫表結構
npx tsx app/server/migrations/verify-tables.ts

# 測試同步功能
npx tsx app/server/migrations/test-sync.ts

資料庫管理

# 執行遷移
npx tsx app/server/migrations/run-migrations.ts

# 驗證表結構
npx tsx app/server/migrations/verify-tables.ts

📚 技術棧

Frontend:
├── Next.js 14 (App Router)
├── React 18 + TypeScript
├── Tailwind CSS
├── TanStack Query (數據管理)
├── Zustand (狀態管理)
└── Recharts (圖表)

Backend:
├── Next.js API Routes
├── PostgreSQL
└── Spotify Web API

認證:
└── Spotify OAuth 2.0 with PKCE

🔄 數據同步機制

系統會自動同步用戶的播放記錄:

  1. 用戶登入時:自動觸發首次同步
  2. Cron Job:每天 UTC 00:00 自動同步所有用戶
  3. 手動同步:用戶可在設定頁面手動觸發

設定 Vercel Cron Jobs

vercel.json 中已配置:

{
  "crons": [{
    "path": "/api/cron/sync",
    "schedule": "0 0 * * *"
  }]
}

❓ 常見問題

為什麼需要資料庫?

資料庫用於儲存歷史播放記錄,讓您可以:

  • 查看更長時間範圍的數據(超過 Spotify API 的 50 筆限制)
  • 追蹤播放趨勢
  • 提供更準確的分析

資料安全嗎?

  • 所有數據儲存在您的資料庫中
  • OAuth token 加密儲存
  • 支援用戶隨時撤銷授權
  • 前端 tokens 儲存在 localStorage(受同源策略保護)

Spotify API 用戶限制

重要:Spotify API 對非企業用戶有白名單限制:

  • 免費/個人帳號:最多只能添加 25 個用戶 到白名單
  • 企業帳號:無限制

這意味著:

  • 如果您的應用面向公眾,需要申請 Spotify 企業帳號
  • 個人使用或小範圍測試,25 個用戶限制通常足夠
  • 超過 25 個用戶後,新用戶將無法完成 OAuth 授權

解決方案

  1. 申請 Spotify 企業帳號(需要商業驗證)
  2. 限制應用使用範圍在 25 人以內
  3. 使用 Spotify 的 Extended Quota 計劃(需要申請)

可以商用嗎?

本專案採用 MIT 授權,可自由使用。但需遵守 Spotify API 使用條款。注意:商業使用需要 Spotify 企業帳號以支援更多用戶。


📄 授權

MIT License - 可自由使用、修改和分發


🤝 貢獻

歡迎 PR!請確保:

  • 通過 TypeScript 檢查
  • 遵循現有代碼風格
  • 更新相關文檔

Built with ❤️ using Next.js + PostgreSQL + Spotify Web API

About

tool to know your spotify data

Resources

Contributing

Stars

5 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages