chore: bump version to 0.7.1
This commit is contained in:
205
backend/modules/routes.py
Normal file
205
backend/modules/routes.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""
|
||||
路由模块
|
||||
包含所有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连接已清理")
|
||||
Reference in New Issue
Block a user