frontend-independent-delivery-plan.md 8.33 KB

前端独立交付方案

1. 文档目的

本文用于回答两个问题:

  1. 当前 BettaFish 是否已经具备“前端独立于 Flask 交付”的基础条件。
  2. 如果要把后端最终收口为 API-only,推荐采用什么交付拓扑,以及应该按什么顺序推进。

本文面向项目维护者和后续继续做结构优化的开发者,重点是给出可执行的迁移方案,而不是只停留在方向判断。

2. 当前现状

2.1 已经完成的能力

  • 本地开发主链路已经切换为 python -m scripts.dev.start_local 默认拉起 Vite HMR + Flask API
  • 前端源码已经完全迁移到 apps/web_ui/
  • 后端根入口已经收口到 python -m apps.web_api
  • 前端当前使用 createWebHashHistory,因此独立静态托管时不依赖服务端做 history fallback。
  • Vite 开发模式已经通过 /api/socket.io 代理对接 http://127.0.0.1:5000

2.2 当前仍然耦合的地方

  • 生产 / 部署兼容构建仍输出到 static/frontend/
  • apps/web_api/app.py 的根路由仍负责:
    • 开发态重定向到 BETTAFISH_FRONTEND_DEV_URL
    • 非开发态返回 static/frontend/index.html
    • 构建产物缺失时返回 templates/index.html
  • Docker 当前仍通过后端镜像把前端产物复制到 /app/static/frontend,然后直接启动 python -m apps.web_api

