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

当前仅样板端点;生产服务器切换与状态统一见后续迁移阶段。
This commit is contained in:
snowy-yang committed 2026-09-26 00:17:56 +08:00
1 parent c2a8da3e43
commit 41356e89c4
7 files changed
+198

No files matched your search

+44
View File
@@ -0,0 +1,44 @@
# -*- 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 fastapi import FastAPI
from core.config import APP_VERSION
from web.routers import system as system_router
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',
)
# 已迁移路由:统一挂 /api/v1(迁移期与 Flask 根路径并存,便于对比)
app.include_router(system_router.router, prefix='/api/v1', tags=['system'])
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))