hasFrontendCode($extensionId)) { Log::info("插件 {$extensionId} 没有前端代码,跳过部署"); return true; // 跳过,不算失败 } // 2. 获取源目录和目标目录 $sourcePath = $this->getFrontendSourcePath($extensionId); $targetPath = $this->getFrontendTargetPath(); // 3. 检查目标目录是否已存在 $pluginTargetPath = $targetPath . '/' . $extensionId; if (is_dir($pluginTargetPath)) { Log::warning("目标目录已存在,将覆盖:{$pluginTargetPath}"); $this->removeDirectory($pluginTargetPath); } // 4. 复制前端代码 $this->copyDirectory($sourcePath, $targetPath); // 5. 记录部署信息 $this->logDeploy($extensionId, [ 'source_path' => $sourcePath, 'target_path' => $targetPath, 'status' => 'success' ]); Log::info("前端代码部署成功:{$extensionId}"); return true; } catch (\Exception $e) { Log::error("前端代码部署失败:{$extensionId},错误:" . $e->getMessage()); // 回滚:删除已复制的文件 if (isset($pluginTargetPath) && is_dir($pluginTargetPath)) { $this->removeDirectory($pluginTargetPath); } // 不抛出异常,允许安装继续 return false; } } /** * 移除前端代码 * * @param string $extensionId 插件ID * @return bool 是否移除成功 */ public function remove(string $extensionId): bool { try { // 兼容性处理:检查插件是否有前端代码 // 如果插件没有前端代码(旧项目),则不删除 admin 的前端代码 if (!$this->hasFrontendCode($extensionId)) { Log::info("插件 {$extensionId} 没有前端代码,跳过删除(兼容旧项目)"); return true; } $targetPath = $this->getFrontendTargetPath(); $pluginTargetPath = $targetPath . '/' . $extensionId; if (!is_dir($pluginTargetPath)) { Log::info("前端代码不存在,无需删除:{$extensionId}"); return true; } // 删除目录 $this->removeDirectory($pluginTargetPath); // 记录删除信息 $this->logRemove($extensionId); Log::info("前端代码删除成功:{$extensionId}"); return true; } catch (\Exception $e) { Log::error("前端代码删除失败:{$extensionId},错误:" . $e->getMessage()); return false; } } /** * 检查插件是否有前端代码 * * @param string $extensionId 插件ID * @return bool */ private function hasFrontendCode(string $extensionId): bool { try { $frontendPath = $this->getFrontendSourcePath($extensionId); } catch (\Exception $e) { Log::warning("获取插件路径失败:{$extensionId},错误:" . $e->getMessage()); return false; } // 检查目录是否存在 if (!is_dir($frontendPath)) { return false; } // 检查是否有插件名称的子目录 $pluginFrontendPath = $frontendPath . '/' . $extensionId; if (!is_dir($pluginFrontendPath)) { Log::warning("插件 {$extensionId} 前端代码目录结构不正确,应为:frontend/{$extensionId}/"); return false; } return true; } /** * 获取插件的实际路径 * * @param string $extensionId 插件ID * @return string * @throws RuntimeException */ private function getPluginPath(string $extensionId): string { // 方法 1: 使用 extension_path() $path = extension_path($extensionId); if (is_dir($path)) { return $path; } // 方法 2: 从 CoreService 获取 Composer 映射 if (isset(CoreService::$extensionComposerMap[$extensionId])) { $composerInfo = CoreService::$extensionComposerMap[$extensionId]; $vendorPath = base_path('vendor/' . $composerInfo['name']); if (is_dir($vendorPath)) { return $vendorPath; } } // 方法 3: 手动检测常见路径 $possiblePaths = [ base_path('runtime/extension/' . $extensionId), base_path('vendor/six-shop/' . $extensionId), base_path('vendor/sixdec/' . $extensionId), ]; foreach ($possiblePaths as $path) { if (is_dir($path)) { return $path; } } throw new RuntimeException("找不到插件路径:{$extensionId}"); } /** * 获取前端代码源路径 * * @param string $extensionId 插件ID * @return string */ private function getFrontendSourcePath(string $extensionId): string { $pluginPath = $this->getPluginPath($extensionId); return $pluginPath . '/frontend'; } /** * 获取前端代码目标路径 * * @return string */ private function getFrontendTargetPath(): string { // base_path() 返回 backend 目录 // 需要回到项目根目录 $backendPath = base_path(); $projectRoot = dirname($backendPath); $realRoot = dirname($projectRoot); return $realRoot . '/frontend/admin/src/views'; } /** * 递归复制目录 * * @param string $source 源目录 * @param string $target 目标目录 * @return void */ private function copyDirectory(string $source, string $target): void { // 递归复制文件 $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($iterator as $item) { // 计算目标路径 $subPath = $iterator->getSubPathName(); $targetPath = $target . DIRECTORY_SEPARATOR . $subPath; if ($item->isDir()) { // 创建目录 if (!is_dir($targetPath)) { mkdir($targetPath, 0755, true); } } else { // 复制文件 $targetDir = dirname($targetPath); if (!is_dir($targetDir)) { mkdir($targetDir, 0755, true); } // 转换 SplFileInfo 对象为字符串路径 copy($item->getPathname(), $targetPath); } } } /** * 递归删除目录 * * @param string $path 目录路径 * @return void */ private function removeDirectory(string $path): void { if (!is_dir($path)) { return; } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST ); foreach ($iterator as $item) { if ($item->isDir()) { rmdir($item->getPathname()); } else { unlink($item->getPathname()); } } rmdir($path); } /** * 记录部署信息 * * @param string $extensionId 插件ID * @param array $info 部署信息 * @return void */ private function logDeploy(string $extensionId, array $info): void { $logDir = runtime_path('frontend_deploy'); if (!is_dir($logDir)) { mkdir($logDir, 0755, true); } $logFile = $logDir . '/deployed.json'; // 读取现有记录 $deployed = []; if (file_exists($logFile)) { $content = file_get_contents($logFile); $deployed = json_decode($content, true) ?: []; } // 添加新记录 if (!isset($deployed['deployed'])) { $deployed['deployed'] = []; } $deployed['deployed'][] = array_merge([ 'extension_id' => $extensionId, 'deployed_at' => date('Y-m-d H:i:s'), ], $info); // 写入文件 file_put_contents( $logFile, json_encode($deployed, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) ); } /** * 记录删除信息 * * @param string $extensionId 插件ID * @return void */ private function logRemove(string $extensionId): void { $logDir = runtime_path('frontend_deploy'); if (!is_dir($logDir)) { mkdir($logDir, 0755, true); } $logFile = $logDir . '/deployed.json'; // 读取现有记录 $deployed = []; if (file_exists($logFile)) { $content = file_get_contents($logFile); $deployed = json_decode($content, true) ?: []; } // 添加删除记录 if (!isset($deployed['removed'])) { $deployed['removed'] = []; } $deployed['removed'][] = [ 'extension_id' => $extensionId, 'removed_at' => date('Y-m-d H:i:s'), ]; // 写入文件 file_put_contents( $logFile, json_encode($deployed, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) ); } }