This repository has been archived on 2026-10-02. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
NeteaseUrl/web/app.py
T
snowy-yang 6c3181341b feat(routes): 迁移账号/全局歌单端点 + 鉴权依赖测试
迁移路由(/api/v1/*,与 Flask 同端点逐字段对齐):
- account/vip:admin/user 双路径 + 5 分钟缓存(降级结果 30s),
  VIP 探测单飞锁与 Flask 同源跨栈共享;未登录可访问(同 Flask)
- global/playlists、global/playlist:管理员闸(require_admin,403 对齐)
- web/deps.py 新增 require_admin;_resolve_user_cookies 支持可选用户(匿名审计保底)

测试:46/46(新增 10 项鉴权依赖:有效/管理员/封禁/禁用/顶号/伪造 Cookie、
401/403 闸);ruff All checks passed;pyright 0 errors。

契约奇偶校验:16 项全 OK(新增 account:vip、global:playlists/playlist、
非管理员 403);uvicorn 实跑双栈 account/vip 均 200、未授权歌单 403。
2026-09-26 01:15:22 +08:00

90 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""FastAPI 应用工厂。
设计(strangler 迁移):
- 已迁移的路由挂在 `/api/v1` 前缀下,与遗留 Flask 的同名路由(根路径)并存,
便于逐端点比对响应契约(.zcode/refactor/parity.py)。
- `serve_flask_fallback=True` 时,把未迁移的遗留 Flask 应用整体挂到根路径兜底,
实现「新端点走 FastAPI、其余走 Flask」的单进程双框架运行;迁移完成后删掉该开关。
注意(默认关闭的原因):Flask fallback 会在 FastAPI 进程内 import main,
进而初始化 MusicAPIService / 打开 SQLite —— 与生产既有的 waitress 进程是
两套独立进程语义。生产切换前需按计划把状态与任务模型统一(见迁移计划阶段 C),
当前仅用于开发期联调。
"""
from contextlib import asynccontextmanager
from fastapi import FastAPI
from core.config import APP_VERSION
from core.security import init_secret_key
from web.errors import install_error_handlers
from web.middleware import ResponseHygieneMiddleware
from web.routers import account as account_router
from web.routers import music as music_router
from web.routers import site as site_router
from web.routers import system as system_router
@asynccontextmanager
async def _lifespan(app: FastAPI):
"""应用生命周期:关闭时释放 aiohttp 连接池(含按事件循环隔离的 session)。"""
yield
try:
from core import aiohttp_compat, http_client
await http_client.client.close()
await aiohttp_compat.close()
except Exception:
pass
def _init_audio_secret() -> None:
"""注入音频签名密钥(与 Flask 同源,令两栈 token 互认)。"""
try:
import auth_system
init_secret_key(auth_system.get_secret_key())
except Exception:
pass
def create_app(serve_flask_fallback: bool = False) -> FastAPI:
"""创建 FastAPI 应用。"""
_init_audio_secret()
app = FastAPI(
title='云村小助手API服务',
version=APP_VERSION,
docs_url='/api/v1/docs',
redoc_url=None,
openapi_url='/api/v1/openapi.json',
lifespan=_lifespan,
)
# 响应侧统一处理:安全头 + 音频直链签名保护 + 版本指纹抹除
app.add_middleware(ResponseHygieneMiddleware)
# 已迁移路由:统一挂 /api/v1(迁移期与 Flask 根路径并存,便于对比)
app.include_router(system_router.router, prefix='/api/v1', tags=['system'])
app.include_router(site_router.router, prefix='/api/v1', tags=['site'])
app.include_router(music_router.router, prefix='/api/v1', tags=['music'])
app.include_router(account_router.router, prefix='/api/v1', tags=['account'])
install_error_handlers(app)
if serve_flask_fallback:
_mount_flask_fallback(app)
return app
def _mount_flask_fallback(app: FastAPI) -> None:
"""把遗留 Flask WSGI 应用挂到根路径,兜底未迁移的路由。"""
from starlette.middleware.wsgi import WSGIMiddleware
import main as legacy_flask
app.mount('/', WSGIMiddleware(legacy_flask.app))