progressCallback = $progressCallback; $this->isOnlineMode = $isOnlineMode; $this->init(); } /** * 设置进度回调 */ public function setProgressCallback(?callable $callback): self { $this->progressCallback = $callback; return $this; } /** * 设置是否为在线模式 */ public function setOnlineMode(bool $isOnlineMode): self { $this->isOnlineMode = $isOnlineMode; return $this; } /** * 输出日志(CLI 模式 echo,在线模式通过回调) */ protected function log(string $message, ?int $progress = null): void { if ($this->isOnlineMode && $this->progressCallback) { // 在线模式:通过回调返回进度 ($this->progressCallback)($message, $progress); } else { // CLI 模式:直接输出 echo $message . "\n"; } } /** * 输出进度 */ protected function progress(string $message, int $progress): void { $this->log($message, $progress); } /** * 输出消息(用于 Trait 中的 echo 替换) * CLI 模式直接输出,在线模式输出到缓冲区 */ protected function output(string $message): void { echo $message . "\n"; } /** * 初始化 */ protected function init(): void { $this->pluginPath = $this->getPluginPath(); $this->pluginName = basename($this->pluginPath); $this->appConfig = $this->getAppConfig(); } /** * 插件路径 - 子类可重写 */ protected function getPluginPath(): string { $class = static::class; $parts = explode('\\', $class); if (count($parts) >= 2 && $parts[0] === 'plugin') { return base_path('plugin/' . $parts[1]); } return base_path('plugin'); } /** * 插件名称 */ protected function getPluginName(): string { return $this->pluginName ?: basename($this->getPluginPath()); } /** * 设置数据库连接 */ public function setConnection(?string $connection): self { $this->connection = $connection; return $this; } /** * 强制重新运行迁移 */ public function forceMigrate(): void { $this->output("⚠️ Force migration - clearing migration logs..."); $this->clearLogs(); $this->runMigrations(); } /** * 安装 */ public function install($version): void { $this->log("====== Plugin Install: {$this->getPluginName()} ======", 0); $this->log("Version: {$version}"); $this->log("Path: {$this->pluginPath}"); // 安装前回调 $this->beforeInstall($version); // 安装 Composer 依赖(后端) $this->installComposerDeps(); // 运行迁移(后端) $this->runMigrations(); // 运行种子(后端) $this->runSeeds(); // 安装前端依赖 $this->installNpmDeps(); // 复制前端模板到各端 $this->copyTemplates(); // 执行各端安装命令 $this->runInstallCommands(); // 安装后回调 - 可重写用于导入菜单等操作 $this->afterInstall($version); // 保存插件安装状态 $this->savePluginConfig($version); $this->log("====== Install Completed ======", 100); } /** * 安装前回调 - 可重写 */ protected function beforeInstall(string $version): void { $this->log(" 🔄 Running beforeInstall..."); } /** * 安装后回调 - 可重写 */ protected function afterInstall(string $version): void { $this->log(" 🔄 Running afterInstall..."); // 加载插件菜单 $this->loadMenus(); } /** * 卸载前回调 - 可重写 */ protected function beforeUninstall(string $version): void { $this->log(" 🔄 Running beforeUninstall..."); } /** * 卸载后回调 - 清理菜单 */ protected function afterUninstall(string $version): void { $this->log(" 🔄 Running afterUninstall..."); // 卸载菜单 $this->uninstallPluginMenus(); } /** * 卸载 */ public function uninstall($version): void { $this->log("====== Plugin Uninstall: {$this->getPluginName()} ======", 0); $this->preserveTemplatesDuringUninstall = true; try { // 卸载前回调和卸载后回调均不得物理删除模板资源。 $this->beforeUninstall($version); // 获取卸载配置 $pluginInfo = $this->getPluginInfo(); $uninstallConfig = $pluginInfo['uninstall'] ?? []; $dropTables = $uninstallConfig['drop_tables'] ?? false; $this->log("📋 Uninstall config: drop_tables=" . ($dropTables ? 'true' : 'false')); // 回滚迁移(可选删除表) $this->rollbackMigrations($dropTables); // 清理日志 $this->clearLogs(); $this->afterUninstall($version); } finally { $this->preserveTemplatesDuringUninstall = false; } $this->log('📦 Plugin package, templates and dependencies are preserved until deletion; only the installation marker is removed.'); $this->log("====== Uninstall Completed ======", 100); } /** * 删除插件 */ public function delete($version): void { $this->log("====== Plugin Delete: {$this->getPluginName()} ======", 10); // 获取插件配置 $pluginInfo = $this->getPluginInfo(); $uninstallConfig = $pluginInfo['uninstall'] ?? []; $undeletable = $uninstallConfig['undeletable'] ?? false; $this->log("📋 Plugin config: undeletable=" . ($undeletable ? 'true' : 'false'), 20); // 检查是否允许删除 if ($undeletable) { throw new \Exception('系统内置插件不允许删除'); } // 删除前回调 $this->beforeDelete($version); // 删除安装时复制到前端项目的模板资源 $this->deleteTemplates(); // 按插件配置移除安装时合并的依赖 $globalUninstallConfig = $this->getConfig('uninstall', []); $removeDependencies = $uninstallConfig['remove_dependencies'] ?? $globalUninstallConfig['remove_dependencies'] ?? false; if ($removeDependencies) { $this->removeMergedDependencies(); } // 获取插件目录 $pluginDir = $this->pluginPath; if (!is_dir($pluginDir)) { throw new \Exception('插件目录不存在:' . $pluginDir); } $this->log("📁 Deleting plugin directory: {$pluginDir}", 40); // 使用递归删除整个插件目录 $this->removeDirectory($pluginDir); $this->log("✅ Plugin deleted successfully", 100); // 删除后回调 $this->afterDelete($version); } /** * 删除前回调 - 可重写 */ protected function beforeDelete(string $version): void { $this->log(" 🔄 Running beforeDelete..."); } /** * 删除后回调 - 可重写 */ protected function afterDelete(string $version): void { $this->log(" 🔄 Running afterDelete..."); } /** * 递归删除目录 */ protected function removeDirectory(string $directory): void { if (!is_dir($directory)) { return; } $files = scandir($directory); foreach ($files as $file) { if ($file === '.' || $file === '..') { continue; } $path = $directory . DIRECTORY_SEPARATOR . $file; if (is_dir($path)) { $this->removeDirectory($path); } else { @unlink($path); } } @rmdir($directory); } /** * 删除模板资源 - 仅应在删除插件或升级清理阶段调用 */ protected function deletePluginTemplates(): void { $this->deleteTemplates(); } /** * 更新 */ public function update($version): void { $this->output("====== Plugin Update: {$this->getPluginName()} ======"); // 先卸载 $this->uninstall($version); // 升级前清理旧模板,避免已删除的旧文件残留 $this->deleteTemplates(); // 再安装 $this->install($version); $this->output("====== Update Completed ======"); } // ========================================================= // 端安装命令执行 // ========================================================= /** * 执行各端安装命令 */ protected function runInstallCommands(): void { $this->output("📦 Running install commands..."); $projectRoot = $this->getProjectRoot(); // 后端安装命令 $this->runBackendCommands($projectRoot); // 前端安装命令 $this->runFrontendCommands($projectRoot); } /** * 执行后端安装命令 */ protected function runBackendCommands(string $projectRoot): void { // 可在子类中重写实现自定义命令 } /** * 执行前端安装命令 */ protected function runFrontendCommands(string $projectRoot): void { // 可在子类中重写实现自定义命令 } }