LyEdu 后端 exe 打包技术分享
本文档总结 LyEdu Python API(FastAPI)使用 PyInstaller 打包为独立可执行文件的完整过程、遇到的问题及解决方案。
一、打包步骤
1.1 环境准备
# 安装 PyInstaller
p# windows
.\.venv\Scripts\pip.exe install pyinstaller
# Linux
.\.venv\bin\pip install pyinstaller
# 确保项目依赖已安装
cd lyedu-api-python
.\.venv\Scripts\pip.exe install -r requirements.txt1.2 打包命令
推荐使用 spec 文件(便于维护 datas、icon 等配置):
cd lyedu-api-python
python -m PyInstaller lyedu_backend.spec或首次生成 spec 后打包:
python -m PyInstaller --onefile --name lyedu_backend main.py1.3 输出产物
dist/lyedu_backend.exe:单文件可执行程序build/:构建缓存(可删除)lyedu_backend.spec:打包配置
二、核心实现要点
2.1 程序入口
必须在 main.py 末尾添加 uvicorn 启动逻辑,否则 exe 加载完路由后立即退出(闪退):
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host=config.HOST, port=config.PORT)2.2 配置策略分离
| 场景 | 配置来源 | 说明 |
|---|---|---|
| 打包 exe | 仅 ~/.lyedu/conf/config.ini | 无则生成模板并提示后退出 |
| 开发环境 | .env / .env.dev | 不使用 config.ini |
打包时通过 sys.frozen 判断,使用 sys.executable 所在目录解析路径(exe 所在目录),而非 __file__(临时解压目录)。
2.3 ENV 环境变量
- 开发默认
ENV=dev - 生产命令行启动需显式添加:
ENV=prod lyedu_backend.exe(Windows:set ENV=prod && lyedu_backend.exe)
2.4 打包数据文件(datas)
lyedu_backend.spec 中需将 alembic 目录和 alembic.ini 一起打包:
datas=[
('alembic.ini', '.'),
('alembic', 'alembic'),
],2.5 Alembic 迁移在 exe 中的使用
- 打包后 alembic 解压在
sys._MEIPASS - 每次启动将 alembic 覆盖复制到
~/.lyedu/alembic,便于用户统一管理,且支持 v2、v3 等新迁移随 exe 更新 - 运行时通过
Config.set_main_option("script_location", str(script_dir))使用绝对路径,避免 cwd 不同导致找不到目录
2.6 exe 图标
在 spec 的 EXE 中设置:icon='favicon.ico',图标文件需与 spec 同目录。
三、问题与解决方案
3.1 闪退(双击后窗口一闪而过)
原因:无 if __name__ == "__main__" 启动 uvicorn,进程加载完即退出。
解决:在 main.py 末尾添加上面的 uvicorn 启动代码。
3.2 配置/错误信息一闪而过,用户无法阅读
原因:控制台输出后立即退出。
解决:
- Windows:使用
ctypes.windll.user32.MessageBoxW弹出消息框,必须点击确定才关闭 - 配置缺失、MySQL/Redis 连接失败、启动失败等场景均弹窗提示
3.3 使用错误配置仍能启动
原因:未在启动前验证 MySQL/Redis 连接。
解决:在 lyedu_config 中增加 test_mysql_connection()、test_redis_connection(),启动前校验,失败则弹窗并退出。
3.4 Redis 连接失败(connection refused)
原因:
- Docker 中 Redis 默认只监听
127.0.0.1,宿主机通过端口映射连接时被拒绝 localhost可能解析为 IPv6::1,而 Redis 仅监听 IPv4
解决:
- compose 中增加:
command: redis-server --bind 0.0.0.0 ... - 代码中将
localhost替换为127.0.0.1
3.5 redis-py 报错:unexpected keyword argument 'connect_timeout'
原因:redis-py 使用 socket_connect_timeout 而非 connect_timeout。
解决:将所有 Redis 客户端的 connect_timeout 改为 socket_connect_timeout。
3.6 Redis 7 需要默认用户名
原因:Redis 6+ ACL 要求显式提供用户名。
解决:在配置中增加 REDIS_USERNAME=default,连接时传入 username 参数。
3.7 Alembic 报错:Path doesn't exist: alembic
原因:alembic.ini 的 script_location = alembic 为相对路径,exe 运行时 cwd 可能是 exe 所在目录或用户目录,导致解析失败。
解决:调用 Config.set_main_option("script_location", str(script_dir)),传入 alembic 的绝对路径。
3.8 数据库迁移版本(v2、v3)如何随 exe 更新?
解决:每次启动时从 _MEIPASS 覆盖复制 alembic 到 ~/.lyedu/alembic,保证用户目录与当前 exe 内的迁移脚本一致。
四、用户目录结构
打包后用户需在 ~/.lyedu/ 下维护配置与 alembic:
~/.lyedu/
├── conf/
│ ├── config.ini # 实际配置(必须)
│ └── config.ini.template # 模板(自动生成)
├── alembic/
│ ├── env.py
│ ├── script.py.mako
│ └── versions/
│ ├── v1_init_schema.py
│ └── ... # v2、v3 等由 exe 覆盖更新
└── alembic.ini4.1 config.ini 示例(与 compose-mysql-redis.yml 一致)
[mysql]
host = 127.0.0.1
port = 3306
user = root
password = lyedu123456
database = lyedu
charset = utf8mb4
[redis]
host = 127.0.0.1
user = default
port = 6379
db = 0
password = lyedu123456五、Redis Docker 相关
- 使用 ACL 时,
redis-acl.conf首行必须是有效的user行,不能以注释开头 - 推荐 ACL 格式:
user default on >lyedu123456 ~* &* +@all - compose 中需加
--bind 0.0.0.0以便宿主机连接
六、打包检查清单
- [ ]
main.py有if __name__ == "__main__"且启动 uvicorn - [ ]
config.py中打包分支使用sys.frozen、sys.executable - [ ]
lyedu_backend.spec中datas包含 alembic、alembic.ini - [ ] Redis 使用
socket_connect_timeout、username - [ ] 错误场景有 MessageBox 或 pause,避免闪退
- [ ] 启动前校验 MySQL、Redis 连接
七、跨平台说明
- Windows:使用 MessageBox 展示错误
- macOS / Linux:使用
input("按回车键退出...")暂停
sys.frozen、sys._MEIPASS 在 PyInstaller 打包后可用,开发环境不存在这些属性。