2.3 当前已经暴露出的独立交付阻塞点

  • 前端 HTTP 请求全部使用相对路径 /api/...
  • 报告预览和下载也使用相对路径 /api/report/...
  • 引擎工作台 iframe 不是走相对路径,而是按当前页面 hostname 拼接 :8501 / :8502 / :8503
  • 后端未发现通用 HTTP CORS 配置;当前只看到 SocketIO(..., cors_allowed_origins="*")
  • Streamlit 当前按端口直接启动,未配置 --server.baseUrlPath,因此还不能直接无改动地挂到 /engines/* 这样的代理子路径下。
  • apps/web_ui/vite.config.ts 的构建 base 仍固定为 /static/frontend/,这适合当前 Flask 托管模式,不适合作为长期的独立前端交付默认值。

3. 方案选项

方案 A:继续由 Flask 托管 static/frontend/

这是当前基线方案,不是真正的“前端独立交付”。

优点:

  • 改动最少。
  • 当前 Docker 已经可用。
  • 不需要新增代理层。

缺点:

  • 后端无法真正收口为 API-only。
  • 前端构建产物仍耦合在后端镜像里。
  • 首页与部署链路继续依赖 Flask 根路由逻辑。

结论:

  • 只能作为过渡基线,不适合作为最终目标。

方案 B:同源反向代理的独立前端服务

推荐方案。前端单独构建并由静态服务承载,但浏览器仍然只访问一个同源入口。

建议拓扑:

flowchart LR
    U["Browser"] --> FE["Frontend Proxy / Static Service"]
    FE -->|" /api , /socket.io "| API["Flask API :5000"]
    FE -->|" /engines/insight "| I["Streamlit Insight :8501"]
    FE -->|" /engines/media "| M["Streamlit Media :8502"]
    FE -->|" /engines/query "| Q["Streamlit Query :8503"]
    API --> DB["PostgreSQL"]

优点:

  • 浏览器仍保持同源访问,前端现有大量相对路径 /api/... 可以基本保留。
  • 报告下载、报告预览 iframe 和 API 调用都可以继续走同源代理。
  • 后端可以逐步去掉首页 HTML 托管职责。
  • 比“跨域直连 API”改动更小、风险更低。

缺点:

  • 需要新增前端静态服务和代理配置。
  • Streamlit 需要补齐路径代理能力,不能只靠当前 :8501/:8502/:8503 端口拼接。

结论:

  • 这是当前最适合落地的主方案。

方案 C:前端独立域名 / 独立源,直接跨域访问后端

例如前端静态站点部署到 CDN、Vercel 或单独域名,直接调用 https://api.example.com

优点:

  • 前后端边界最彻底。
  • 后端可以更纯粹地只提供 API。

缺点:

  • 需要系统性引入 API Base URL、报告预览 / 下载 URL、引擎 URL 等运行时配置。
  • 需要给 Flask API 增加可靠的 HTTP CORS 策略。
  • 需要为报告预览 iframe、下载、新开窗口、引擎 iframe 重新设计 URL 生成逻辑。
  • 发布面更大,回归风险更高。

结论:

  • 适合作为第二阶段能力,不适合当前直接切换。

4. 推荐结论

当前推荐采用:

  1. 短期继续保留现有 static/frontend/ 托管作为兼容链路。
  2. 中期引入“同源反向代理的独立前端服务”。
  3. 在新链路稳定后,再删除 Flask 根路由中的前端 HTML 托管逻辑。
  4. 若未来确实需要 CDN / 独立域名前端,再在此基础上推进跨域版本。

换句话说,当前最优路径不是“直接删掉 Flask 托管”,而是先把前端真正迁移到独立服务,再让后端自然退化成 API-only。

5. 推荐实施顺序

阶段 1:前端运行时 URL 抽象

目标:让前端不再把“当前同源 + 端口拼接”写死在各组件里。

建议动作:

  • 为前端新增统一 URL 配置层,例如:
    • VITE_PUBLIC_BASE_PATH
    • VITE_API_BASE_URL
    • VITE_SOCKET_BASE_URL
    • VITE_REPORT_BASE_URL
    • VITE_ENGINE_BASE_URL
  • 把当前各处硬编码的 /api/...、报告下载 URL、报告预览 URL、引擎 URL 拼接收敛到一个统一模块。
  • 保持默认值仍兼容本地开发:
    • VITE_PUBLIC_BASE_PATH=/
    • VITE_API_BASE_URL= 空值时回退同源 /api
    • VITE_ENGINE_BASE_URL= 空值时回退当前行为

注意:

  • 这一阶段不需要立即切部署,只要先把 URL 生成逻辑从页面代码里抽离出来。

阶段 2:引擎 iframe 去端口耦合

目标:让前端不再依赖浏览器直接访问 :8501/:8502/:8503

建议动作:

  • 为三个 Streamlit 应用增加可配置的 baseUrlPath
  • 代理层统一暴露:
    • /engines/insight
    • /engines/media
    • /engines/query
  • 把前端 buildEngineUrl() 从“当前 hostname + port”改成“统一代理路径”。

这是当前 API-only 收口前的关键前置条件,因为不处理这一层,前端即使独立交付,工作台仍然被后端主机和端口强耦合。

阶段 3:建立独立前端服务

目标:让前端构建产物由独立静态服务承载,而不是继续由 Flask 返回首页。

建议动作:

  • 新增独立前端镜像,例如:
    • infra/frontend/Dockerfile
    • infra/frontend/nginx.confinfra/frontend/Caddyfile
  • 前端服务负责:
    • 承载静态页面
    • 反向代理 /api
    • 反向代理 /socket.io
    • 反向代理 /engines/*
  • Docker Compose 从当前的 bettafish + db 两服务,演进为:
    • frontend
    • api
    • db
    • 必要时保留 streamlit 子服务或继续由 api 管控子进程

阶段 4:收口 Flask 根路由

只有在以下条件全部满足后,才建议真正删除 Flask 的前端 HTML 托管:

  • 前端已经有独立交付入口。
  • /api/socket.io、报告下载与预览已经在新链路下验证通过。
  • 三个引擎 iframe 已经不再依赖裸端口访问。
  • Docker / 本地部署文档已经切到新入口。
  • 旧的 static/frontend/ 链路有明确回退窗口或已经确认无需保留。

届时可以把 apps/web_api/app.py 的根路由收口为:

  • API 健康检查
  • 404 / 说明页
  • 或明确跳转到新的前端入口

6. 当前最值得优先做的具体任务

P0

  • 收口前端 URL 配置层,替换散落在前端中的相对路径与端口拼接。
  • 为 Streamlit 引擎评估并补齐 baseUrlPath 方案。

P1

  • 新增独立前端静态服务镜像与代理配置。
  • 调整 Docker Compose 为前端 / API 分离拓扑。

P2

  • 收口 Flask 根路由。
  • 移除后端镜像中对 static/frontend/ 的复制依赖。

7. 不建议现在立刻做的事

  • 不建议现在直接删除 apps/web_api/app.py 返回 static/frontend/index.html 的分支。
  • 不建议直接切到“跨域前端直连 API”的方案。
  • 不建议在没有处理 Streamlit iframe 之前就宣布后端已经 API-only。

8. 完成标准

当以下条件满足时,可以认为“前端独立交付”已经完成:

  • 本地开发继续保持 Vite HMR + Flask API 不回退。
  • 部署环境中的首页不再由 Flask 返回 HTML。
  • 前端构建产物不再复制进后端镜像。
  • 报告预览、下载、Socket.IO 和引擎工作台均在新拓扑下正常工作。
  • apps/web_api/app.py 不再承担前端页面托管职责。