3, 'code' => 'official', 'name' => 'official', 'type' => 'madong', 'version' => '1.0.0', 'status' => 1, 'description' => '官方应用插件', 'detail_description' => '', 'author' => 'Madong', 'cover' => '', 'poster' => '', 'price' => 0, 'downloads' => 0, 'rating' => 0, 'created_at' => '2026-03-19 16:26:19', 'updated_at' => '2026-03-19 16:26:19', 'update_time' => '', 'update_logs' => [], 'category' => null, 'category_name' => '', 'tags' => [], 'is_new' => 0, 'is_hot' => 0, 'purchased' => 0, 'installed' => 0, 'manual_uninstall' => 0, 'composer_dependencies' => [], 'npm_dependencies' => [], 'is_installed' => 1, 'installed_version' => '1.0.0', 'has_update' => 0, 'is_purchased' => 1, 'is_downloaded' => 1, 'can_uninstall' => 1, 'can_download' => 1, 'undeletable' => 1, 'version_info' => [ 'remote' => [ 'version' => '1.0.0', 'release_date' => '2026-03-19 16:26:19', ], 'local' => [ 'version' => '1.0.0', 'install_date' => '2026-03-19 16:26:19', ], 'comparison' => [ 'needs_update' => 0, 'is_latest' => 1, 'version_difference' => null, ], ], ]])] #[Permission(code: 'plugin:module:list')] public function index(Request $request): \support\Response { try { $category = $request->input('category', null); $pageInput = $request->input('current', $request->input('page', 1)); $limitInput = $request->input('size', $request->input('limit', 15)); $page = max(1, (int)$pageInput); $limit = max(1, (int)$limitInput); $type = $request->input('type'); $keyword = $request->input('keyword'); /** @var PluginService $service */ $service = Container::make(PluginService::class); $result = $service->getList($category, $type, $keyword, $page, $limit); return Json::success('common.operation.success', $result); } catch (\Throwable $e) { return Json::fail($e->getMessage(), []); } } #[OA\Get( path: '/plugin/{name}/download', summary: '下载模块', tags: ['模块市场'], )] #[OA\Parameter( name: 'name', description: '模块标识', in: 'path', required: true, schema: new OA\Schema(type: 'string') )] #[SimpleResponse(schema: [], example: [])] public function download(Request $request): \support\Response { try { $pluginCode = $request->route->param('name'); // 1. 获取下载链接 $remoteService = Container::make(PluginRemoteService::class); $authCode = config('madong.auth_code'); $secretKey = config('madong.auth_secret'); $downloadInfo = $remoteService->getRemoteDownloadService($authCode, $secretKey, $pluginCode, 'latest'); if (empty($downloadInfo['url'])) { return Json::fail('获取下载链接失败'); } // 2. 下载并解压插件 $downloadService = Container::make(PluginDownloadService::class); $result = $downloadService->downloadAndExtract($pluginCode, $downloadInfo['url']); return Json::success('下载成功', $result); } catch (\Throwable $e) { return Json::fail($e->getMessage()); } } #[OA\Get( path: '/plugin/detail/{name}', summary: '获取模块详情', tags: ['模块市场'], )] #[OA\Parameter( name: 'name', description: '模块标识', in: 'path', required: true, schema: new OA\Schema(type: 'string') )] #[SimpleResponse(schema: [], example: [])] public function detail(Request $request): \support\Response { try { $moduleName = $request->route->param('name'); /** @var PluginService $service */ $service = Container::make(PluginService::class); $result = $service->getList(null, null, $moduleName); return Json::success('common.operation.success', $result['items'][0]); } catch (\Throwable $e) { return Json::fail($e->getMessage(), []); } } #[OA\Get( path: '/plugin/{name}/upgrade-logs', summary: '获取模块升级日志', tags: ['模块市场'], )] #[OA\Parameter( name: 'name', description: '模块标识', in: 'path', required: true, schema: new OA\Schema(type: 'string') )] #[SimpleResponse(schema: [], example: [])] public function upgradeLogs(Request $request): \support\Response { try { $moduleName = $request->route->param('name'); /** @var PluginService $service */ $service = Container::make(PluginService::class); $result = $service->getUpgradeLogs($moduleName); return Json::success('common.operation.success', $result); } catch (\Throwable $e) { return Json::fail($e->getMessage(), []); } } #[OA\Get( path: '/plugin/check-environment', summary: '环境检测', tags: ['模块市场'], )] #[OA\Parameter( name: 'name', description: '模块标识', in: 'query', required: true, schema: new OA\Schema(type: 'string') )] #[SimpleResponse(schema: [], example: [])] public function checkEnvironment(Request $request): \support\Response { try { $name = $request->input('name'); /** @var PluginInstallService $service */ $service = Container::make(PluginInstallService::class); $result = $service->installCheck($name); // 提取路径列表和状态 $paths = []; foreach ($result['checks'] as $check) { if (isset($check['path'])) { // 只展示从madong开始的路径 $path = $check['path']; $madongPos = strpos(strtolower($path), 'madong'); if ($madongPos !== false) { // 截取从madong开始的路径 $path = substr($path, $madongPos); // 确保路径格式正确,madong后面应该有目录分隔符 if (substr($path, 6, 1) !== DIRECTORY_SEPARATOR) { $path = 'madong' . DIRECTORY_SEPARATOR . substr($path, 6); } } $paths[] = [ 'path' => $path, 'requirement' => $check['permission_type'] ?? 'readable', 'status' => $check['status'] === 'success' ? 'success' : 'error', ]; } } return Json::success('common.operation.success', [ 'paths' => $paths, 'overall_status' => $result['overall_status'], 'message' => $result['message'], ]); } catch (\Throwable $e) { return Json::fail($e->getMessage(), []); } } #[OA\Get( path: '/plugin/install', summary: '插件安装', tags: ['模块市场'], )] #[SimpleResponse(schema: [], example: [])] public function install(Request $request): void { $connection = $request->connection; $code = $request->input('code'); $mode = $request->input('mode', 'local'); // 验证参数 if (empty($code)) { $connection->send(new Response(200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => 'true', 'Access-Control-Expose-Headers' => 'Content-Type', ], "\r\n")); $connection->send(\core\tool\Sse::error('参数错误:缺少code参数')); return; } // 发送SSE响应头 $connection->send(new Response(200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => 'true', 'Access-Control-Expose-Headers' => 'Content-Type', ], "\r\n")); try { // 发送开始安装消息 $connection->send(Sse::progress('开始安装插件 ' . $code, 0)); // 调用安装服务 $generator = (new PluginInstallService())->install($code, $mode); foreach ($generator as $chunk) { $connection->send($chunk); } } catch (\Exception $e) { // 发送错误消息 $connection->send(Sse::error('安装失败:' . $e->getMessage())); } } #[OA\Get( path: '/plugin/uninstall', summary: '插件卸载(SSE流式)', tags: ['模块市场'], )] #[OA\Parameter( name: 'code', description: '插件编码', in: 'query', required: true, schema: new OA\Schema(type: 'string') )] #[SimpleResponse(schema: [], example: [])] public function uninstall(Request $request): void { $connection = $request->connection; $code = $request->input('code'); // 验证参数 if (empty($code)) { $connection->send(new Response(200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => 'true', 'Access-Control-Expose-Headers' => 'Content-Type', ], "\r\n")); $connection->send(Sse::error('参数错误:缺少code参数')); return; } // 发送SSE响应头 $connection->send(new Response(200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => 'true', 'Access-Control-Expose-Headers' => 'Content-Type', ], "\r\n")); try { // 调用卸载服务(统一入口) $generator = (new PluginUninstallService())->uninstall($code); foreach ($generator as $chunk) { $connection->send($chunk); } } catch (\Exception $e) { // 发送错误消息 $connection->send(Sse::error('卸载失败:' . $e->getMessage())); } } #[OA\Delete( path: '/plugin/{name}', summary: '插件删除', tags: ['模块市场'], )] #[OA\Parameter( name: 'name', description: '插件名称', in: 'path', required: true, schema: new OA\Schema(type: 'string') )] #[Permission(code: 'plugin:delete')] #[SimpleResponse(schema: [], example: [])] public function destroy(Request $request): \support\Response { try { $name = $request->route->param('name'); if (empty($name)) { return Json::fail('参数错误:缺少name参数', []); } /** @var PluginUninstallService $service */ $service = Container::get(PluginUninstallService::class); $service->delete($name); return Json::success('common.operation.delete.success', []); } catch (\Throwable $e) { return Json::fail($e->getMessage()); } } #[OA\Get( path: '/plugin/select', summary: '获取已安装插件列表(用于下拉框)', tags: ['模块市场'], )] #[SimpleResponse(schema: [], example: [])] public function select(Request $request): \support\Response { try { /** @var PluginService $service */ $service = Container::make(PluginService::class); // 获取本地插件列表(包括已安装和未安装的) $localModules = $service->getLocalModules(); // 过滤出已安装的插件 $installedPlugins = array_filter($localModules, function ($module) { return $module['is_installed'] === true && $module['status'] == 1; }); // 构建下拉框格式数据 $selectItems = []; foreach ($installedPlugins as $plugin) { $selectItems[] = [ 'value' => $plugin['name'], 'label' => $plugin['name'] . ' - ' . $plugin['description'], 'name' => $plugin['name'], 'description' => $plugin['description'], 'version' => $plugin['version'], 'type' => $plugin['type'] ?? 'plugin', ]; } return Json::success('common.operation.success', array_values($selectItems)); } catch (\Throwable $e) { return Json::fail($e->getMessage(), []); } } }