This repository has been archived on 2026-10-02. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
snowy-yang b089dadac2 refactor: geo 走原生异步 HTTP,清掉短链展开死链
「同步转异步」的残留清理:

1) auth_system/geo.query_ip_province 原经 core.sync_http 同步门面(在后台
   事件循环桥接)查询归属地,但其唯一调用方 record_login_location 的入口
   本来就是 async 的登录链路(web/routers/auth 里还套了一层 to_thread)。
   改为直接 await core.http_client——桥接纯属多余,顺带把 record_login_location
   转 async,落库仍走同步 sqlite 但经 asyncio.to_thread 卸载,避免 sqlite busy
   等待(最长 15s)冻结事件循环。
   调用方两处(普通用户/管理员登录)去掉 to_thread 包装,直接 await。

2) MusicAPIService._extract_music_id 是死代码:ID 归一化早已迁到
   web.request_utils(注释里明说是它的迁移口径),全仓零调用。它也是
   core.utils.expand_shortlink 的唯一调用方——一并删除,core.utils 由此
   彻底不再 import core.sync_http。

门禁:ruff 全过 / pyright 0-0 / pytest 387 项全绿(0 失败)。
2026-09-28 19:10:45 +08:00

117 lines
3.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""服务类与进程级服务实例。
"""
import logging
from pathlib import Path
from core.config import (
Settings,
)
from music_api import (
NeteaseAPI,
load_cookies,
)
from music_downloader import (
MusicDownloader,
)
# 向后兼容别名:既有代码/文档沿用 APIConfig 名称
APIConfig = Settings
class MusicAPIService:
"""音乐API服务类"""
def __init__(self, config: APIConfig):
self.config = config
self.logger = self._setup_logger()
self.netease_api = NeteaseAPI()
# 下载目录传给下载器——原实现下载器固定 downloads,
# 服务配置指向自定义目录时音乐落盘位置与配置分离
self.downloader = MusicDownloader(download_dir=config.downloads_dir)
# 创建下载目录
self.downloads_path = Path(config.downloads_dir)
self.downloads_path.mkdir(exist_ok=True)
self.logger.info(f"音乐API服务初始化完成,下载目录: {self.downloads_path.absolute()}")
def _setup_logger(self) -> logging.Logger:
"""设置日志记录器"""
logger = logging.getLogger('music_api')
logger.setLevel(getattr(logging, self.config.log_level.upper()))
if not logger.handlers:
# 控制台处理器
console_handler = logging.StreamHandler()
console_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console_handler.setFormatter(console_formatter)
logger.addHandler(console_handler)
# 文件处理器(轮转:单文件 5MB,保留 3 份,防止长期运行无限膨胀)
try:
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler('music_api.log', maxBytes=5 * 1024 * 1024,
backupCount=3, encoding='utf-8')
file_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s'
)
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)
except Exception as e:
logger.warning(f"无法创建日志文件: {e}")
return logger
def _get_cookies(self) -> dict[str, str]:
"""直接从 cookie.txt 读取并解析为 dict"""
try:
cookies = load_cookies()
if not cookies:
self.logger.warning("cookie.txt 为空或不存在,部分接口(VIP 音质)将不可用")
return cookies
except Exception as e:
self.logger.error(f"读取 cookie.txt 异常: {e}")
return {}
def _format_file_size(self, size_bytes) -> str:
"""格式化文件大小(上游 size 可能为 null/垃圾值:容错返回"大小未知"而非 500)"""
try:
size = float(size_bytes or 0)
except (TypeError, ValueError):
return "大小未知"
if size <= 0:
return "0B"
units = ["B", "KB", "MB", "GB", "TB"]
unit_index = 0
while size >= 1024.0 and unit_index < len(units) - 1:
size /= 1024.0
unit_index += 1
return f"{size:.2f}{units[unit_index]}"
def _get_quality_display_name(self, quality: str) -> str:
"""获取音质显示名称"""
quality_names = {
'standard': "标准音质",
'exhigh': "极高音质",
'lossless': "无损音质",
'hires': "Hi-Res音质",
'sky': "沉浸环绕声",
'jyeffect': "高清环绕声",
'jymaster': "超清母带",
'dolby': "杜比全景声"
}
return quality_names.get(quality, f"未知音质({quality})")
# 服务实例(框架无关)
config = APIConfig()
api_service = MusicAPIService(config)