Archived
为什么:单文件 1100~1900 行的巨石模块让「改一处要通读全文」;同时注释里残留 大量已删台账的编号,以及「Flask/FastAPI 双栈迁移期」这类已失实的架构描述, 会误导后来者按不复存在的架构去改代码。 拆分(对外导入路径不变,行为零变更): - music_downloader.py -> music_downloader/(models/downloader/cover/_http/_log/_concurrency) - auth_system.py -> auth_system/(db/security/users/credits/ledger/mail/cards/backup...) - music_api.py -> music_api/(api/client/crypto/qr/cookies/compat/constants/errors) - services/runtime.py -> services/(api_service/_shared/_taskcore/downloads/batch/podcast/ streaming/tasks);services/runtime.py 保留为兼容门面 兼容性做法:各包用 __init__.py 以 __all__ 原样再导出全部符号,既有调用点 (auth_system.xxx / services.xxx / from music_api import Y)零改动;路径常量改为经包属性 动态读取(auth_system.DB_PATH),否则测试的 monkeypatch 会静默失效并写到生产库。 修复 9 处代码损伤(上一轮注释清理把 `()` 当「空括号残留」删掉所致;用「与拆分前逐字 等价」校验逐一定位,静态检查只会报出其中 4 处): - core/config.py ).strip() or '*' - core/state.py threading.Lock() x2 - qr_login.py datetime.now() / .strip() - music_downloader/downloader.py .lower() - services/podcast.py set() / time.time() x2 清理与删除: - 删除 部署文档.md / 功能介绍.md / README_私有包警告.txt - 清理指向已删台账与文档的过时编号注释;把「迁移期 / 两栈 / 双框架 / 已迁移」等 失实描述改写为现状描述(保留 `<-> Flask /path` 映射表等仍有价值的契约来源说明) - _mkpkg.py:公开包剔除清单去掉已不存在的文档名;私有包警告改为自包含 - pyproject.toml:补 pythonpath = ["."],使裸跑 pytest 不再 import core 失败 验证:ruff 全通过;pyright 0 errors / 0 warnings(102 文件);pytest 144 passed; 真实 uvicorn 冒烟 6 条路由符合预期且生产 data/ 指纹未变;忽略注释的逐字等价性校验通过。
72 lines
3.1 KiB
Python
72 lines
3.1 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""进程内运行时状态(框架无关)。
|
||
|
||
自原单体模块迁出:这些容器全部**原地修改**(永不重新绑定),因此
|
||
`from core.state import DOWNLOAD_TASKS` 与各模块的引用共享同一对象,
|
||
搬移不改变任何并发行为。
|
||
|
||
注意:这些状态**仅存于进程内存**,重启即丢(任务记录/限流桶/统计/缓存);
|
||
持久数据(账本/用户/设置)在 SQLite(auth_system)。集中在此以便
|
||
web/ 与各服务模块共用,并为后续封装成带锁的单例(State 对象)留出唯一入口。
|
||
|
||
依赖仅限标准库 threading/collections——任何下载器/HTTP 相关对象
|
||
(如批量打包信号量)不放在这里,随对应服务模块搬迁。
|
||
"""
|
||
|
||
import collections
|
||
import threading
|
||
from typing import Any
|
||
|
||
# ============ 短链展开结果缓存 ============
|
||
# 用户反复解析同一分享链接不再重复请求 163cn.tv(有界防膨胀)
|
||
SHORTLINK_CACHE: dict[str, str] = {}
|
||
SHORTLINK_CACHE_LOCK = threading.Lock()
|
||
|
||
|
||
# ============ 公网限流(按 IP 滑动窗口,防刷防爆破) ============
|
||
RATE_BUCKETS = {}
|
||
RATE_LOCK = threading.Lock()
|
||
|
||
|
||
# ============ 批量打包排队估算 ============
|
||
# 最近完成批次(成功打包过)的耗时记录,用于估算排队用户的预计等待时间
|
||
BATCH_DURATIONS = collections.deque(maxlen=10)
|
||
BATCH_DURATIONS_LOCK = threading.Lock()
|
||
|
||
|
||
# ============ 下载任务表 ============
|
||
# (批量打包 + 单曲下载统一):task_id -> {type, total, done, failed, status, start, msg, cancel, pause, device_id, result, params}
|
||
# 前端通过 /api/batch_progress 轮询进度,支持取消;完成后文件按 35 分钟保留期清理
|
||
DOWNLOAD_TASKS: dict[str, dict[str, Any]] = {}
|
||
DOWNLOAD_TASKS_LOCK = threading.Lock()
|
||
|
||
|
||
# ============ 设备在线心跳 ============
|
||
# device_id -> 最近心跳时间。心跳超时(PAUSE_AFTER_SECONDS)未上线的设备,其批量任务
|
||
# 自动暂停让出排队名额;超时超过 CANCEL_AFTER_SECONDS(30 分钟)则自动取消任务并删文件
|
||
DEVICE_LAST_SEEN: dict[str, float] = {}
|
||
DEVICE_LAST_SEEN_LOCK = threading.Lock()
|
||
|
||
|
||
# ============ 接口请求统计 ============
|
||
REQUEST_STATS: dict[str, dict[str, Any]] = {}
|
||
STATS_LOCK = threading.Lock()
|
||
|
||
# 用户解析/下载操作记录(IP);仅保留最近 2000 条
|
||
REQ_LOG: list[dict[str, Any]] = []
|
||
|
||
|
||
# ============ 上游探测缓存 ============
|
||
# 全局账号 MUSIC_U(5 分钟缓存;后台重新扫码后自动跟随新值)
|
||
GLOBAL_MUSIC_U_CACHE = {'v': None, 'ts': 0.0}
|
||
|
||
# 服务器网易云账号昵称缓存(10 分钟):非管理员可见响应中遮蔽真实昵称
|
||
SERVER_NICK_CACHE = {'nick': None, 'ts': 0.0}
|
||
SERVER_NICK_LOCK = threading.Lock() # 刷新单飞锁,防并发重复打上游
|
||
VIP_PROBE_LOCK = threading.Lock() # 音质探测单飞锁(有界等待)
|
||
|
||
# 全局账号 VIP 探测缓存(5 分钟):probe 一次要打 8 次网易云接口,
|
||
# 公网下账号栏高频刷新会连累服务器账号触发风控。
|
||
VIP_CACHE = {'data': None, 'ts': 0.0}
|
||
VIP_USER_CACHE = {} # user 路径按 cookie 哈希的 5 分钟缓存
|