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 22f4d77abd feat(routes): 迁移管理员扫码/Cookie/操作记录端点
- web/routers/admin.py:admin/qr/create、admin/qr/check、admin/cookie/status、
  admin/cookie/delete、admin/logs
- 管理员闸用本地实现,文案与「无 error_code」包体与 Flask _verify_admin_password
  逐字对齐(与 auth_system.admin_required 的 forbidden 字段不同)
- QRLoginManager await;文件类操作(save_cookie/logout/_get_cookie_info) 走 to_thread
- Cookie 变更后同步清 VIP 与全局 MUSIC_U 缓存

契约奇偶校验:36 项全 OK(新增 admin:logs、admin:cookie_status、非管理员 403);
ruff All checks passed;pyright 0 errors;冒烟 10/10。
2026-09-26 09:59:04 +08:00

116 lines
4.6 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 pathlib import Path
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
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 admin as admin_router
from web.routers import auth as auth_router
from web.routers import auth_status as auth_status_router
from web.routers import delivery as delivery_router
from web.routers import index as index_router
from web.routers import music as music_router
from web.routers import podcast as podcast_router
from web.routers import site as site_router
from web.routers import stream as stream_router
from web.routers import system as system_router
from web.routers import tasks as tasks_router
from web.routers import user as user_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)
# 静态资源(/static/site_auth.js 等)——与 Flask static_folder 同目录
_static_dir = Path(__file__).resolve().parent.parent / 'static'
if _static_dir.is_dir():
app.mount('/static', StaticFiles(directory=str(_static_dir)), name='static')
# 已迁移路由:统一挂 /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'])
app.include_router(podcast_router.router, prefix='/api/v1', tags=['podcast'])
app.include_router(tasks_router.router, prefix='/api/v1', tags=['tasks'])
app.include_router(delivery_router.router, prefix='/api/v1', tags=['delivery'])
app.include_router(stream_router.router, prefix='/api/v1', tags=['stream'])
app.include_router(auth_status_router.router, prefix='/api/v1', tags=['auth'])
app.include_router(auth_router.router, prefix='/api/v1', tags=['auth'])
app.include_router(user_router.router, prefix='/api/v1', tags=['user'])
app.include_router(admin_router.router, prefix='/api/v1', tags=['admin'])
# 首页与静态资源(迁移完成形态;fallback 模式下同路径也能由 Flask 提供)
app.include_router(index_router.router, tags=['index'])
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))