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 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

70 lines
2.5 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 web.errors import install_error_handlers
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 create_app(serve_flask_fallback: bool = False) -> FastAPI:
"""创建 FastAPI 应用。"""
app = FastAPI(
title='云村小助手API服务',
version=APP_VERSION,
docs_url='/api/v1/docs',
redoc_url=None,
openapi_url='/api/v1/openapi.json',
lifespan=_lifespan,
)
# 已迁移路由:统一挂 /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'])
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))