Files
FinDataHub/Scripts
FinDataHub 3de1d78863 refactor(backend): 后端装配收敛到 create_app,根治双入口漂移
## 问题

仓库有两条 FastAPI 启动路径——app/main.py(uvicorn,手册/开发)与
Scripts/bin/newsflash_backend.py(**生产实际入口**),两者各自 new FastAPI 并各写一遍
中间件与路由注册。只改 app/main.py 的装配时,生产**完全空转且不报错**;而
app/database.py 因被 import 反而生效——一半生效一半不生效,最难察觉。

2026-10-27 装机实测:CORS 预检 405、/health 只有 {status, time} 两个字段,
而源码里明明是完整实现。

早先的止血只做到「两处都改」(给 backend 补中间件、health 载荷共用),
漂移风险仍在。本次根治。

## 改动

新增 Services/kuaixun-service/app/factory.py::create_app(),把**必须逐字一致**的
两件事收敛到一处:

1. 中间件顺序(由内到外)exception_catch → inner_middlewares → security_stack → cors
   Starlette 的 add_middleware 是 insert(0),先添加的位于更内层。CORS 必须最外,
   否则异常响应拿不到跨源头、浏览器只报 "blocked by CORS policy"。
2. 基础路由清单(7 个 router)

两个入口现在都只调 create_app(),self-build 的 FastAPI 实例全部删除。
差异只保留「本就该不同」的部分:

| 差异项 | 处理 |
|---|---|
| lifespan | 各自实现(main 等建表 / backend 走 db_pool) |
| backend 独占端点(analysis / source-test / shutdown / SPA catch-all) | 留在 backend |
| ResponseCache / RequestLog | 经 inner_middlewares 传入,**层序仍由 factory 决定** |

app/main.py 顺带清掉变为无用的导入(WebSocket / Request / JSONResponse /
HTTPException / 7 个 router 名)。

## 必须保留的实现细节

_security_module() 先试常规导入、失败再按 __file__ + importlib 加载。原因:
Scripts/bin/common.py(模块)与 kuaixun-service/common/(包)同名,bin 在 sys.path
前部且已被缓存,生产入口下 from common.security import ... 会报
'common' is not a package。实测该场景确实走 fallback,加载到正确的 security.py。

## 顺序验证(实测,非推理)

app.user_middleware 由外到内:

  app.main : CORSMiddleware → SecurityHeaders → ExceptionCatch
  backend  : CORSMiddleware → SecurityHeaders → RequestLog → ResponseCache → ExceptionCatch

与原 backend 的 exception_catch → ResponseCache → RequestLog → security → cors
逐层一致。新版 FastAPI 用 _IncludedRouter 惰性挂载(path 为 None),
故测试断言挂载数量而非 path 集合。

## 防回退锁 + 证伪

tests/test_app_factory.py 8 条,核心是「两个入口源码里不得出现 FastAPI( 与
include_router(」(注释行豁免)。

证伪:把 backend 改回 app = FastAPI(...) 后,
test_backend_entry_does_not_build_app_itself 立即失败:
  E  AssertionError: 生产入口必须经 create_app 装配
恢复后 443 passed,grep FastAPI( 无残留。

## 部署位实测(只改 Python 资源,免全量重建)

  /health        → {"status":"ok","db":{"ready":false,"connected":true,
                    "timebase_drift_seconds":0.537},...}
  OPTIONS 预检   → 200, access-control-allow-origin: http://tauri.localhost
  安全头         → x-content-type-options / x-frame-options / referrer-policy 齐全
  analysis/*     → overview / trend / hourly 全 200(后端专属端点未被破坏)
  news/live,stats→ 200
  RequestLog     → stderr 有 "POST /api/v1/news/notify-internal → 200 (14ms)"

回归:后端 435 → 443 passed。全量 8 套件 PASS(含真实库集成),40.6s。
2026-10-27 13:30:37 +08:00
..