Archived
为什么:podcast_api.py(560 行) 与 qr_login.py(397 行) 既不属于任何包、也不是 入口脚本,却同时被 services 层与 web 层引用——`services/downloads.py` 直接 `from qr_login import ...`、`services/podcast.py` 直接 `import podcast_api`, 业务层反向依赖顶层游离模块,是本次架构体检里唯一的无争议分层倒置。 两者本就依赖 music_api(前者取 url_v1/APIConstants/APIException,后者取 QRLoginManager),与 music_api 下已有的 qr.py / compat.py 是同一层的同类物。 迁移(git 保留重命名历史): - podcast_api.py -> music_api/podcast.py - qr_login.py -> music_api/qr_client.py **命名避让**:不叫 music_api/qr_login.py——music_api/__init__ 已经从 .compat 导出了一个**同名函数** qr_login,同名子模块会与之互相遮蔽。改叫 qr_client (其核心导出即 QRLoginClient),并在验证里专门确认 music_api.qr_login 仍是 可调用函数、music_api.podcast 是模块,两者不冲突。 内部导入顺带改为精确子模块,不再绕道包门面: - podcast.py:from music_api import APIConstants/APIException/url_v1 → from .compat import url_v1 / from .constants import ... / from .errors import ... - qr_client.py:删掉那层 try/except ImportError 包装(它给的是「请确保 music_api.py 文件存在」这类顶层脚本时代的提示,作为包内模块已无意义), 改 from .cookies/.errors/.qr 相对导入 - CLI 用法文本 python qr_login.py → python -m music_api.qr_client (相对导入后直接跑脚本会失败,必须走 -m;全仓确认无脚本/文档引用旧入口) - 路径安全性已核对:qr_client 用的是 CWD 相对的 cookie.txt,不是 __file__ 相对, 迁移不改其解析结果 引用点全部更新(AST 扫描确认覆盖全集,替换后残留 0): - podcast:services/podcast.py(1 + 5 处调用) · web/routers/podcast.py(1 + 6) · web/routers/download.py(2 处函数内 + 3),导入名统一为 `podcast` - qr_login:services/downloads.py · web/routers/admin.py · web/routers/user.py(×2) - services/__init__.py 依赖自检列表去掉这两个名字(已并入 music_api) - pyproject include 去掉两个顶层文件名(music_api 目录已覆盖) 验证: - 11 + 3 个符号在新位置全部 hasattr 通过;podcast_api / qr_login 已 ModuleNotFoundError;6 个调用方模块均可 import;services 依赖自检仍生效 - 门禁:ruff 0 · pyright 0 errors/0 warnings · pytest 184 passed - 真实 uvicorn 冒烟 10/10,含 /podcast/parse 与 /podcast/hot 返回 401 (模块导入失败会是 500,路由缺失会是 404,401 证明两者都正常) - 生产 data/ 指纹 before == after
31 lines
1.5 KiB
Python
31 lines
1.5 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""服务层:从原 Flask 巨石中剥离的框架无关实现(不含任何 Flask 依赖)。
|
||
|
||
模块划分(原先集中在单个 1900 行模块,现按职责拆分;调用方直接
|
||
``from services import <子模块>``,不再有兼容门面层):
|
||
|
||
- ``api_service``:MusicAPIService 与进程级实例(config / api_service)
|
||
- ``_shared``:限流桶、请求统计、命名与共享常量
|
||
- ``_taskcore``:任务生命周期原语(启动 / 终态 / 文件收尾 / 结算)
|
||
- ``downloads``:单曲下载、账号探测与任务交付
|
||
- ``batch``:歌单/专辑批量打包 worker
|
||
- ``podcast``:播客单集下载与整台打包 worker
|
||
- ``streaming``:同步流式响应垫片(音频分段透传)
|
||
- ``tasks``:任务监控循环、设备心跳与缓存清扫
|
||
"""
|
||
|
||
# ============ 依赖自检 ============
|
||
# 应用依赖缺失时给出可读提示并非零退出。本层常以 NSSM 服务方式运行、控制台重定向为
|
||
# GBK,裸 traceback 反而更难定位,故提示串保持纯 ASCII。放在包初始化里是因为它必然
|
||
# 先于任何子模块执行——子模块各自 import 失败会先抛 traceback,那段提示就没机会出现。
|
||
import importlib
|
||
import sys
|
||
|
||
for _dep in ('auth_system', 'music_api', 'music_downloader'):
|
||
try:
|
||
importlib.import_module(_dep)
|
||
except ImportError as _exc:
|
||
print("ImportError: " + str(_exc))
|
||
print("Please make sure all dependency modules exist and are importable.")
|
||
sys.exit(1)
|