chore: bump version to 0.7.1

This commit is contained in:
ethan.chen
2025-05-27 14:16:48 +08:00
commit 63eda91fd6
103 changed files with 15868 additions and 0 deletions

205
backend/modules/routes.py Normal file
View 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连接已清理")