版本: v0.4.0 最后更新: 2026-09-24 配套文档: README.md(用户视角)、DEV.md(开发者指南)、AGENTS.md(AI Agent 协作规范)
PHP-Stack 是一个基于 Tauri v2 + Docker 的跨平台 PHP 开发环境可视化管理工具。
核心价值:
- 🎯 可视化配置 — GUI 替代手动编辑
.env和docker-compose.yml - 🌐 站点挂载 — 多站点 Nginx conf + PHP/Nginx 卷映射,支持跨机路径重映射
- 🌐 镜像源加速 — 统一管理 Docker/APT/Composer/NPM 镜像源
- 💾 环境备份恢复 — ZIP 打包 + SHA256 校验 + 恢复前自动回滚包
- 🔧 多版本管理 — PHP/MySQL/Redis/Nginx 多版本共存;清单 + 用户 override/custom
- 🌍 国际化与主题 — 中/英双语、自动/明亮/暗黑三种模式
- 🐳 权限映射 — PUID/PGID 用户映射,解决挂载目录权限问题
- 🔄 关于与更新 — 日志等级/导出、GitHub Releases 自动更新
技术栈:前端 Vue 3 + TypeScript + Tailwind CSS v4;后端 Rust (Tauri v2) + bollard;容器 Docker + Compose。
┌─────────────────────────────────────────────────────────────┐
│ 前端层 (Vue 3) │
├─────────────────────────────────────────────────────────────┤
│ App.vue (主框架: 侧边栏/日志面板/容器状态) │
│ ├── EnvConfigPage.vue (环境配置 + 多站点 + 版本下拉) │
│ ├── MirrorPanel.vue (镜像源管理) │
│ ├── BackupPage.vue (环境备份) │
│ ├── RestorePage.vue (环境恢复向导) │
│ ├── SoftwareSettings.vue (版本映射/用户覆盖) │
│ ├── AboutPage.vue (关于/日志等级/导出) │
│ └── SettingsPage.vue / MigrationPage.vue / ... │
│ api/ (命令封装: client/envConfig/mirror/backup/workspace/… )│
│ composables/ (useToast/useConfirmDialog/useDocker) │
│ types/ (与 Rust 结构体对应的 TS 类型) │
└──────────────────────┬──────────────────────────────────────┘
│ Tauri IPC (invoke / event)
┌──────────────────────▼──────────────────────────────────────┐
│ 后端层 (Rust/Tauri) │
├─────────────────────────────────────────────────────────────┤
│ commands/ (API 入口,按业务域拆分) │
│ ├── docker.rs / env_config.rs / mirror.rs │
│ ├── backup.rs / workspace.rs / app.rs │
│ └── paths.rs (路径解析单一模块) / mod.rs │
├─────────────────────────────────────────────────────────────┤
│ engine/ (核心业务引擎) │
│ ├── config_generator.rs (配置生成 + 模板释放) │
│ ├── version_manifest.rs (版本清单 — VersionEntry) │
│ ├── user_override_manager.rs(用户覆盖 — entry_kind) │
│ ├── env_parser.rs (.env 解析器) │
│ ├── mirror_manager.rs + mirror_config_manager.rs + mirror_config.rs(兼容层) │
│ ├── workspace_manager.rs (工作目录管理) │
│ ├── backup_engine.rs / restore_engine.rs / backup_manifest.rs │
│ ├── config_extractor.rs (运行时配置提取 Phase 3) │
│ ├── site_manager.rs / user_config.rs / backup_options_store.rs │
├─────────────────────────────────────────────────────────────┤
│ docker/ (Docker 交互层: manager.rs / mirror.rs) │
│ logging.rs (三层日志) / macros.rs (app_log!/ui_log!) │
└──────────────────────┬──────────────────────────────────────┘
│ bollard / docker CLI
┌──────────────────────▼──────────────────────────────────────┐
│ Docker Engine │
├─────────────────────────────────────────────────────────────┤
│ Containers: ps-php85, ps-mysql84, ps-redis82, ps-nginx128 │
│ Networks: php-stack-network │
│ Volumes: data/, logs/ │
└─────────────────────────────────────────────────────────────┘
| 模块 | 职责 |
|---|---|
config_generator.rs |
根据 GUI 输入生成 .env、docker-compose.yml、释放 services/ 模板(resolve_template_dir 回退 + copy_template_file 存在即跳过) |
version_manifest.rs |
管理 VersionEntry 数据(get_entry / get_available_entries / find_entry_by_env_prefix),清单 include_str! 编译进二进制,可被 app_data_dir 外部清单覆盖 |
user_override_manager.rs |
管理 .user-config/version_overrides.json(entry_kind: override/custom),get_merged_entry 合并用户镜像 tag |
env_parser.rs |
.env 文件可靠读写,保留注释和空行,往返一致 |
mirror_manager.rs / mirror_config_manager.rs |
镜像源预设与用户配置管理;mirror_config.rs 为向后兼容层 |
workspace_manager.rs |
管理 workspace.json(app_data_dir),解耦软件本体与业务数据 |
backup_engine.rs / backup_manifest.rs |
ZIP 备份(manifest + SHA256 + 64KB 分块流式写入) |
restore_engine.rs |
备份验证与还原:zip-slip 防护、预览端口冲突检测、恢复前自动回滚包、返回完整明细 |
config_extractor.rs |
运行时按需配置提取(Phase 3):docker create + cp 从官方镜像提取默认配置 |
site_manager.rs / user_config.rs / backup_options_store.rs |
站点定义 / 用户配置收纳 / 备份选项持久化 |
| 组件 | 职责 |
|---|---|
EnvConfigPage.vue |
服务版本选择、端口配置、PHP 扩展、多站点挂载、应用配置 |
SoftwareSettings.vue |
版本映射表格、用户 Override 编辑(override/custom) |
MirrorPanel.vue |
镜像源预设、独立配置、连接测试 |
BackupPage.vue / RestorePage.vue |
备份选项与分步恢复向导(预览→校验→确认→明细结果) |
AboutPage.vue / SettingsPage.vue |
关于/日志等级/导出;应用设置 |
CustomSelect.vue / UiTabs.vue / ConfirmDialog.vue / Toast.vue |
通用 UI 组件 |
VersionHelpModal.vue / ImagePullConfirmModal.vue |
版本帮助弹窗 / 镜像拉取确认弹窗(Phase 3 配套) |
WorkspaceInitDialog.vue / WorkspaceMissingDialog.vue |
工作区初始化与缺失提示 |
VersionEntry(Rust — version_manifest.rs):
pub struct VersionEntry {
pub display_name: String, // "PHP 8.2"
pub image_tag: String, // "php:8.2-fpm"(完整镜像名)
pub service_dir: String, // "php82"(配置目录名)
pub default_port: u16, // 9000
pub show_port: bool, // PHP 不显示端口配置
pub eol: bool,
pub description: Option<String>,
}设计原则:manifest 每条记录自描述。ID 即目录名(php82),env_prefix 由 service_dir.to_uppercase() 推导(PHP82),无需运行时格式转换。
版本映射集中在 version_manifest.json,配置生成器直接使用 entry.image_tag,消除全部格式转换逻辑(version.replace('.',"") / format!("{}:{}",image,tag) / 前缀硬编码链)。添加新版本只需编辑 JSON(或由 sync-version-manifest 同步),无需修改 Rust 代码。
默认配置由开发者维护(安全性),高级用户可自定义(灵活性)。.user-config/version_overrides.json 按 manifest ID 覆盖,entry_kind 必填:
entry_kind |
语义 |
|---|---|
override |
叠在清单基线上,只换 image_tag(+可选 description) |
custom |
完整 VersionEntry 独立条目,可出现在环境配置下拉 |
优先级:用户覆盖 → 内置清单 → Dockerfile 默认值(兜底)。
使用 ${VAR} 插值语法,.env 与 docker-compose.yml 解耦,修改配置无需重新生成 compose 文件。
zip::ZipWriter(Deflated)单一 ZIP 文件,含 manifest.json + SHA256 逐文件校验;失败即删除 ZIP,单文件失败不影响其他(容错优于严格)。备份写入采用 64KB 分块流式,避免大文件整体读入内存。
build.args.PHP_BASE_IMAGE 注入 ${PHP82_VERSION} → Dockerfile ARG PHP_BASE_IMAGE; FROM ${PHP_BASE_IMAGE}。PHP Dockerfile 已收敛为参数化模板(scripts/sync-php-dockerfile.mjs 同步),新增版本不必复制目录。
前后端共享 ContainerState 枚举(running/exited/...),替换早期 format!("{:?}") 字符串 + includes('running') 猜测的脆弱做法。
- zip-slip 防护:解压前校验条目路径不得逃逸目标目录(拒绝
..、绝对路径、盘符)。 - 恢复前自动回滚包:
execute_restore开始前把将被覆盖的文件打包为.restore_rollback_<ts>.zip,出错可回退。 - 恢复结果明细:始终返回
RestoreResult(恢复文件列表 + 逐条错误 + 回滚包路径),前端渲染结果卡片。 - 端口冲突:预览时检测宿主机占用并给出建议端口;恢复仍写入原配置,不自动改写(用户启动前可手动调整)。
用户输入 (GUI) → EnvConfig { services: [{ version: "php82", ... }] }
→ ConfigGenerator.generate_env(config, workspace_root)
→ VersionManifest.get_entry("php", "php82")
→ UserOverrideManager.get_merged_entry(...) // 合并用户 image_tag
→ env_prefix = service_dir.to_uppercase() // "PHP82"
→ env.set("PHP82_VERSION", entry.image_tag)
→ 写入 .env → 写入 docker-compose.yml → 释放 services/<dir>/ 模板(已存在则跳过)
start_environment
→ docker compose down --remove-orphans(清理旧容器)
→ 等待 ps- 容器完全停止(循环检测,最多 10 次 × 1s)
→ 检查端口冲突(PORT_CONFLICT → 前端 ConfirmDialog:忽略并继续 / 取消)
→ docker compose up -d(后台启动)
→ 智能等待容器就绪(每 2s 检查 running,无硬超时;logs -f 进程异常退出视为失败)
启动流程的同步阻塞调用(
compose down/up等待 / 日志轮询)统一走spawn_blocking。
备份:选择选项(项目本地配置 / 全树 / 日志 / 站点范围等,偏好持久化到 .user-config/backup.json)→ BackupEngine.create_backup → 打包 .env、docker-compose.yml、services/、.user-config/(不含 backup.json UI 偏好)→ 生成 manifest.json(含站点路径元数据)→ SHA256 校验 → 流式写入 ZIP → 进度事件。不包含数据库 mysqldump。
恢复(分步向导):
- 选择备份文件 → 2. 预览(manifest 解析 + 端口冲突检测 + 站点路径覆写)→ 3. SHA256 完整性校验 → 4. 确认恢复(自动生成回滚包 → 解压 .env / compose / services / 其他 → 返回明细结果)。
src-tauri/services/ 下文件是三类性质不同的东西,上游可得性差异极大:
| 层 | 内容 | 策略 |
|---|---|---|
| L1 版本元数据 | version_manifest.json |
全自动同步(official-images + endoflife.date) |
| L2 配置基线 | php.ini / mysql.cnf / redis.conf / nginx.conf |
运行时按需提取(Phase 3) |
| L3 构建定义 | Dockerfile |
参数化模板手工维护(项目自研,含 PUID/PGID/镜像源注入) |
核心决策:同步放 CI,不放进 App。 App 保持离线可用,模板与版本清单作为随版本发布的快照内置;镜像源等"执行期联网"能力与"定义期联网"(运行时抓取上游定义)严格区分,后者不做。
services/ 目录名(php85、mysql84...)是贯穿全项目的隐式契约,被以下位置消费:
| 位置 | 用法 |
|---|---|
version_manifest.json → service_dir |
清单条目指向模板目录 |
config_generator |
env_prefix = service_dir.to_uppercase() |
| compose 服务名 | {service_dir}(如 redis62);容器名 ps-{service_dir} |
| compose 网络别名 | 每类仅 1 个实例时额外挂短名 redis / mysql / nginx(多实例不加);环境配置页展示推荐主机名 |
| 目录映射 | services/、data/、logs/ 三处同名 |
resolve_template_dir |
按 service_dir 查找模板,找不到回退默认目录 |
该约定是"项目停止维护后用户仍可自救"的根基:用户加一条清单 + 建一个同名目录即可,无需改代码。任何改动不得破坏它。
service_dir 与 ID 是两个独立字段:新增版本可复用已有目录(service_dir 指向旧目录),配合 resolve_template_dir 回退,"加版本"退化为"改一行 JSON"。
集成在 ConfigGenerator::apply → generate_service_dirs 复制逻辑之前:
- 工作区配置已存在(用户改过)→ 跳过,不覆盖
- 内置模板有对应目录 → 直接复制(零成本)
- 都没有 → 检查本地镜像 → 有则
docker create + cp + rm(不删镜像,供后续up复用);无则汇总弹窗让用户确认后串行 pull - 提取失败 → 降级到 fallback 模板 + warning 日志,不阻断 apply
配置映射(硬编码在 Rust,跨版本稳定):PHP /usr/local/etc/php/php.ini-production(5.x 无 -production 后缀);MySQL /etc/my.cnf(5.x 为 /etc/mysql/my.cnf);Redis /usr/local/etc/redis/redis.conf;Nginx /etc/nginx/nginx.conf。
- 永不覆盖用户已修改的文件(
copy_template_file目标存在则跳过) - 同步产物不自动合入(PR + 人工 review,防 rc/beta 推给用户)
- 不引入 Docker Hub API 依赖(实测不通且限流)
- Dockerfile 不从上游复制;不删除用户手写的清单条目
三层日志架构:
| 层级 | 目标 | 用途 |
|---|---|---|
| 文件日志 | php-stack.log(app_data_dir) |
持久化,启动时轮转保留最近 3 份 |
| 控制台 | 终端 | 开发调试(tracing + EnvFilter,可用 RUST_LOG 覆盖) |
| UI 日志 | 前端面板 | env-log 推送 { level, message },最多 200 条,按等级过滤着色 |
app_log!(level, module, ...):写文件 + 控制台,不推 UIui_log!(app, level, module, ...):先app_log!再 emitenv-log;用户可见文案统一英文短句、无 emoji- 前端
useToast.ts管理日志缓冲与等级过滤(php-stack-log-level);面板支持复制(当前可见条目)/清空/导出(文件日志)
路径以运行时解析为准;
app_data_dir为 Tauri 官方应用数据目录(Windows%APPDATA%\com.php-stack.dev等)。
| 写入物 | 落点 | 负责模块 |
|---|---|---|
.env / docker-compose.yml |
工作区(workspace.json 配置路径) |
config_generator / env_config |
services/ / data/ / logs/ |
工作区 | config_generator / Docker 挂载 |
.user-config/mirror_config.json / version_overrides.json / sites.json / backup.json |
工作区 | mirror_config_manager / user_override_manager / site_manager / backup_options_store |
备份 ZIP / .restore_rollback_*.zip |
用户选择路径 / 工作区 | backup_engine / commands::backup |
workspace.json |
app_data_dir | workspace_manager |
php-stack.log |
app_data_dir | logging(轮转) |
services/version_manifest.json(可选) |
app_data_dir | version_manifest(外部清单覆盖;缺失用二进制内置) |
路径解析统一收口在
commands/paths.rs,禁止业务代码自行爬目录。
| 文档 | 用途 |
|---|---|
| README.md | 项目介绍、用户快速开始 |
| DEV.md | 开发者贡献指南:环境准备、开发/测试命令、扩展指南、版本同步工作流 |
| AGENTS.md | AI Agent 协作规范 |
文档维护原则:架构级变更更新本文档;面向用户的变更更新 README;变更记录进 CHANGELOG;过时内容直接删除(git 历史可追溯),不保留历史归档目录。