""" 路由模块 包含所有API端点的定义和处理逻辑 """ import asyncio import time import logging from fastapi import WebSocket, WebSocketDisconnect from pydantic import BaseModel from .apple_music import get_music_status, control_music from .lyrics import get_lyrics_data, delete_lyrics, get_lyrics_from_netease, get_lyrics_from_id, save_lyrics, clear_lyrics_cache from .websocket_manager import ConnectionManager logger = logging.getLogger(__name__) # 音乐控制请求模型 class ControlRequest(BaseModel): action: str # 'playpause', 'previous', 'next', 'seek' position: float = None # 仅当 action=='seek' 时有效 # 手动搜索歌词请求模型 class SearchLyricsRequest(BaseModel): track_name: str artist: str # 获取指定ID的歌词请求模型 class GetLyricsFromIdRequest(BaseModel): id: int track_name: str artist: str def register_routes(app, manager: ConnectionManager): """注册所有API路由到FastAPI应用""" # 保留向下兼容的HTTP接口 @app.get("/status") def status_endpoint(): """获取 Apple Music 播放状态""" return get_music_status() @app.get("/lyrics") def lyrics_endpoint(): """获取当前歌词""" status = get_music_status() return get_lyrics_data(status) @app.delete("/lyrics") def delete_lyrics_endpoint(track_name: str, artist: str): """删除指定歌曲的歌词""" success = delete_lyrics(track_name, artist) if success: return {"status": "success", "message": "歌词已删除"} else: return {"status": "error", "message": "未找到要删除的歌词"} @app.post("/lyrics/search") def search_lyrics_endpoint(request: SearchLyricsRequest): """手动搜索歌词""" try: # 搜索歌词 result = get_lyrics_from_netease(request.track_name, request.artist) if result['code'] == 200 and result['result']['songCount'] > 0: songs = result['result']['songs'] return { "status": "success", "songs": songs } else: return { "status": "error", "message": "未找到歌词" } except Exception as e: logger.error(f"搜索歌词时出错: {e}", exc_info=True) return { "status": "error", "message": f"搜索歌词时出错: {str(e)}" } @app.post("/lyrics/getLyricsFromId") def get_lyrics_from_id_endpoint(request: GetLyricsFromIdRequest): """获取指定ID的歌词""" result = get_lyrics_from_id(request.id) if result['code'] == 200 and 'lrc' in result: logger.debug(f"网易云音乐歌词: {result['lrc']['lyric']}") lyrics_text = result['lrc']['lyric'] # 保存到数据库 save_lyrics(request.track_name, request.artist, lyrics_text) clear_lyrics_cache() return { "status": "success", } # 添加播放控制接口 @app.post("/control") async def control_endpoint(request: ControlRequest): """控制Apple Music播放""" result = control_music(request.action, request.position) # 如果控制成功,广播更新的状态给所有WebSocket客户端 if result["status"] == "success": # 延迟一下,让Apple Music有时间执行命令 await asyncio.sleep(0.1) status = get_music_status() lyrics = get_lyrics_data(status) combined_data = { "type": "position_update", # 添加消息类型 "timestamp": time.time(), "status": status, "lyrics": lyrics } # 异步广播更新 asyncio.create_task(manager.broadcast(combined_data)) return result # WebSocket端点 @app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket): """WebSocket连接处理,合并推送歌词和状态""" await manager.connect(websocket) try: # 连接建立后立即发送一次数据 status = get_music_status() lyrics = get_lyrics_data(status) logger.info(f"发送初始状态数据: 当前歌曲={status.get('track_name', '无')}, 歌词可用={lyrics is not None}") combined_data = { "type": "track_change", # 确保添加消息类型 "timestamp": time.time(), "status": status, "lyrics": lyrics } await websocket.send_json(combined_data) # 发送欢迎消息 await websocket.send_json({"type": "info", "message": "WebSocket连接已建立"}) # 连接状态标志,用于安全终止循环 is_connected = True # 持续接收并处理客户端消息 while is_connected: try: # 接收客户端消息,设置超时避免无限阻塞 data = await asyncio.wait_for(websocket.receive_json(), timeout=1.0) logger.info(f"收到WebSocket消息: {data}") # 处理不同类型的消息 if isinstance(data, dict): # 处理请求状态消息 if data.get("type") == "request_status": logger.info("客户端请求状态更新") status = get_music_status() lyrics = get_lyrics_data(status) update_data = { "type": "track_change", # 确保添加消息类型 "timestamp": time.time(), "status": status, "lyrics": lyrics } await websocket.send_json(update_data) # 处理ping消息 elif data.get("type") == "ping": logger.debug("收到客户端ping") await websocket.send_json({ "type": "pong", "timestamp": time.time() }) except WebSocketDisconnect: # 连接已断开,退出循环 logger.info("WebSocket连接断开") is_connected = False break except asyncio.TimeoutError: # 接收超时,但连接可能仍然有效,继续循环 continue except Exception as e: # 其他错误,记录后继续(除非是连接断开) if "disconnect" in str(e).lower() or "not connected" in str(e).lower(): logger.info(f"WebSocket连接可能已断开: {e}") is_connected = False break else: logger.error(f"处理WebSocket消息时出错: {e}") # 继续监听下一条消息,不断开连接 continue except WebSocketDisconnect: logger.info("WebSocket连接断开 (外层异常)") except Exception as e: logger.error(f"WebSocket错误: {e}") finally: # 确保连接从管理器中移除 await manager.disconnect(websocket) logger.info("WebSocket连接已清理")