snowy-yang
|
a2758aa0da
|
feat(routes): 迁移解析端点 /song /playlist /album + 响应卫生中间件
迁移路由(/api/v1/*,与 Flask 同端点逐字段对齐):
- song(type: url/name/lyric/json)、playlist、album
个人账号路径实现全局账号冒用拦截(同 Flask 口径);json 模式 asyncio.gather 并发
song/url/lyric(歌词失败不阻断,与 Flask 线程池语义一致)
- core/media.py:format_file_size/quality_display_name/parse_cookie_str 纯函数复用
web/middleware.py(镜像 Flask after_request):
- 音频直链 → HMAC 签名代理地址(密钥同源,两栈 token 互认)
- 安全头 nosniff/DENY/Referrer-Policy/CSP 基线;Server 抹为 NeteaseUrl;429 带 Retry-After
- 纯 ASGI 逐段转发(不破坏流式下载);JSON 200 延迟 start 并**重算 Content-Length**
——修实测 IncompleteRead(原实现转发旧长度 + 新 body)
契约奇偶校验:12 项全 OK(新增 album/playlist/song×3);
token/CDN 主机归一化(均为易变值,非契约漂移)。
验证:pytest 36/36;ruff All checks passed;pyright 0 errors;
uvicorn 实跑 /api/v1/song 直链已代理、CSP/Server 头正确。
|
2026-09-26 01:12:54 +08:00 |
|
snowy-yang
|
2c55b55e03
|
feat(routes): 迁移查询类路由到 FastAPI + 会话互通 + 统一错误/限流
会话互通(迁移关键):
- core/session.py:itsdangerous 兼容的签名 Cookie 编解码(同密钥/同 salt/同
时间戳编码),Flask 与 FastAPI 双向可读——实测 F->core 与 core->F 双向解出一致,
篡改/错密钥返回 None
FastAPI 基础设施:
- web/deps.py 会话→当前用户(封禁/顶号校验,口径同 auth_system.current_user)
- web/errors.py AppError + 全局异常处理,404/405/400 统一为本站包体
- web/ratelimit.py 复用 core.state 同一批限流桶(跨栈共享,避免绕框架绕过限流)
- core/repo.py 补 get_user_by_id(异步)
迁移路由(/api/v1/*,与 Flask 同端点逐字段对齐):
- toplist / artist_songs / comments / search,走 music_api 异步方法
- search 的服务器昵称遮蔽改为 FastAPI 侧异步实现(不再依赖 main)
契约奇偶校验:扩展至 7 项(含登录会话互通 + 未登录 401),全 OK;
CDN 分片主机差异归一化(上游易变值,非契约漂移)。
验证:pytest 33/33;ruff All checks passed;pyright 0 errors;
uvicorn 实跑未登录 401 包体与 Flask 一致。
|
2026-09-26 01:09:01 +08:00 |
|
snowy-yang
|
c77ef40fd5
|
feat(db): 同步栈读写并发(_q 去锁)+ core.db 并发/跨loop 修复
auth_system._q:单语句读不再持 _DB_LOCK —— WAL 下每连接读一致性快照,
读写互不阻塞;旧实现把全部读也串行化且被写挡住。写事务(_x 与 24 个
with _DB_LOCK 块,经扫描均为写事务)仍全程串行,无写-写竞争。
core.db 并发修复(由测试暴露的真实缺陷):
- WAL 切换竞态:并发连接同时执行 PRAGMA journal_mode=WAL 争排他锁
→ 专用锁内串行切换 + 二次复查(busy_timeout 前置)
- 跨事件循环锁:模块级 asyncio.Lock 绑定首个 loop,多 loop 场景报
"bound to a different event loop" → 锁按 loop 隔离
测试:25/25 通过(新增「读不被写锁阻塞」红转绿、读+同步写者并发 WAL、
异步并发读);
lint: ruff All checks passed;pyright 0 errors。
验收:冒烟 10/10、奇偶校验 2/2、uvicorn 真实启动 Flask 兜底+FastAPI 双通。
|
2026-09-26 01:03:18 +08:00 |
|
snowy-yang
|
52ed8b54fb
|
feat(server): 生产服务器切换 uvicorn(FastAPI + Flask 兜底)
- web/main.py:uvicorn 入口,默认开启 Flask 兜底(未迁移路由回落遗留应用);
HOST/PORT/LOG_LEVEL 环境变量可配;proxy_headers 按 XFF 取真实 IP(仅信回环);
server_header=False 抹服务器指纹
- entrypoint.sh:默认 exec web/main.py;WEB_STACK=flask 可应急回退旧入口
- Dockerfile:安装 requirements-fastapi.txt(目标栈)+ requirements.txt(Flask 兜底)
验证(单进程双框架实跑):/health、/(Flask 兜底)与 /api/v1/site/stats(FastAPI)
均 200;Server 头为 NeteaseUrl;ruff/pyright 干净。
|
2026-09-26 00:35:02 +08:00 |
|
snowy-yang
|
0f2e72db44
|
feat(db-async): 站点数据异步读写仓储 + FastAPI 生命周期
- core/repo.py:经由 core.db(aiosqlite) 读写 auth_system 的**同一份** site.db
(settings/announcements/users 只读/ledger 只读),字段口径对齐 auth_system;
读并发、写串行、WAL 读写同时进行
- web/routers/site.py:/api/v1/site/announcements、/api/v1/site/stats(异步多表并发读)
- web/app.py:lifespan 关闭时释放 aiohttp 连接池(含按 loop 隔离的 session)
写路径说明:迁移期写操作仍归 auth_system 的 _DB_LOCK 事务口径,避免两套写
路径互相踩踏;本层先只做读。
验证:pytest 22/22(新增 6 项含「异步读 + 同步写者并发 WAL」);
/api/v1/site/* 实测读到共享库(3 用户/1 管理员);ruff/pyright 干净。
|
2026-09-26 00:34:11 +08:00 |
|
snowy-yang
|
ce7ea66d6a
|
refactor(async): music_api 异步化 + 全项目 lint 收敛
music_api.py:
- 22 个方法改 async(HTTPClient.post_request*/NeteaseAPI.*/QRLoginManager.*),
33 处 requests.* → await core.aiohttp_compat.*
- 16 个模块级同步包装改走 run_sync 桥(main.py 等 99 个同步调用点零改动)
- async 内 time.sleep → asyncio.sleep(修 7 处阻塞事件循环)
musica_downloader/qr_login:
- 单曲三请求 ThreadPoolExecutor → asyncio.gather(语义等价,失败处理保留)
- interactive_login 改 async,CLI 入口用 asyncio.run
真实缺陷修复(lint 发现):
- podcast_api._to_int 未定义(2 处调用会 NameError)→ 补安全转换 helper
- main.py VALID_QUALITY_LEVELS 重复定义(覆盖 core.config 导入值)→ 移除
- 移除死代码变量(salt×2/device_id×3/file_path/zipped_files)
lint 收敛:524→0(ruff All checks passed);pyright 0 errors。
pyproject 记录「有意保留」项(B904/RUF012/S305 上游协议等)及理由。
验证:冒烟 10/10;pytest 16/16;真实上游 get_toplist 异步直调与桥接均得 63 条。
|
2026-09-26 00:32:45 +08:00 |
|
snowy-yang
|
4962542e78
|
feat(core): requests 兼容异步垫片 + 同步/异步桥
- core/aiohttp_compat.py:以 requests 同形 API(post/get + Response.json/text/
status_code/raise_for_status + HTTPError/RequestException/Timeout)实现异步请求,
使既有 requests 风格代码可「加 await + async def」最小改动迁移;4xx/5xx 不抛、
仅 raise_for_status() 抛(与 requests 语义一致,保住风控判断逻辑)
- core/async_bridge.py:run_sync(coro) 经常驻后台事件循环线程执行协程并阻塞等待,
供迁移期同步调用方(遗留 Flask 路由/线程池)复用异步实现
验证:pytest 16/16;ruff All checks passed;pyright 0 errors。
|
2026-09-26 00:27:47 +08:00 |
|
snowy-yang
|
9f56f68b66
|
chore(async-infra): 异步基础层 + 工具链 + 清理
异步基础层(core/):
- db.py aiosqlite + WAL:读路径无锁并发、写路径 asyncio.Lock 串行,
busy_timeout 自旋——实现「读写同时进行」(旧实现用 RLock 全读串行)
- http_client.py aiohttp 连接池 + 有界重试(5xx 重试 / 4xx 不重试)+
响应体上限 + 流式接口;按事件循环隔离 session(支持迁移期双 loop)
工具链:
- pyproject.toml ruff(E/F/W/I/B/UP/C4/ASYNC/S/RUF) + pyright(basic) + pytest(asyncio auto)
- requirements-fastapi.txt FastAPI/aiohttp/aiosqlite/aiofiles 等目标栈
- requirements-dev.txt 开发依赖(pytest/ruff/pyright)
清理:删除无用脚本 _csscheck.py 与产物标记 _css_ok.txt/_pkg_ok.txt,同步 .gitignore
验证:pytest 10/10(含「写 200 条同时 4 读协程零异常」并发用例);
ruff All checks passed;pyright 0 errors。
|
2026-09-26 00:24:00 +08:00 |
|
snowy-yang
|
41356e89c4
|
feat(web): FastAPI 脚手架(strangler 迁移起点)
新增 web/ 包,与 Flask 应用并存、共享 core/:
- web/app.py create_app 工厂;已迁移路由挂 /api/v1,可挂遗留 Flask 兜底
- web/main.py uvicorn 入口(sys.path 补项目根)
- web/response.py 响应工具,与 Flask APIResponse 的 JSON 契约逐字段对齐
- web/routers/system.py 迁移样板:/api/v1/health、/api/v1/api/info
- requirements-fastapi.txt 迁移目标依赖(不影响当前生产 Flask 部署)
验证:
- 奇偶校验(.zcode/refactor/parity.py)Flask /health vs FastAPI /api/v1/health、
/api/info vs /api/v1/api/info —— 状态码/顶层键/响应体全一致
- uvicorn 真实启动 200;Flask fallback 挂载下 /api/v1/* 走 FastAPI、/* 走 Flask 同为 200
当前仅样板端点;生产服务器切换与状态统一见后续迁移阶段。
|
2026-09-26 00:17:56 +08:00 |
|
snowy-yang
|
c2a8da3e43
|
refactor(core): 抽出框架无关核心层 core/,为 FastAPI 迁移铺路
第一步模块拆分(纯搬运,零行为变化):
- 新增 core/ 包:config(常量/配置) + state(运行时状态) + utils(纯函数) + security(音频签名)
- main.py 改为从 core 导入并以原下划线名再导出,250+ 既有调用点零改动
关键设计:
- 状态全部原地修改,import 即共享同一对象(21 项身份断言 + 10 项冒烟全绿)
- 音频签名密钥由启动时 _init_audio_secret(app.secret_key) 注入,
解除 core.security 对 Flask app 的依赖,新老应用可互认 token
验证:py_compile 通过;冒烟 10/10 PASS;对象身份 22/22 PASS;
waitress 真实启动 /health 200 version=2.4.0。main.py 减 229 行。
|
2026-09-26 00:16:04 +08:00 |
|
snowy-yang
|
d209af847a
|
chore: 基线快照 —— 纳入 git 版本控制(站点 v2.4.0 / Flask 单体)
在框架迁移与模块拆分前保存当前可运行状态:
- 后端:main.py/auth_routes.py/auth_system.py/music_api.py/music_downloader.py/podcast_api.py/qr_login.py
- 前端:templates/index.html + static/site_auth.js
- 部署:Dockerfile/docker-compose.yml/entrypoint.sh/requirements.txt
敏感数据(data/、cookie.txt)与内部文档(交接文档/新会话引导)按 .gitignore 排除。
|
2026-09-26 00:13:05 +08:00 |
|