Archived
refactor: 拆分 4 个巨石模块为包,清理过时注释并删除作废文档
为什么:单文件 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/ 指纹未变;忽略注释的逐字等价性校验通过。
This commit is contained in:
1 parent
838d635bc5
commit
6288a98ecc
89 files changed
+5806
-5112
No files matched your search
@@ -0,0 +1,249 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""路径常量、连接与建表。
|
||||
|
||||
(自 auth_system.py 拆出,行为与原实现一致。)
|
||||
"""
|
||||
|
||||
import os
|
||||
import secrets
|
||||
import sqlite3
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import auth_system # 路径常量以包属性为唯一来源(动态读取,便于测试/运维覆盖)
|
||||
|
||||
from .security import (
|
||||
_hash_new,
|
||||
_normalize_email,
|
||||
)
|
||||
|
||||
# ============ 路径与常量 ============
|
||||
DATA_DIR = Path('data')
|
||||
|
||||
DB_PATH = DATA_DIR / 'site.db'
|
||||
|
||||
BACKUP_DIR = DATA_DIR / 'backups'
|
||||
|
||||
SECRET_KEY_PATH = DATA_DIR / 'secret.key'
|
||||
|
||||
|
||||
DEFAULT_ADMIN_USER = 'admin'
|
||||
|
||||
SESSION_KEY = 'site_uid'
|
||||
|
||||
DEFAULT_REGISTER_CREDITS = 3 # 注册直接赠送 3 次下载次数(无需领取;TRIAL_CREDITS 死常量已删)
|
||||
|
||||
|
||||
_DB_LOCK = threading.RLock()
|
||||
|
||||
|
||||
# ============ 数据库 ============
|
||||
|
||||
|
||||
def get_conn() -> sqlite3.Connection:
|
||||
auth_system.DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(str(auth_system.DB_PATH), timeout=15)
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute('PRAGMA journal_mode=WAL')
|
||||
return conn
|
||||
|
||||
|
||||
def _q(sql: str, params: tuple = ()) -> list[sqlite3.Row]:
|
||||
"""单语句读:**不持锁**。
|
||||
|
||||
WAL 模式下每个连接读到一致性快照,读与写互不阻塞;旧实现对读也加 _DB_LOCK,
|
||||
等于把全部读串行化并被写挡住。此处仅放开单语句读——写事务(_x 与各
|
||||
`with _DB_LOCK` 块)仍全程串行,故不引入写-写竞争。
|
||||
"""
|
||||
conn = get_conn()
|
||||
try:
|
||||
return conn.execute(sql, params).fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _x(sql: str, params: tuple = ()) -> int:
|
||||
"""执行写操作,返回 lastrowid / rowcount"""
|
||||
with _DB_LOCK:
|
||||
conn = get_conn()
|
||||
try:
|
||||
cur = conn.execute(sql, params)
|
||||
conn.commit()
|
||||
return cur.lastrowid if cur.lastrowid else cur.rowcount
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def init_db() -> None:
|
||||
"""初始化表结构与默认数据"""
|
||||
auth_system.DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
with _DB_LOCK:
|
||||
conn = get_conn()
|
||||
try:
|
||||
conn.executescript('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
salt TEXT NOT NULL,
|
||||
email TEXT DEFAULT '',
|
||||
role TEXT DEFAULT 'user',
|
||||
duration_seconds INTEGER DEFAULT 0,
|
||||
trial_device TEXT DEFAULT '',
|
||||
register_ip TEXT DEFAULT '',
|
||||
register_ua TEXT DEFAULT '',
|
||||
netease_cookie TEXT DEFAULT '',
|
||||
ann_seen TEXT DEFAULT '',
|
||||
last_active INTEGER DEFAULT 0,
|
||||
created_at INTEGER DEFAULT 0,
|
||||
last_login INTEGER DEFAULT 0
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS email_codes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT NOT NULL,
|
||||
code TEXT NOT NULL,
|
||||
expires_at INTEGER NOT NULL,
|
||||
created_at INTEGER DEFAULT 0,
|
||||
purpose TEXT DEFAULT 'register'
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS cards (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT UNIQUE NOT NULL,
|
||||
minutes INTEGER NOT NULL,
|
||||
created_by TEXT DEFAULT '',
|
||||
created_at INTEGER DEFAULT 0,
|
||||
used_by TEXT DEFAULT '',
|
||||
used_at INTEGER DEFAULT 0
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
type TEXT DEFAULT 'success',
|
||||
username TEXT DEFAULT '',
|
||||
ip TEXT DEFAULT '',
|
||||
action TEXT DEFAULT '',
|
||||
detail TEXT DEFAULT '',
|
||||
time TEXT DEFAULT ''
|
||||
);
|
||||
''')
|
||||
# 单行配置表
|
||||
conn.execute('''CREATE TABLE IF NOT EXISTS smtp_config (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
host TEXT DEFAULT '', port INTEGER DEFAULT 587,
|
||||
sender TEXT DEFAULT '', password TEXT DEFAULT '',
|
||||
updated_at INTEGER DEFAULT 0
|
||||
)''')
|
||||
conn.execute('INSERT OR IGNORE INTO smtp_config (id, port) VALUES (1, 587)')
|
||||
# 键值设置表
|
||||
conn.execute('''CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY, value TEXT DEFAULT ''
|
||||
)''')
|
||||
conn.execute('''CREATE TABLE IF NOT EXISTS announcements (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
title TEXT DEFAULT '', content TEXT DEFAULT '',
|
||||
popup_mode TEXT DEFAULT 'off',
|
||||
updated_at INTEGER DEFAULT 0
|
||||
)''')
|
||||
conn.execute('INSERT OR IGNORE INTO announcements (id) VALUES (1)')
|
||||
# 计费账本(任务实扣/交付/退款/终态的持久化唯一记录,结算幂等)
|
||||
conn.execute('''CREATE TABLE IF NOT EXISTS billing_ledger (
|
||||
task_id TEXT PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
reserved INTEGER DEFAULT 0,
|
||||
delivered INTEGER DEFAULT 0,
|
||||
refunded INTEGER DEFAULT 0,
|
||||
status TEXT DEFAULT 'open',
|
||||
created_at INTEGER DEFAULT 0,
|
||||
settled_at INTEGER DEFAULT 0
|
||||
)''')
|
||||
# 轻量迁移:新增列。仅"列已存在"可忽略(其余迁移失败必须暴露,不能吞掉)
|
||||
_already = set()
|
||||
for trow in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall():
|
||||
for crow in conn.execute(f"PRAGMA table_info({trow['name']})").fetchall():
|
||||
_already.add(f"{trow['name']}.{crow['name']}")
|
||||
for alter_sql, tbl_col in (
|
||||
('ALTER TABLE users ADD COLUMN status INTEGER DEFAULT 1', 'users.status'),
|
||||
('ALTER TABLE users ADD COLUMN login_seq INTEGER DEFAULT 0', 'users.login_seq'),
|
||||
('ALTER TABLE users ADD COLUMN recent_provinces TEXT DEFAULT \'[]\'', 'users.recent_provinces'),
|
||||
('ALTER TABLE users ADD COLUMN ban_reason TEXT DEFAULT \'\'', 'users.ban_reason'),
|
||||
('ALTER TABLE cards ADD COLUMN note TEXT DEFAULT \'\'', 'cards.note'),
|
||||
('ALTER TABLE cards ADD COLUMN expire_at INTEGER DEFAULT 0', 'cards.expire_at'),
|
||||
('ALTER TABLE cards ADD COLUMN disabled INTEGER DEFAULT 0', 'cards.disabled'),
|
||||
('ALTER TABLE email_codes ADD COLUMN purpose TEXT DEFAULT \'register\'', 'email_codes.purpose'),
|
||||
):
|
||||
if tbl_col in _already:
|
||||
continue # 幂等:列已存在
|
||||
try:
|
||||
conn.execute(alter_sql)
|
||||
except sqlite3.OperationalError as e:
|
||||
if 'duplicate column' not in str(e).lower():
|
||||
# 磁盘/锁/结构等真实故障:抛出,禁止静默带病初始化
|
||||
raise
|
||||
# 非空邮箱唯一(部分索引,不约束历史空串)。存量库若已有重复非空邮箱,
|
||||
# 索引创建失败仅告警不阻断启动(查重逻辑在注册/建号路由另有兜底)
|
||||
try:
|
||||
conn.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email_nonempty '
|
||||
'ON users(email) WHERE email != \'\'')
|
||||
except sqlite3.IntegrityError:
|
||||
print('[警告] users.email 存在重复的非空邮箱,唯一索引未创建(请人工清理后重启)')
|
||||
# users.email 口径统一为归一化(lower + 剥 Gmail +tag)——与
|
||||
# get_user_by_email/找回/邮箱登录的查询口径对齐。逐行迁移,归一化后与他
|
||||
# 人撞唯一索引的行保留原值跳过(与"存量重复仅告警"同口径)。幂等:已
|
||||
# 归一化的行零写入
|
||||
for urow in conn.execute("SELECT id, email FROM users WHERE email != ''").fetchall():
|
||||
fixed = _normalize_email(urow['email'])
|
||||
if not fixed or fixed == urow['email']:
|
||||
continue
|
||||
try:
|
||||
conn.execute('UPDATE users SET email = ? WHERE id = ?', (fixed, urow['id']))
|
||||
except sqlite3.IntegrityError:
|
||||
print(f"[警告] 用户 {urow['id']} 的邮箱归一化后与他人重复,保留原值")
|
||||
# 初始管理员(新装不再使用公开默认口令,随机生成并写入日志一次)
|
||||
# 引导条件改为"不存在任何管理员角色"——按用户名查会在
|
||||
# 管理员改名(如 admin→qfbsh)后误判缺失,重启即重建出一个新的 admin 账号
|
||||
row = conn.execute("SELECT id FROM users WHERE role = 'admin' LIMIT 1").fetchone()
|
||||
if not row:
|
||||
initial_pass = secrets.token_urlsafe(12)
|
||||
conn.execute(
|
||||
'INSERT INTO users (username, password_hash, salt, role, duration_seconds, created_at) '
|
||||
'VALUES (?, ?, ?, ?, -1, ?)',
|
||||
(DEFAULT_ADMIN_USER, _hash_new(initial_pass), '',
|
||||
'admin', int(time.time())))
|
||||
# ——非 ASCII 输出须防 GBK 重定向下 UnicodeEncodeError
|
||||
try:
|
||||
print(f'[初始化] 已创建管理员账号 admin,初始密码:{initial_pass}(请立即登录并修改,本提示不再显示)')
|
||||
except UnicodeEncodeError:
|
||||
print('[init] admin account created, initial password printed above failed to encode')
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_secret_key() -> str:
|
||||
"""获取(或生成)持久化的会话签名密钥(原子写入 + 失败显式告警)"""
|
||||
key = ''
|
||||
try:
|
||||
if auth_system.SECRET_KEY_PATH.exists():
|
||||
key = auth_system.SECRET_KEY_PATH.read_text(encoding='utf-8').strip()
|
||||
if key:
|
||||
return key
|
||||
except OSError:
|
||||
pass
|
||||
new_key = secrets.token_hex(32)
|
||||
try:
|
||||
auth_system.SECRET_KEY_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
# 先写临时文件再原子替换,避免部分写入留下损坏密钥
|
||||
tmp = auth_system.SECRET_KEY_PATH.with_suffix('.key.tmp')
|
||||
tmp.write_text(new_key, encoding='utf-8')
|
||||
os.replace(str(tmp), str(auth_system.SECRET_KEY_PATH))
|
||||
# 并发初始化者以"替换后文件里的值"为准(最后写入者胜出但全员一致)
|
||||
final = auth_system.SECRET_KEY_PATH.read_text(encoding='utf-8').strip()
|
||||
return final or new_key
|
||||
except OSError as e:
|
||||
# 写入失败不能装作成功:显著告警,返回临时值(本进程会话可用,重启后失效)
|
||||
try:
|
||||
print(f'[警告] 会话密钥持久化失败({e}),本进程将使用临时密钥,重启后所有会话失效!', file=sys.stderr)
|
||||
except UnicodeEncodeError:
|
||||
print('[warn] secret key persistence failed; using temporary key for this process', file=sys.stderr)
|
||||
return new_key
|
||||
Reference in new issue
Block a user