refactor: 清理 13 处生产死代码

为什么:用 AST 级引用分析(而非文本 grep——同名符号会互相掩盖)确认这些
符号全项目零引用。文本 grep 查不出的原因是它们都有「活着的同名兄弟」:

services/downloads.py(共 6 个,93 行)
  _parse_cookie_str / _global_music_u / _vip_name / _detect_vip /
  _admin_client / _user_client
  —— 活的是 core.media.parse_cookie_str、web/routers/music.py、account.py 的版本
  随之失效的 import:USER_COOKIE_FILE / GLOBAL_MUSIC_U_CACHE / VIP_PROBE_LOCK /
  account_info / detect_vip_by_probe / QRLoginClient

services/api_service.py
  APIResponse 类(29 行)—— web/response.py 才是活的响应封装

services/_shared.py
  _rate_ok —— 逐字复制 web.ratelimit.rate_ok

core/utils.py
  safe_int / safe_console_print —— 活的是 web/request_utils.safe_int

core/repo.py
  all_settings / get_user_by_name —— auth_system.get_user_by_name 才是活的

core/db.py / web/auth_state.py / web/request_utils.py
  execute_many / _safe_register_credits + secrets_rand / dedup_ids

结果:生产代码死代码候选 13 → 0(余下 118 个全是 pytest 测试函数,属正常)。
This commit is contained in:
snowy-yang committed 2026-09-26 18:54:11 +08:00
1 parent 445c16c329
commit 0569b50737
8 files changed
+10 -217

No files matched your search

