🎯 專案動機與背景
Spotify 作為全球最受歡迎的音樂串流平台之一,雖然擁有強大的推薦演算法,但往往會陷入推薦相似歌曲的循環中,使用者缺乏主動探索新音樂的有效途徑。因此,我開發了這個全端應用程式,讓使用者能夠更主動地控制音樂發現過程。
💡 核心理念: “讓使用者主動參與音樂推薦過程,而不是被動接受演算法的建議”
🏗️ 系統架構總覽
🔧 技術堆疊
1Frontend (前端)
2├── Vue.js 3.x
3├── Vue Router
4├── Axios (HTTP Client)
5└── Bootstrap/CSS3
6
7Backend (後端)
8├── Spring Boot 2.x
9├── Spring Security (OAuth2)
10├── Spring Web MVC
11├── Spotify Web API Java Client
12└── Maven
13
14External Services (外部服務)
15├── Spotify Web API
16├── Spotify OAuth 2.0
17└── Machine Learning 推薦引擎
🗺️ 系統架構流程圖
graph TD
A[使用者] --> B[Vue.js Frontend]
B --> C[Spring Boot Backend]
C --> D[Spotify OAuth Server]
C --> E[Spotify Web API]
C --> F[ML Recommendation Engine]
D --> G[Access Token]
G --> C
E --> H[音樂資料]
H --> C
F --> I[個人化推薦]
I --> C
C --> B
B --> A
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#e8f5e8
style D fill:#fff3e0
style E fill:#fff3e0
style F fill:#fce4ec
⭐ 核心功能特色
🤖 1. 智能音樂推薦系統
- 基於機器學習的推薦演算法
- 多維度音樂特徵分析 (節拍、能量、舞蹈性等)
- 使用者偏好學習與適應
🔍 2. 互動式音樂探索
- 藝人/歌曲智能搜尋
- 專輯預覽與試聽功能
- 相關音樂發現
🔐 3. Spotify 深度整合
- OAuth 2.0 安全認證
- 即時播放清單同步
- 使用者音樂庫存取
🖥️ 後端核心實作
🔐 Spotify OAuth 認證流程
1@RestController
2@RequestMapping("/api/spotify")
3public class SpotifyController {
4
5 @Autowired
6 private SpotifyService spotifyService;
7
8 /**
9 * 初始化 Spotify OAuth 認證流程
10 * 引導使用者至 Spotify 授權頁面
11 */
12 @GetMapping("/auth")
13 public ResponseEntity<?> authenticateSpotify(HttpServletRequest request) {
14 try {
15 // 生成隨機 state 參數防止 CSRF 攻擊
16 String state = UUID.randomUUID().toString();
17 request.getSession().setAttribute("spotify_state", state);
18
19 // 構建 Spotify 授權 URL
20 String authUrl = spotifyService.getAuthorizationUrl(state);
21
22 return ResponseEntity.ok(Map.of(
23 "authUrl", authUrl,
24 "message", "請前往此 URL 進行 Spotify 授權"
25 ));
26 } catch (Exception e) {
27 return ResponseEntity.status(500)
28 .body(Map.of("error", "授權初始化失敗: " + e.getMessage()));
29 }
30 }
31
32 /**
33 * 處理 Spotify OAuth 回調
34 * 交換授權碼取得存取權杖
35 */
36 @GetMapping("/callback")
37 public ResponseEntity<?> handleCallback(
38 @RequestParam("code") String code,
39 @RequestParam("state") String state,
40 HttpServletRequest request) {
41
42 try {
43 // 驗證 state 參數
44 String sessionState = (String) request.getSession().getAttribute("spotify_state");
45 if (!state.equals(sessionState)) {
46 throw new SecurityException("State 參數驗證失敗");
47 }
48
49 // 交換授權碼取得 access token
50 SpotifyTokens tokens = spotifyService.exchangeCodeForTokens(code);
51
52 // 儲存 tokens 到 session 或資料庫
53 request.getSession().setAttribute("spotify_tokens", tokens);
54
55 return ResponseEntity.ok(Map.of(
56 "message", "Spotify 授權成功",
57 "expiresIn", tokens.getExpiresIn()
58 ));
59
60 } catch (Exception e) {
61 return ResponseEntity.status(400)
62 .body(Map.of("error", "授權處理失敗: " + e.getMessage()));
63 }
64 }
65}
🎵 音樂推薦核心演算法
1@Service
2public class MusicRecommendationService {
3
4 @Autowired
5 private SpotifyApiService spotifyApiService;
6
7 /**
8 * 基於使用者偏好產生音樂推薦
9 * 結合多種推薦策略提供個人化建議
10 */
11 public List<Track> generateRecommendations(String userId, RecommendationRequest request) {
12 try {
13 // 1. 獲取使用者歷史播放記錄
14 List<Track> recentTracks = spotifyApiService.getRecentlyPlayed(userId, 50);
15
16 // 2. 分析音樂特徵偏好
17 AudioFeaturePreferences preferences = analyzeUserPreferences(recentTracks);
18
19 // 3. 種子歌曲/藝人選擇
20 RecommendationSeeds seeds = buildRecommendationSeeds(request, preferences);
21
22 // 4. 呼叫 Spotify 推薦 API
23 List<Track> spotifyRecommendations = spotifyApiService.getRecommendations(
24 seeds.getArtists(),
25 seeds.getTracks(),
26 seeds.getGenres(),
27 preferences.toTuneableAttributes()
28 );
29
30 // 5. 應用自定義過濾與排序
31 List<Track> filteredTracks = applyCustomFiltering(
32 spotifyRecommendations,
33 preferences,
34 request.getExcludedArtists()
35 );
36
37 // 6. 多樣性增強處理
38 return enhanceDiversity(filteredTracks, request.getDiversityLevel());
39
40 } catch (Exception e) {
41 log.error("音樂推薦生成失敗", e);
42 throw new RecommendationException("推薦系統暫時無法使用", e);
43 }
44 }
45
46 /**
47 * 分析使用者音樂偏好模式
48 * 從歷史播放記錄中提取音頻特徵趋势
49 */
50 private AudioFeaturePreferences analyzeUserPreferences(List<Track> recentTracks) {
51 if (recentTracks.isEmpty()) {
52 return AudioFeaturePreferences.getDefault();
53 }
54
55 // 批次獲取音頻特徵
56 List<String> trackIds = recentTracks.stream()
57 .map(Track::getId)
58 .collect(Collectors.toList());
59
60 List<AudioFeatures> audioFeatures = spotifyApiService.getAudioFeatures(trackIds);
61
62 // 計算各項特徵的平均值與標準差
63 DoubleSummaryStatistics energyStats = audioFeatures.stream()
64 .mapToDouble(AudioFeatures::getEnergy)
65 .summaryStatistics();
66
67 DoubleSummaryStatistics valenceStats = audioFeatures.stream()
68 .mapToDouble(AudioFeatures::getValence)
69 .summaryStatistics();
70
71 DoubleSummaryStatistics danceabilityStats = audioFeatures.stream()
72 .mapToDouble(AudioFeatures::getDanceability)
73 .summaryStatistics();
74
75 // 建構偏好物件
76 return AudioFeaturePreferences.builder()
77 .targetEnergy((float) energyStats.getAverage())
78 .energyRange(calculateOptimalRange(energyStats))
79 .targetValence((float) valenceStats.getAverage())
80 .valenceRange(calculateOptimalRange(valenceStats))
81 .targetDanceability((float) danceabilityStats.getAverage())
82 .danceabilityRange(calculateOptimalRange(danceabilityStats))
83 .build();
84 }
85
86 /**
87 * 多樣性增強演算法
88 * 確保推薦結果具有適當的多樣性,避免推薦過於相似的音樂
89 */
90 private List<Track> enhanceDiversity(List<Track> tracks, DiversityLevel level) {
91 if (level == DiversityLevel.LOW || tracks.size() <= 10) {
92 return tracks.subList(0, Math.min(20, tracks.size()));
93 }
94
95 List<Track> diversifiedTracks = new ArrayList<>();
96 Set<String> selectedArtists = new HashSet<>();
97 Set<String> selectedGenres = new HashSet<>();
98
99 // 第一輪:選擇不同藝人的高品質推薦
100 for (Track track : tracks) {
101 if (diversifiedTracks.size() >= 15) break;
102
103 String primaryArtist = track.getArtists().get(0).getId();
104 if (!selectedArtists.contains(primaryArtist)) {
105 diversifiedTracks.add(track);
106 selectedArtists.add(primaryArtist);
107
108 // 記錄風格資訊(如果可用)
109 if (track.getGenres() != null) {
110 selectedGenres.addAll(track.getGenres());
111 }
112 }
113 }
114
115 // 第二輪:在剩餘空間中加入多樣性選項
116 if (level == DiversityLevel.HIGH && diversifiedTracks.size() < 20) {
117 for (Track track : tracks) {
118 if (diversifiedTracks.size() >= 20) break;
119 if (diversifiedTracks.contains(track)) continue;
120
121 // 優先選擇不同風格的歌曲
122 boolean isDifferentGenre = track.getGenres() != null &&
123 track.getGenres().stream().noneMatch(selectedGenres::contains);
124
125 if (isDifferentGenre || diversifiedTracks.size() < 18) {
126 diversifiedTracks.add(track);
127 }
128 }
129 }
130
131 return diversifiedTracks;
132 }
133}
🔌 Spotify API 整合服務
1@Service
2public class SpotifyApiService {
3
4 private final SpotifyApi spotifyApi;
5 private final TokenRefreshService tokenRefreshService;
6
7 public SpotifyApiService(SpotifyConfiguration config) {
8 this.spotifyApi = SpotifyApi.builder()
9 .setClientId(config.getClientId())
10 .setClientSecret(config.getClientSecret())
11 .setRedirectUri(SpotifyHttpManager.makeUri(config.getRedirectUri()))
12 .build();
13 }
14
15 /**
16 * 搜尋藝人資訊
17 * 提供模糊搜尋與自動完成功能
18 */
19 public List<Artist> searchArtists(String query, int limit) {
20 try {
21 ensureValidToken();
22
23 SearchArtistsRequest request = spotifyApi.searchArtists(query)
24 .limit(limit)
25 .market(CountryCode.TW)
26 .build();
27
28 Paging<Artist> artistPaging = request.execute();
29 return Arrays.asList(artistPaging.getItems());
30
31 } catch (IOException | SpotifyWebApiException | ParseException e) {
32 log.error("藝人搜尋失敗: query={}", query, e);
33 throw new SpotifyServiceException("搜尋服務暫時無法使用", e);
34 }
35 }
36
37 /**
38 * 獲取藝人熱門歌曲
39 * 用於推薦系統的種子選擇
40 */
41 public List<Track> getArtistTopTracks(String artistId, int limit) {
42 try {
43 ensureValidToken();
44
45 GetArtistsTopTracksRequest request = spotifyApi.getArtistsTopTracks(artistId, CountryCode.TW)
46 .build();
47
48 Track[] tracks = request.execute();
49 return Arrays.stream(tracks)
50 .limit(limit)
51 .collect(Collectors.toList());
52
53 } catch (Exception e) {
54 log.error("獲取藝人熱門歌曲失敗: artistId={}", artistId, e);
55 throw new SpotifyServiceException("無法獲取藝人資訊", e);
56 }
57 }
58
59 /**
60 * Token 自動刷新機制
61 * 確保 API 呼叫的持續有效性
62 */
63 private void ensureValidToken() {
64 try {
65 String currentToken = getCurrentAccessToken();
66 if (tokenRefreshService.isTokenExpired(currentToken)) {
67 String refreshedToken = tokenRefreshService.refreshAccessToken();
68 spotifyApi.setAccessToken(refreshedToken);
69 log.info("Spotify access token 已自動刷新");
70 }
71 } catch (Exception e) {
72 log.error("Token 刷新失敗", e);
73 throw new SpotifyServiceException("認證失效,請重新登入", e);
74 }
75 }
76}
💻 前端實作重點
🧩 Vue.js 主要元件架構
1// SpotifyAuth.vue - 認證元件
2<template>
3 <div class="spotify-auth">
4 <div v-if="!isAuthenticated" class="auth-container">
5 <h2>連接您的 Spotify 帳戶</h2>
6 <p>授權後即可開始探索個人化音樂推薦</p>
7 <button @click="initiateAuth" class="auth-button" :disabled="loading">
8 <i class="fab fa-spotify"></i>
9 {{ loading ? '連接中...' : '連接 Spotify' }}
10 </button>
11 </div>
12
13 <div v-else class="auth-success">
14 <h3>✓ Spotify 帳戶已連接</h3>
15 <p>歡迎回來,{{ userProfile.display_name }}!</p>
16 <button @click="logout" class="logout-button">登出</button>
17 </div>
18 </div>
19</template>
20
21<script>
22import { mapState, mapActions } from 'vuex'
23
24export default {
25 name: 'SpotifyAuth',
26 data() {
27 return {
28 loading: false
29 }
30 },
31 computed: {
32 ...mapState('spotify', ['isAuthenticated', 'userProfile'])
33 },
34 methods: {
35 ...mapActions('spotify', ['authenticate', 'fetchUserProfile', 'clearAuth']),
36
37 async initiateAuth() {
38 try {
39 this.loading = true
40 const response = await this.$http.get('/api/spotify/auth')
41
42 // 開啟新視窗進行 OAuth 認證
43 const authWindow = window.open(
44 response.data.authUrl,
45 'spotify-auth',
46 'width=600,height=700,scrollbars=yes,resizable=yes'
47 )
48
49 // 監聽認證完成訊息
50 this.listenForAuthComplete(authWindow)
51
52 } catch (error) {
53 this.$toast.error('認證初始化失敗:' + error.message)
54 } finally {
55 this.loading = false
56 }
57 },
58
59 listenForAuthComplete(authWindow) {
60 const checkClosed = setInterval(() => {
61 if (authWindow.closed) {
62 clearInterval(checkClosed)
63 this.checkAuthStatus()
64 }
65 }, 1000)
66
67 // 監聽來自認證視窗的訊息
68 window.addEventListener('message', (event) => {
69 if (event.data.type === 'SPOTIFY_AUTH_SUCCESS') {
70 clearInterval(checkClosed)
71 authWindow.close()
72 this.handleAuthSuccess()
73 }
74 })
75 },
76
77 async handleAuthSuccess() {
78 await this.authenticate()
79 await this.fetchUserProfile()
80 this.$toast.success('Spotify 認證成功!')
81 this.$router.push('/recommendations')
82 },
83
84 logout() {
85 this.clearAuth()
86 this.$toast.info('已登出 Spotify')
87 }
88 }
89}
90</script>
🎛️ 音樂推薦介面元件
1// MusicRecommendations.vue - 推薦系統主介面
2<template>
3 <div class="recommendations-container">
4 <!-- 推薦參數控制面板 -->
5 <div class="recommendation-controls">
6 <h2>個人化音樂推薦</h2>
7
8 <form @submit.prevent="generateRecommendations" class="controls-form">
9 <!-- 種子藝人選擇 -->
10 <div class="form-group">
11 <label>喜愛的藝人 (最多 5 位)</label>
12 <ArtistSelector
13 v-model="seedArtists"
14 :max-selections="5"
15 @artists-changed="onArtistsChanged"
16 />
17 </div>
18
19 <!-- 音樂特徵調整 -->
20 <div class="form-group">
21 <label>音樂風格偏好</label>
22 <div class="feature-sliders">
23 <FeatureSlider
24 v-for="feature in audioFeatures"
25 :key="feature.key"
26 :label="feature.label"
27 :value="feature.value"
28 :description="feature.description"
29 @input="updateFeature(feature.key, $event)"
30 />
31 </div>
32 </div>
33
34 <!-- 多樣性控制 -->
35 <div class="form-group">
36 <label>推薦多樣性</label>
37 <select v-model="diversityLevel" class="diversity-select">
38 <option value="LOW">相似風格為主</option>
39 <option value="MEDIUM">平衡探索</option>
40 <option value="HIGH">最大化多樣性</option>
41 </select>
42 </div>
43
44 <button type="submit" class="generate-btn" :disabled="generating">
45 {{ generating ? '生成中...' : '生成推薦清單' }}
46 </button>
47 </form>
48 </div>
49
50 <!-- 推薦結果展示 -->
51 <div v-if="recommendations.length > 0" class="recommendations-results">
52 <h3>為您推薦的音樂</h3>
53 <div class="tracks-grid">
54 <TrackCard
55 v-for="track in recommendations"
56 :key="track.id"
57 :track="track"
58 @play="playTrack"
59 @add-to-playlist="showPlaylistModal"
60 @like="toggleLike"
61 />
62 </div>
63
64 <!-- 批次操作 -->
65 <div class="batch-actions">
66 <button @click="createPlaylist" class="create-playlist-btn">
67 建立為新播放清單
68 </button>
69 <button @click="exportRecommendations" class="export-btn">
70 匯出推薦結果
71 </button>
72 </div>
73 </div>
74
75 <!-- 載入中狀態 -->
76 <div v-if="generating" class="loading-container">
77 <div class="loading-spinner"></div>
78 <p>正在分析您的音樂偏好,請稍候...</p>
79 </div>
80 </div>
81</template>
82
83<script>
84import ArtistSelector from '@/components/ArtistSelector.vue'
85import FeatureSlider from '@/components/FeatureSlider.vue'
86import TrackCard from '@/components/TrackCard.vue'
87
88export default {
89 name: 'MusicRecommendations',
90 components: {
91 ArtistSelector,
92 FeatureSlider,
93 TrackCard
94 },
95 data() {
96 return {
97 seedArtists: [],
98 diversityLevel: 'MEDIUM',
99 generating: false,
100 recommendations: [],
101 audioFeatures: [
102 {
103 key: 'energy',
104 label: '能量感',
105 value: 0.5,
106 description: '音樂的強度與活力程度'
107 },
108 {
109 key: 'valence',
110 label: '情感僾向',
111 value: 0.5,
112 description: '正面情感 vs 憂鬱情感'
113 },
114 {
115 key: 'danceability',
116 label: '舞蹈性',
117 value: 0.5,
118 description: '適合跳舞的程度'
119 },
120 {
121 key: 'acousticness',
122 label: '原聲比例',
123 value: 0.5,
124 description: '原聲樂器 vs 電子音樂'
125 }
126 ]
127 }
128 },
129 methods: {
130 async generateRecommendations() {
131 if (this.seedArtists.length === 0) {
132 this.$toast.warning('請至少選擇一位喜愛的藝人')
133 return
134 }
135
136 try {
137 this.generating = true
138
139 const requestData = {
140 seedArtists: this.seedArtists.map(artist => artist.id),
141 audioFeatures: this.getAudioFeaturesValues(),
142 diversityLevel: this.diversityLevel,
143 limit: 20
144 }
145
146 const response = await this.$http.post('/api/recommendations', requestData)
147 this.recommendations = response.data.tracks
148
149 // 記錄推薦成功事件
150 this.$analytics.track('recommendation_generated', {
151 seed_artists_count: this.seedArtists.length,
152 diversity_level: this.diversityLevel,
153 results_count: this.recommendations.length
154 })
155
156 } catch (error) {
157 this.$toast.error('推薦生成失敗:' + error.message)
158 } finally {
159 this.generating = false
160 }
161 },
162
163 getAudioFeaturesValues() {
164 return this.audioFeatures.reduce((features, feature) => {
165 features[feature.key] = feature.value
166 return features
167 }, {})
168 },
169
170 updateFeature(key, value) {
171 const feature = this.audioFeatures.find(f => f.key === key)
172 if (feature) {
173 feature.value = value
174 }
175 },
176
177 async createPlaylist() {
178 try {
179 const trackUris = this.recommendations.map(track => track.uri)
180 const playlistName = `個人推薦 - ${new Date().toLocaleDateString()}`
181
182 await this.$http.post('/api/playlists', {
183 name: playlistName,
184 tracks: trackUris,
185 description: '由智能推薦系統生成的個人化播放清單'
186 })
187
188 this.$toast.success('播放清單建立成功!')
189 } catch (error) {
190 this.$toast.error('播放清單建立失敗')
191 }
192 }
193 }
194}
195</script>
🚀 部署與配置
🐳 Docker 容器化部署
1# docker-compose.yml
2version: '3.8'
3
4services:
5 # Spring Boot 後端服務
6 backend:
7 build:
8 context: ./backend/SpotifyPlayList
9 dockerfile: Dockerfile
10 container_name: spotify-backend
11 ports:
12 - "8888:8888"
13 environment:
14 - SPOTIFY_CLIENT_ID=${SPOTIFY_CLIENT_ID}
15 - SPOTIFY_CLIENT_SECRET=${SPOTIFY_CLIENT_SECRET}
16 - SPOTIFY_REDIRECT_URI=${SPOTIFY_REDIRECT_URI}
17 - SERVER_PORT=8888
18 volumes:
19 - ./logs:/app/logs
20 depends_on:
21 - redis
22 networks:
23 - spotify-network
24 restart: unless-stopped
25
26 # Vue.js 前端服務
27 frontend:
28 build:
29 context: ./frontend/spotify-playlist-ui
30 dockerfile: Dockerfile
31 container_name: spotify-frontend
32 ports:
33 - "3000:80"
34 environment:
35 - VUE_APP_API_BASE_URL=http://localhost:8888/api
36 - VUE_APP_SPOTIFY_CLIENT_ID=${SPOTIFY_CLIENT_ID}
37 depends_on:
38 - backend
39 networks:
40 - spotify-network
41 restart: unless-stopped
42
43 # Redis 快取服務
44 redis:
45 image: redis:7-alpine
46 container_name: spotify-redis
47 ports:
48 - "6379:6379"
49 volumes:
50 - redis-data:/data
51 command: redis-server --appendonly yes
52 networks:
53 - spotify-network
54 restart: unless-stopped
55
56 # Nginx 反向代理
57 nginx:
58 image: nginx:alpine
59 container_name: spotify-nginx
60 ports:
61 - "80:80"
62 - "443:443"
63 volumes:
64 - ./nginx/nginx.conf:/etc/nginx/nginx.conf
65 - ./nginx/ssl:/etc/nginx/ssl
66 depends_on:
67 - frontend
68 - backend
69 networks:
70 - spotify-network
71 restart: unless-stopped
72
73volumes:
74 redis-data:
75
76networks:
77 spotify-network:
78 driver: bridge
⚙️ 環境配置檔案
1# application.properties (Spring Boot 配置)
2
3# 伺服器配置
4server.port=8888
5server.servlet.context-path=/api
6
7# Spotify API 配置
8spotify.client-id=${SPOTIFY_CLIENT_ID:your-client-id}
9spotify.client-secret=${SPOTIFY_CLIENT_SECRET:your-client-secret}
10spotify.redirect-uri=${SPOTIFY_REDIRECT_URI:http://localhost:3000/callback}
11
12# 快取配置
13spring.cache.type=redis
14spring.redis.host=${REDIS_HOST:localhost}
15spring.redis.port=${REDIS_PORT:6379}
16spring.redis.timeout=2000ms
17spring.redis.lettuce.pool.max-active=10
18
19# 日誌配置
20logging.level.com.yen.spotify=DEBUG
21logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
22logging.file.name=./logs/spotify-app.log
23
24# CORS 配置
25app.cors.allowed-origins=${CORS_ORIGINS:http://localhost:3000}
26app.cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
27app.cors.allowed-headers=*
28
29# 推薦系統配置
30recommendation.default-limit=20
31recommendation.max-seed-artists=5
32recommendation.cache-duration=30m
33
34# 安全配置
35app.jwt.secret=${JWT_SECRET:your-jwt-secret}
36app.jwt.expiration=86400000
💎 系統特色與創新點
🧠 1. 智能化推薦策略
- 多維度特徵分析: 不只依賴風格標籤,深入分析音頻特徵
- 動態偏好學習: 根據使用者行為持續調整推薦模型
- 多樣性平衡: 在相關性與探索性之間找到最佳平衡
🎨 2. 使用者體驗優化
- 直觀的參數控制: 讓一般使用者也能輕鬆調整推薦參數
- 即時預覽功能: 在生成完整推薦前提供快速預覽
- 個人化介面: 根據使用者偏好自動調整介面主題
🏛️ 3. 技術架構優勢
- 微服務設計: 前後端分離,便於獨立擴展
- 容器化部署: 支援 Docker,簡化部署流程
- 快取優化: 減少 Spotify API 呼叫,提升回應速度
🔮 未來發展規劃
📋 短期目標 (3-6 個月)
- 整合 ChatGPT API: 提供自然語言音樂描述搜尋
- UI/UX 重新設計: 更現代化的使用者介面
- 行動端 App: React Native 跨平台應用
- 社交功能: 朋友間的播放清單分享與推薦
🎯 中期目標 (6-12 個月)
- 機器學習模型升級: 自建深度學習推薦系統
- 多平台整合: 支援 Apple Music、YouTube Music
- 情境感知推薦: 基於時間、天氣、活動的智能推薦
- CI/CD 流水線: 自動化測試與部署
🌟 長期願景 (1-2 年)
- 雲端原生架構: 遷移至 AWS/GCP
- 大數據分析: 音樂趨勢分析與預測
- 商業化功能: 藝人推廣與音樂行銷工具
- 國際化支援: 多語系與全球音樂市場適應
🎉 總結與心得
這個 Spotify 播放清單推薦系統不僅展示了全端開發的完整流程,更重要的是體現了以使用者為中心的產品思維。透過深度整合 Spotify API 與自建的推薦演算法,我們成功打破了傳統音樂平台的推薦局限性。
🔧 技術收穫
- OAuth 2.0 深度實作: 深入理解第三方 API 整合的安全性考量
- 推薦系統設計: 學習了從資料收集到演算法實現的完整流程
- 前後端協作: Vue.js 與 Spring Boot 的無縫整合經驗
- 容器化實戰: Docker 在複雜應用架構中的實際運用
💎 產品價值
- 使用者自主性: 讓使用者主動參與音樂發現過程
- 個人化體驗: 基於深度學習的個人偏好建模
- 探索樂趣: 在熟悉與新奇之間找到完美平衡
這個專案證明了技術創新與使用者需求的完美結合,未來將持續迭代優化,為音樂愛好者帶來更豐富的聽覺體驗。
🔗 相關連結
項目 | 連結 |
---|---|
📂 專案原始碼 | GitHub - SpringPlayground/springSpotifyPlayList |
🌐 線上展示 | 即將推出 |
📖 技術文件 | API 文件 |