后端报告
分析对象:
E:\AI\antigravity\stady-code\final\Supplement_Installer_x64.msi分析日期:2026-06-22
把 Supplement_Installer_x64.msi 发给朋友安装后:
- 桌面端
Supplement.exe能正常打开窗口; - 但所有功能(生成题目、判题、教练对话、设置等)全部失败,表现为前端连不上后端;
- 即使你本机双击
final\start.bat能正常使用,朋友的机器上依然不行。
二、架构梳理
Section titled “二、架构梳理”项目是 Tauri(Rust + React)桌面壳 + FastAPI(Python)后端 的组合。需要先搞清楚”谁启动后端、后端在哪个端口、前端去连哪个端口”这三件事。
2.1 源码版(first/)的预期设计
Section titled “2.1 源码版(first/)的预期设计”| 环节 | 文件 | 行为 |
|---|---|---|
| 端口分配 | src-tauri/src/lib.rs:30 | Tauri 启动时 TcpListener::bind("127.0.0.1:0"),让操作系统随机分配一个空闲端口 X |
| 端口传递 | src-tauri/src/lib.rs:36-37 | 把 http://127.0.0.1:X 写入 BackendUrlState |
| 启动后端 | src-tauri/src/lib.rs:67-71 | spawn backend_server.exe --port X(或 python main.py --port X) |
| 前端取端口 | src/services/api.ts:3-13 | invoke("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:8000(backend/main.py:40),不解析--port参数。
而 Supplement.exe 仍然是从 first/ 原样编译出来的,内部还是那套动态端口逻辑。
于是产生了下面的严重不匹配。
三、根本原因(按严重程度排序)
Section titled “三、根本原因(按严重程度排序)”🔴 原因 1:端口不匹配 —— 这是最核心的问题
Section titled “🔴 原因 1:端口不匹配 —— 这是最核心的问题”在朋友的机器上,start.bat 如果被运行(或者你交付时根本没让朋友用 bat,而是直接双击 Supplement.exe),发生的事是:
Supplement.exe启动 →lib.rs的 setup 钩子执行;TcpListener::bind("127.0.0.1:0")拿到一个随机端口,例如53712;BackendUrlState被写入http://127.0.0.1:53712;- 前端
ensureBackendUrl()调用invoke("get_backend_url"),拿到53712,于是BASE_URL = http://127.0.0.1:53712; - 但真正的后端(无论是 bat 启动的,还是 Tauri 想启动的)要么在 8000,要么根本没启动;
- 结果:前端疯狂请求
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.exeresource_dir/backend/dist/backend_server.exe
而 tauri.conf.json:27-29 的 resources 只声明了:
"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”或依赖加载失败。
🟡 原因 5:is_prod 判定不可靠
Section titled “🟡 原因 5:is_prod 判定不可靠”backend/config.py:11 和 main.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 “五、验证方法(给朋友的机器做复现确认)”让朋友按下面任意一种方式确认:
- 打开 Supplement.exe 后,按
F12或右键检查控制台,看是否有:Failed to fetch/ERR_CONNECTION_REFUSED;- 控制台里
Detected Supplement Backend on port ...这条日志——如果没有,就证明浏览器探测没走到。
- 在朋友机器上
Win+R→cmd→netstat -ano | findstr :8000:- 如果没有输出 → 后端根本没起(印证原因 2/4);
- 如果有输出,再看
netstat -ano | findstr LISTENING里有没有 8000 之外的高位端口被Supplement.exe占着。
- 浏览器访问
http://127.0.0.1:8000/:返回{"status":"running",...}才说明后端在跑。
六、修复建议(三选一,按推荐度排序)
Section titled “六、修复建议(三选一,按推荐度排序)”✅ 方案 A(推荐,改动最小、最稳):让前端固定连 8000,并强制用 start.bat 启动
Section titled “✅ 方案 A(推荐,改动最小、最稳):让前端固定连 8000,并强制用 start.bat 启动”适用场景:你不想重新打包 Rust/PyInstaller,只想让朋友能用。
- 改
src/services/api.ts:把 invoke 分支去掉或兜底,直接BASE_URL = "http://127.0.0.1:8000";保留端口探测作为浏览器模式备用。 - 改
src-tauri/src/lib.rs:release 模式下跳过自己启动后端的逻辑(因为交给 bat),BackendUrlState固定写入http://127.0.0.1:8000。 - 重新打包 exe,并把
final\backend\连同.venv一起压缩给朋友——但要让朋友先双击start.bat,再开 Supplement.exe(或把 MSI 的快捷方式指向 bat)。 - 注意
.venv换机器问题(见方案 C)。
✅ 方案 B(推荐,正规做法):把后端打成单文件 exe 作为 Tauri 资源
Section titled “✅ 方案 B(推荐,正规做法):把后端打成单文件 exe 作为 Tauri 资源”- 用 PyInstaller 把
backend/main.py打成backend_server.exe:pyinstaller --onefile --name backend_server --paths backend main.py - 修复
main.py的--port解析(原因 3):import argparsep = 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) - 确保
tauri.conf.json的resources指向正确的backend_server.exe路径,重新pnpm tauri build。 - 这样 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 “七、优先级清单(如果要我动手,建议按此顺序)”- 改
main.py支持--port(5 分钟,所有方案都需要); - 改
api.ts的ensureBackendUrl,把 Tauri 模式下的端口也兜底成 8000(10 分钟); - 决定走方案 A 还是 B:
- 想快速给朋友能用 → 方案 A + 便携 Python;
- 想做成正规产品后续还能更新 → 方案 B(PyInstaller 打包)。
- 顺手修掉
is_prod字符串判断(原因 5),改成基于环境变量或打包标记。
八、一句话结论
Section titled “八、一句话结论”不是”后端没做好”,而是”后端的启动方式和前端期望的端口对不上”:Tauri 前端去连一个操作系统随机分配的端口,而后端要么没被启动、要么写死在 8000,两者永远碰不到一起。修复的关键是让”端口”在三方(Tauri 启动器、后端进程、前端 fetch)之间保持一致。