跳转到内容

后端报告

分析对象:E:\AI\antigravity\stady-code\final\Supplement_Installer_x64.msi 分析日期:2026-06-22


Supplement_Installer_x64.msi 发给朋友安装后:

  • 桌面端 Supplement.exe 能正常打开窗口;
  • 但所有功能(生成题目、判题、教练对话、设置等)全部失败,表现为前端连不上后端
  • 即使你本机双击 final\start.bat 能正常使用,朋友的机器上依然不行。

项目是 Tauri(Rust + React)桌面壳 + FastAPI(Python)后端 的组合。需要先搞清楚”谁启动后端、后端在哪个端口、前端去连哪个端口”这三件事。

环节文件行为
端口分配src-tauri/src/lib.rs:30Tauri 启动时 TcpListener::bind("127.0.0.1:0"),让操作系统随机分配一个空闲端口 X
端口传递src-tauri/src/lib.rs:36-37http://127.0.0.1:X 写入 BackendUrlState
启动后端src-tauri/src/lib.rs:67-71spawn backend_server.exe --port X(或 python main.py --port X
前端取端口src/services/api.ts:3-13invoke("get_backend_url") 从 Rust 拿到 X,赋给 BASE_URL

这是一个”动态端口”设计:后端不写死 8000,由 Tauri 决定端口,再传给后端和前端。

2.2 安装包版(final/)的实际实现

Section titled “2.2 安装包版(final/)的实际实现”

final/ 放弃了 Tauri 自启动后端,改用一个外挂脚本:

final/start.bat:11

start /b "" "backend\.venv\Scripts\python.exe" "backend\main.py"
  • 后端由 bat 拉起,main.py 写死监听 127.0.0.1:8000backend/main.py:40),不解析 --port 参数

Supplement.exe 仍然是从 first/ 原样编译出来的,内部还是那套动态端口逻辑

于是产生了下面的严重不匹配。


三、根本原因(按严重程度排序)

Section titled “三、根本原因(按严重程度排序)”

🔴 原因 1:端口不匹配 —— 这是最核心的问题

Section titled “🔴 原因 1:端口不匹配 —— 这是最核心的问题”

在朋友的机器上,start.bat 如果被运行(或者你交付时根本没让朋友用 bat,而是直接双击 Supplement.exe),发生的事是:

  1. Supplement.exe 启动 → lib.rs 的 setup 钩子执行;
  2. TcpListener::bind("127.0.0.1:0") 拿到一个随机端口,例如 53712
  3. BackendUrlState 被写入 http://127.0.0.1:53712
  4. 前端 ensureBackendUrl() 调用 invoke("get_backend_url"),拿到 53712,于是 BASE_URL = http://127.0.0.1:53712
  5. 但真正的后端(无论是 bat 启动的,还是 Tauri 想启动的)要么在 8000,要么根本没启动
  6. 结果:前端疯狂请求 127.0.0.1:53712,永远连不上 → 表现为”无法连接”。

注意:浏览器探测端口的逻辑(api.ts:16-36不会生效,因为这段代码被包在”非 Tauri 环境”分支里,Tauri 桌面端会先走 invoke 分支提前 return。

🔴 原因 2:Tauri 在发布模式下根本没启动后端

Section titled “🔴 原因 2:Tauri 在发布模式下根本没启动后端”

lib.rs:54-65 的 release 分支,它会去找两个路径之一:

  • resource_dir/backend_server.exe
  • resource_dir/backend/dist/backend_server.exe

tauri.conf.json:27-29resources 只声明了:

"resources": ["../backend/dist/backend_server.exe"]

final/ 目录里根本没有 backend_server.exe(也没有 backend/dist/),只有 Python 源码和 .venv。这意味着:

  • 通过 MSI 安装后,资源目录里也不会有 backend_server.exe
  • lib.rs 的第一个分支 backend_exe = None
  • fallback 分支去 resource_dir/backend/.venv/Scripts/python.exe 找解释器——MSI 里同样没有打进去;
  • 最终 app.manage(BackendProcess(Mutex::new(None)))Tauri 自己一个后端都没拉起来

🟠 原因 3:main.py 不支持 --port 参数

Section titled “🟠 原因 3:main.py 不支持 --port 参数”

即使将来把 backend_server.exe 正确打包,lib.rs 是这样调用的:

cmd.arg("--port").arg(port.to_string());

backend/main.py:39-40 是:

if __name__ == "__main__":
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=not is_prod)

完全没解析 --port,永远监听 8000。动态端口设计在后端侧是断的

🟡 原因 4:依赖 .venv,朋友机器上几乎不可能用

Section titled “🟡 原因 4:依赖 .venv,朋友机器上几乎不可能用”

start.bat 写死了 backend\.venv\Scripts\python.exe。这个 .venv 是你本机创建的虚拟环境:

  • 路径是绝对的、绑定你本机的 Python 版本;
  • MSI 安装包里也不会包含 final\backend\.venv(它根本没被打包进 resources);
  • 即便拷贝过去,venv 里的 pyvenv.cfg 记录的是你本机 Python 路径,换机器后会失效。

所以 start.bat 在朋友机器上多半直接报”找不到 python.exe”或依赖加载失败。

backend/config.py:11main.py:37

is_prod = "resources" in os.path.abspath(__file__)