+1 -13
View File
@@ -14,7 +14,7 @@
import asyncio import asyncio
import os import os
from collections.abc import AsyncGenerator, Iterable, Sequence from collections.abc import AsyncGenerator, Sequence
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -126,18 +126,6 @@ async def execute(sql: str, params: Sequence = ()) -> int:
await conn.close() await conn.close()
async def execute_many(sql: str, seq: Iterable[Sequence]) -> int:
"""批量写,返回受影响行数。"""
async with _write_lock():
conn = await connect()
try:
cur = await conn.executemany(sql, seq)
await conn.commit()
return cur.rowcount
finally:
await conn.close()
@asynccontextmanager @asynccontextmanager
async def write_transaction() -> AsyncGenerator[aiosqlite.Connection, None]: async def write_transaction() -> AsyncGenerator[aiosqlite.Connection, None]:
"""写事务上下文:多条写语句原子提交(串行锁内)。 """写事务上下文:多条写语句原子提交(串行锁内)。
-10
View File
@@ -28,11 +28,6 @@ async def set_setting(key: str, value: str) -> None:
) )
async def all_settings() -> dict[str, str]:
rows = await db.fetch_all('SELECT key, value FROM settings')
return {r['key']: r['value'] for r in rows}
# ---------- announcements ---------- # ---------- announcements ----------
async def list_announcements(limit: int = 20) -> list[dict[str, Any]]: async def list_announcements(limit: int = 20) -> list[dict[str, Any]]:
@@ -49,11 +44,6 @@ async def count_users(role: str | None = None) -> int:
return int(await db.fetch_val('SELECT COUNT(*) FROM users', (), 0)) return int(await db.fetch_val('SELECT COUNT(*) FROM users', (), 0))
async def get_user_by_name(username: str) -> dict[str, Any] | None:
row = await db.fetch_one('SELECT * FROM users WHERE username = ?', (username,))
return dict(row) if row is not None else None
async def get_user_by_id(uid: int) -> dict[str, Any] | None: async def get_user_by_id(uid: int) -> dict[str, Any] | None:
row = await db.fetch_one('SELECT * FROM users WHERE id = ?', (uid,)) row = await db.fetch_one('SELECT * FROM users WHERE id = ?', (uid,))
return dict(row) if row is not None else None return dict(row) if row is not None else None
+1 -22
View File
@@ -1,34 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""纯函数工具(框架无关,无 Web 框架依赖)。 """纯函数工具(框架无关,无 Web 框架依赖)。
安全整数解析、控制台输出兜底、SRT 时间轴、 SRT 时间轴、LRC→SRT 转换、字段参数解析、163cn.tv 短链展开(含私有缓存)。
LRC→SRT 转换、字段参数解析、163cn.tv 短链展开(含私有缓存)。
""" """
import re import re
import sys
import threading import threading
def safe_int(value, default: int) -> int:
"""用户输入的安全整数解析:垃圾值回退默认值(而非 500)"""
try:
return int(value)
except (TypeError, ValueError):
return default
def safe_console_print(text: str) -> None:
"""控制台输出兜底:stdout 被重定向到文件且系统编码非 UTF-8 时
(典型:NSSM 服务日志 + 中文 Windows GBK 控制台),emoji 等字符会触发
UnicodeEncodeError 中断启动流程——实测崩溃循环。此处降级为可编码字符。"""
try:
print(text)
except UnicodeEncodeError:
enc = getattr(sys.stdout, 'encoding', None) or 'ascii'
print(text.encode(enc, errors='replace').decode(enc, errors='replace'))
def srt_time(t: float) -> str: def srt_time(t: float) -> str:
"""秒 → SRT 时间格式 HH:MM:SS,mmm""" """秒 → SRT 时间格式 HH:MM:SS,mmm"""
h = int(t // 3600) h = int(t // 3600)
+4 -21
View File
@@ -2,7 +2,6 @@
"""服务层共享状态与低层工具(限流桶、请求统计、命名与常量)。 """服务层共享状态与低层工具(限流桶、请求统计、命名与常量)。
""" """
import collections
import re import re
import time import time
from typing import Any from typing import Any
@@ -19,25 +18,8 @@ from music_downloader import (
CountingSemaphore, CountingSemaphore,
) )
# 限流实现只有一处:web.ratelimit.rate_ok(同读 core.state 的桶,
def _rate_ok(key: str, limit: int, window: int) -> bool: # 此处不再保留同名副本——曾有两份逐字相同的滑动窗口实现)
"""滑动窗口限流:True=放行,False=超限"""
now = time.time()
with _RATE_LOCK:
dq = _RATE_BUCKETS.get(key)
if dq is None:
# 桶数量防膨胀:超限时清理不活跃桶
if len(_RATE_BUCKETS) > 8000:
for k in [k for k, v in _RATE_BUCKETS.items() if not v or now - v[-1] > window]:
_RATE_BUCKETS.pop(k, None)
dq = collections.deque()
_RATE_BUCKETS[key] = dq
while dq and now - dq[0] > window:
dq.popleft()
if len(dq) >= limit:
return False
dq.append(now)
return True
# 旧版管理员口令已废除(2026-09-15):管理员权限仅认站点管理员会话(admin 登录), # 旧版管理员口令已废除(2026-09-15):管理员权限仅认站点管理员会话(admin 登录),
@@ -76,7 +58,8 @@ def _sweep_rate_buckets() -> None:
_RATE_BUCKETS.pop(k, None) _RATE_BUCKETS.pop(k, None)
# _safe_int / _safe_console_print 已迁出至 core.utils(此处 import 同名共享) # 参数安全转换只有一处:web.request_utils.safe_int
# (原 core.utils.safe_int / safe_console_print 与 _shared 的副本均已删除——三份实现零调用)
# ============ 接口请求统计 ============ # ============ 接口请求统计 ============
-29
View File
@@ -5,7 +5,6 @@
import logging import logging
import re import re
from pathlib import Path from pathlib import Path
from typing import Any
from urllib.parse import parse_qs, urlsplit from urllib.parse import parse_qs, urlsplit
from core.config import ( from core.config import (
@@ -24,34 +23,6 @@ from music_downloader import (
APIConfig = Settings APIConfig = Settings
class APIResponse:
"""API响应工具类"""
@staticmethod
def success(data: Any = None, message: str = 'success', status_code: int = 200) -> tuple[dict[str, Any], int]:
"""成功响应"""
response = {
'status': status_code,
'success': True,
'message': message
}
if data is not None:
response['data'] = data
return response, status_code
@staticmethod
def error(message: str, status_code: int = 400, error_code: str | None = None) -> tuple[dict[str, Any], int]:
"""错误响应"""
response = {
'status': status_code,
'success': False,
'message': message
}
if error_code:
response['error_code'] = error_code
return response, status_code
class MusicAPIService: class MusicAPIService:
"""音乐API服务类""" """音乐API服务类"""
+4 -89
View File
@@ -1,5 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""单曲下载、账号探测与任务交付。 """单曲下载任务 worker:下载 → 打包 → 交付 → 结算。
账号探测(VIP/音质)与二维码登录客户端均已迁出(前者只剩 web.routers.account
的异步实现,后者只剩 web.routers.admin 自己的客户端),本模块只负责单曲任务。
""" """
import os import os
@@ -10,21 +13,11 @@ import zipfile
from pathlib import Path from pathlib import Path
import auth_system import auth_system
from core.config import (
USER_COOKIE_FILE,
)
from core.state import ( from core.state import (
DOWNLOAD_TASKS, DOWNLOAD_TASKS,
DOWNLOAD_TASKS_LOCK, DOWNLOAD_TASKS_LOCK,
) )
from core.state import GLOBAL_MUSIC_U_CACHE as _GLOBAL_MUSIC_U_CACHE
from core.state import VIP_PROBE_LOCK as _VIP_PROBE_LOCK
from core.utils import format_lyric as _format_lyric from core.utils import format_lyric as _format_lyric
from music_api import (
account_info,
detect_vip_by_probe,
)
from music_api.qr_client import QRLoginClient
from music_downloader import ( from music_downloader import (
DownloadCanceled, DownloadCanceled,
DownloadException, DownloadException,
@@ -43,84 +36,6 @@ from .api_service import (
) )
def _parse_cookie_str(raw) -> dict[str, str]:
"""将 'k=v;k2=v2' 格式的 Cookie 字符串解析为字典"""
cookies: dict[str, str] = {}
if not raw:
return cookies
for part in str(raw).split(';'):
if '=' in part:
k, v = part.strip().split('=', 1)
if k:
cookies[k] = v
return cookies
# _GLOBAL_MUSIC_U_CACHE 已迁出至 core.state(此处 import 同名共享)
def _global_music_u() -> str:
"""全局账号 MUSIC_U(5 分钟缓存;后台重新扫码后自动跟随新值)"""
now = time.time()
if _GLOBAL_MUSIC_U_CACHE['v'] is None or now - _GLOBAL_MUSIC_U_CACHE['ts'] > 300:
try:
ck = api_service._get_cookies()
_GLOBAL_MUSIC_U_CACHE['v'] = (ck.get('MUSIC_U') or '').strip()
except Exception:
_GLOBAL_MUSIC_U_CACHE['v'] = ''
_GLOBAL_MUSIC_U_CACHE['ts'] = now
# 契约返回 str:缓存未命中且读取失败时 v 会是 None,统一收敛为空串
return _GLOBAL_MUSIC_U_CACHE['v'] or ''
def _vip_name(vip_type: int) -> str:
"""根据 vipType 返回会员类型名称(兼容新旧取值)"""
return {
0: '普通用户',
1: '音乐包',
11: '黑胶VIP',
100: '黑胶VIP',
10: '黑胶SVIP',
110: '黑胶SVIP'
}.get(vip_type, '普通用户')
def _detect_vip(cookies: dict[str, str]) -> tuple[int, str, list[str]]:
"""检测账号会员类型与可解析音质:
优先账号接口(vipType 精确),并始终用音质探测接口实测可解析列表。
探测单飞锁(有界等待 0.5s)——上游黑洞时并发请求不再各自串行
8 级探测(最坏 16 分钟线程滞留),等锁超时者直接用账号接口给降级结果"""
if not _VIP_PROBE_LOCK.acquire(timeout=0.5):
info = account_info(cookies)
if info:
return info.get('vipType', 0), 'account', []
return 0, 'probe', []
try:
probe = detect_vip_by_probe(cookies)
levels_ok = probe.get('levels_ok', [])
info = account_info(cookies)
if info:
return info.get('vipType', 0), 'account', levels_ok
return probe.get('vip_type', 0), 'probe', levels_ok
finally:
_VIP_PROBE_LOCK.release()
# ============ 管理员设置(二维码登录 / Cookie 管理) ============
def _admin_client() -> QRLoginClient:
"""创建二维码登录客户端(基于 cookie.txt)"""
return QRLoginClient()
# ============ 个人歌单(游客扫码登录自己的账号) ============
def _user_client() -> QRLoginClient:
"""创建个人用户二维码登录客户端(后不再落盘 user_cookie.txt,
USER_COOKIE_FILE 仅作 QRLoginClient 构造参数遗留)"""
return QRLoginClient(USER_COOKIE_FILE)
def _task_delivery_copy(task_id: str, src: Path, filename: str) -> str: def _task_delivery_copy(task_id: str, src: Path, filename: str) -> str:
"""把共享缓存文件复制/硬链接为任务专属交付文件(任务成果与共享缓存分离, """把共享缓存文件复制/硬链接为任务专属交付文件(任务成果与共享缓存分离,
任何任务的清理都不会再删掉别的任务的成果)""" 任何任务的清理都不会再删掉别的任务的成果)"""
-17
View File
@@ -7,8 +7,6 @@
import secrets import secrets
import time import time
import auth_system as auth
# ---- 登录失败锁定(按用户名维度,10 分钟窗口内 >=5 次临时锁定)---- # ---- 登录失败锁定(按用户名维度,10 分钟窗口内 >=5 次临时锁定)----
_LOGIN_FAILS: dict = {} _LOGIN_FAILS: dict = {}
_LOGIN_FAIL_SWEEP_TS = 0.0 _LOGIN_FAIL_SWEEP_TS = 0.0
@@ -119,16 +117,6 @@ def _login_fail_locked_log_once(username: str) -> bool:
return True return True
def _safe_register_credits() -> int:
"""注册赠送次数(settings.register_credits,默认 3)。
防呆:损坏回退默认;<0 一律按 0——设为 -1 会经 create_user 造出"不限次"新号。"""
try:
credits = int(auth.get_setting('register_credits', str(auth.DEFAULT_REGISTER_CREDITS)) or 0)
except (TypeError, ValueError):
return auth.DEFAULT_REGISTER_CREDITS
return max(0, credits)
_SEND_CODE_IP = {} _SEND_CODE_IP = {}
@@ -165,8 +153,3 @@ def re_username_ok(username: str) -> bool:
return bool(_re.fullmatch(r'[A-Za-z0-9_\u4e00-\u9fa5]{3,20}', username)) and \ return bool(_re.fullmatch(r'[A-Za-z0-9_\u4e00-\u9fa5]{3,20}', username)) and \
not _re.match(r'^_+$', username) and not username.startswith('_') and not username.endswith('_') \ not _re.match(r'^_+$', username) and not username.startswith('_') and not username.endswith('_') \
and username.lower() not in _RESERVED_USERNAMES and username.lower() not in _RESERVED_USERNAMES
def secrets_rand() -> int:
import secrets as _s
return _s.randbelow(900000) + 100000
-16
View File
@@ -65,19 +65,3 @@ def cookies() -> dict[str, str]:
"""全局账号 Cookie(管理员 cookie.txt)。""" """全局账号 Cookie(管理员 cookie.txt)。"""
import music_api import music_api
return music_api.load_cookies() return music_api.load_cookies()
def dedup_ids(raw_ids: Any, transform=extract_id) -> list[str]:
"""ID 列表去重(保持首次出现顺序):与 Flask 侧「提取→去重」计费口径一致。"""
if isinstance(raw_ids, str):
raw_list = raw_ids.split(',')
else:
raw_list = [str(x) for x in (raw_ids or [])]
out: list[str] = []
seen: set[str] = set()
for x in raw_list:
s = str(transform(x) or '').strip()
if s and s not in seen:
seen.add(s)
out.append(s)
return out