这种字符串匹配判断很脆弱——只要最终部署路径里恰好不含 “resources” 子串(比如 MSI 装到 Program Files\supplement\),就会被误判为开发模式,数据目录、reload 行为都会跑偏。


四、为什么”你本机能用,朋友不能用”

Section titled “四、为什么”你本机能用,朋友不能用””
情况你的机器朋友的机器
你怎么启动双击 final/start.bat直接双击 Supplement.exe(MSI 装出来的快捷方式)
后端来源bat 用 .venv 里的 python 起 main.py(8000)Tauri 想起后端,但找不到 exe/venv → 没起
前端连的端口❗仍然是 Tauri 给的随机端口(不匹配,但有时碰巧能通)随机端口,且无后端 → 必然失败
你”觉得能用”可能是因为之前 8000 端口探测逻辑或缓存让你误判完全裸奔

即使你自己用,端口不匹配也是定时炸弹:只是你机器上恰好装着开发环境,Python 后端可能由别的方式起着,掩盖了问题。


五、验证方法(给朋友的机器做复现确认)

Section titled “五、验证方法(给朋友的机器做复现确认)”

让朋友按下面任意一种方式确认:

  1. 打开 Supplement.exe 后,按 F12 或右键检查控制台,看是否有:
    • Failed to fetch / ERR_CONNECTION_REFUSED
    • 控制台里 Detected Supplement Backend on port ... 这条日志——如果没有,就证明浏览器探测没走到
  2. 在朋友机器上 Win+Rcmdnetstat -ano | findstr :8000
    • 如果没有输出 → 后端根本没起(印证原因 2/4);
    • 如果有输出,再看 netstat -ano | findstr LISTENING 里有没有 8000 之外的高位端口被 Supplement.exe 占着。
  3. 浏览器访问 http://127.0.0.1:8000/:返回 {"status":"running",...} 才说明后端在跑。

六、修复建议(三选一,按推荐度排序)

Section titled “六、修复建议(三选一,按推荐度排序)”

✅ 方案 A(推荐,改动最小、最稳):让前端固定连 8000,并强制用 start.bat 启动

Section titled “✅ 方案 A(推荐,改动最小、最稳):让前端固定连 8000,并强制用 start.bat 启动”

适用场景:你不想重新打包 Rust/PyInstaller,只想让朋友能用。

  1. src/services/api.ts:把 invoke 分支去掉或兜底,直接 BASE_URL = "http://127.0.0.1:8000";保留端口探测作为浏览器模式备用。
  2. src-tauri/src/lib.rs:release 模式下跳过自己启动后端的逻辑(因为交给 bat),BackendUrlState 固定写入 http://127.0.0.1:8000
  3. 重新打包 exe,并把 final\backend\ 连同 .venv 一起压缩给朋友——但要让朋友先双击 start.bat,再开 Supplement.exe(或把 MSI 的快捷方式指向 bat)。
  4. 注意 .venv 换机器问题(见方案 C)。

✅ 方案 B(推荐,正规做法):把后端打成单文件 exe 作为 Tauri 资源

Section titled “✅ 方案 B(推荐,正规做法):把后端打成单文件 exe 作为 Tauri 资源”
  1. 用 PyInstaller 把 backend/main.py 打成 backend_server.exe
    pyinstaller --onefile --name backend_server --paths backend main.py
  2. 修复 main.py--port 解析(原因 3):
    import argparse
    p = argparse.ArgumentParser()
    p.add_argument("--port", type=int, default=8000)
    args = p.parse_args()
    uvicorn.run("main:app", host="127.0.0.1", port=args.port, reload=not is_prod)
  3. 确保 tauri.conf.jsonresources 指向正确的 backend_server.exe 路径,重新 pnpm tauri build
  4. 这样 MSI 装好后,Supplement.exe 自己会拉起后端,端口动态分配且前后端一致——朋友直接装 MSI 就能用,不再需要 start.bat

⚠️ 方案 C(临时):用便携 Python 替代 .venv

Section titled “⚠️ 方案 C(临时):用便携 Python 替代 .venv”

如果坚持用 Python 源码 + bat:

  • 不要用 .venv,改用随包附带的嵌入式 Python(python-embed),路径写死成 runtime\python.exe
  • start.bat 里改成 runtime\python.exe -m pip install -r backend\requirements.txt(首次运行)+ 启动;
  • 同时按方案 A 修掉端口不匹配。

七、优先级清单(如果要我动手,建议按此顺序)

Section titled “七、优先级清单(如果要我动手,建议按此顺序)”
  1. main.py 支持 --port(5 分钟,所有方案都需要);
  2. api.tsensureBackendUrl,把 Tauri 模式下的端口也兜底成 8000(10 分钟);
  3. 决定走方案 A 还是 B
    • 想快速给朋友能用 → 方案 A + 便携 Python;
    • 想做成正规产品后续还能更新 → 方案 B(PyInstaller 打包)。
  4. 顺手修掉 is_prod 字符串判断(原因 5),改成基于环境变量或打包标记。

不是”后端没做好”,而是”后端的启动方式和前端期望的端口对不上”:Tauri 前端去连一个操作系统随机分配的端口,而后端要么没被启动、要么写死在 8000,两者永远碰不到一起。修复的关键是让”端口”在三方(Tauri 启动器、后端进程、前端 fetch)之间保持一致。