From fef39b27208247cb16650140fd4694a1c2d4508a Mon Sep 17 00:00:00 2001 From: kuaifan Date: Tue, 20 May 2025 20:07:16 +0800 Subject: [PATCH] no message --- app/Http/Controllers/Api/AppsController.php | 344 ----- app/Module/Apps.php | 1440 +------------------ routes/web.php | 4 - 3 files changed, 12 insertions(+), 1776 deletions(-) delete mode 100755 app/Http/Controllers/Api/AppsController.php diff --git a/app/Http/Controllers/Api/AppsController.php b/app/Http/Controllers/Api/AppsController.php deleted file mode 100755 index 3170b409f..000000000 --- a/app/Http/Controllers/Api/AppsController.php +++ /dev/null @@ -1,344 +0,0 @@ - $app['config']['status'] === 'installed'), - 'name' - ) - ); - } - - // 获取应用菜单 - $menusData = []; - $res = Apps::getAppMenuItems(); - if (Base::isSuccess($res)) { - $menusData = $res['data']; - foreach ($menusData as &$menu) { - $menu['label'] = Apps::getMultiLanguageField($menu['label']); - } - } - - return Base::retSuccess('success', [ - 'installed' => $installedName, - 'menus' => $menusData, - ]); - } - - /** - * @api {post} api/apps/install 05. 安装应用(限管理员) - * - * @apiVersion 1.0.0 - * @apiGroup apps - * @apiName install - * - * @apiParam {String} app_name 应用名称 - * @apiParam {String} [version] 版本号,不指定则使用最新版本 - * @apiParam {Object} [params] 应用参数 - * @apiParam {Object} [resources] 资源限制 - * @apiParam {String} [resources.cpu_limit] CPU限制 - * @apiParam {String} [resources.memory_limit] 内存限制 - * - * @apiSuccess {Number} ret 返回状态码(1正确、0错误) - * @apiSuccess {String} msg 返回信息(错误描述) - * @apiSuccess {Object} data 安装结果信息 - */ - public function install() - { - $user = User::auth('admin'); - // - $appName = Request::input('app_name'); - $version = Request::input('version', 'latest'); - $params = Request::input('params', []); - $resources = Request::input('resources', []); - - if (empty($appName)) { - return Base::retError('应用名称不能为空'); - } - - // 获取应用配置 - $appConfig = Apps::getAppConfig($appName); - - // 保存用户设置的参数 - $updateConfig = []; - - // 记录安装用户 - $updateConfig['installer'] = is_array($appConfig['installer']) ? $appConfig['installer'] : []; - $updateConfig['installer'][] = ['userid' => $user->userid, 'time' => Timer::time()]; - if (count($updateConfig['installer']) > 5) { - $updateConfig['installer'] = array_slice($updateConfig['installer'], -5); - } - - // 设置参数 - if (!empty($params) && is_array($params)) { - $updateConfig['params'] = $params; - } - - // 设置资源限制 - if (!empty($resources) && is_array($resources)) { - $updateConfig['resources'] = $resources; - } - - // 保存配置 - Apps::saveAppConfig($appName, $updateConfig); - - // 执行安装 - return Apps::dockerComposeUp($appName, $version); - } - - /** - * @api {get} api/apps/install/url 06. 通过url安装应用(限管理员) - * - * @apiVersion 1.0.0 - * @apiGroup apps - * @apiName install_url - * - * @apiParam {String} url 应用url - * - * @apiSuccess {Number} ret 返回状态码(1正确、0错误) - * @apiSuccess {String} msg 返回信息(错误描述) - * @apiSuccess {Object} data 安装结果信息 - */ - public function install__url() - { - User::auth('admin'); - // - $url = Request::input('url'); - - // 下载应用 - $res = Apps::downloadApp($url); - if (Base::isError($res)) { - return $res; - } - - // 安装应用 - return Apps::dockerComposeUp($res['app_name']); - } - - /** - * 更新应用状态(用于安装结束之后回调) - * - * @apiParam {String} app_name 应用名称 - * @apiParam {String} status 新状态,可选值: installed, error - * - * @return string - */ - public function install__callback() - { - // 用户权限验证 - $authorization = Base::leftDelete(Request::header("Authorization"), "Bearer "); - if ($authorization != md5(env('APP_KEY'))) { - return 'Authorization error'; - } - - // 获取参数 - $appName = Request::input('app_name'); - $status = Request::input('status'); - - if (empty($appName)) { - return 'app name is empty'; - } - - // 处理状态 - $status = str_replace(['successful', 'failed'], ['installed', 'error'], $status); - if (!in_array($status, ['installed', 'error'])) { - return 'status is invalid'; - } - - // 最后一步处理 - $res = Apps::dockerComposeFinalize($appName, $status); - if (Base::isError($res)) { - return 'response error (' . $res['msg'] . ')'; - } - return 'ok'; - } - - /** - * @api {post} api/apps/uninstall 07. 卸载应用(限管理员) - * - * @apiVersion 1.0.0 - * @apiGroup apps - * @apiName uninstall - * - * @apiParam {String} app_name 应用名称 - * - * @apiSuccess {Number} ret 返回状态码(1正确、0错误) - * @apiSuccess {String} msg 返回信息(错误描述) - * @apiSuccess {Object} data 卸载结果信息 - */ - public function uninstall() - { - $user = User::auth('admin'); - // - $appName = Request::input('app_name'); - - if (empty($appName)) { - return Base::retError('应用名称不能为空'); - } - - // 获取应用配置 - $appConfig = Apps::getAppConfig($appName); - - // 保存用户设置的参数 - $updateConfig = []; - - // 记录安装用户 - $updateConfig['uninstaller'] = is_array($appConfig['uninstaller']) ? $appConfig['uninstaller'] : []; - $updateConfig['uninstaller'][] = ['userid' => $user->userid, 'time' => Timer::time()]; - if (count($updateConfig['uninstaller']) > 5) { - $updateConfig['uninstaller'] = array_slice($updateConfig['uninstaller'], -5); - } - - // 保存配置 - Apps::saveAppConfig($appName, $updateConfig); - - // 执行卸载 - return Apps::dockerComposeDown($appName); - } - - /** - * @api {get} api/apps/logs 08. 获取应用日志(限管理员) - * - * @apiVersion 1.0.0 - * @apiGroup apps - * @apiName logs - * - * @apiParam {String} app_name 应用名称 - * @apiParam {Number} [lines=50] 获取日志行数,默认50行 - * @apiParam {Boolean} [simple=false] 是否只返回日志,默认false - * - * @apiSuccess {Number} ret 返回状态码(1正确、0错误) - * @apiSuccess {String} msg 返回信息(错误描述) - * @apiSuccess {Object} data 返回数据 - * @apiSuccess {String} data.name 应用名称 - * @apiSuccess {Object} data.config 应用配置信息 - * @apiSuccess {Object} data.versions 可用版本列表 - * @apiSuccess {Boolean} data.upgradeable 是否可升级 - * @apiSuccess {String} data.log 日志内容 - */ - public function logs() - { - User::auth('admin'); - // - $appName = Request::input('app_name'); - $lines = intval(Request::input('lines', 50)); - $simple = Request::input('simple', false); - - $logContent = implode("\n", Apps::getAppLog($appName, $lines)); - - $data = [ - 'name' => $appName, - 'log' => trim($logContent) - ]; - if (!$simple) { - $config = Apps::getAppConfig($appName); - $versions = Apps::getAvailableVersions($appName); - $data['config'] = $config; - $data['versions'] = $versions; - $data['upgradeable'] = Apps::isUpgradeable($config, $versions); - } - - return Base::retSuccess('success', $data); - } -} diff --git a/app/Module/Apps.php b/app/Module/Apps.php index 3fa5e5b45..44c84b819 100644 --- a/app/Module/Apps.php +++ b/app/Module/Apps.php @@ -2,1447 +2,31 @@ namespace App\Module; -use Cache; -use Symfony\Component\Yaml\Exception\ParseException; +use App\Services\RequestContext; use Symfony\Component\Yaml\Yaml; class Apps { - // 软件源列表URL - const SOURCES_URL = 'https://appstore.dootask.com/'; - - // 受保护的服务名称列表 - const PROTECTED_NAMES = [ - 'php', - 'nginx', - 'redis', - 'mariadb', - 'search', - 'appstore', - ]; - - // 缓存键集合 - const CACHE_KEYS = [ - 'list' => 'appstore_app_list', // 应用列表缓存 - 'menu' => 'appstore_menu_items', // 应用菜单缓存 - 'installed' => 'appstore_installed', // 已安装应用缓存 - ]; - - /** - * 获取应用列表 - * - * @param bool $cache 是否使用缓存,默认true - * @return array - */ - public static function appList(bool $cache = true): array - { - if ($cache === false) { - Cache::forget(self::CACHE_KEYS['list']); - } - $apps = Cache::remember(self::CACHE_KEYS['list'], now()->addHour(), function () { - $apps = []; - $baseDir = base_path('docker/appstore/apps'); - $dirs = scandir($baseDir); - foreach ($dirs as $appName) { - if ($appName === '.' || $appName === '..' || str_starts_with($appName, '.')) { - continue; - } - $info = self::getAppInfo($appName); - $config = self::getAppConfig($appName); - $versions = self::getAvailableVersions($appName); - $apps[] = [ - 'name' => $appName, - 'info' => $info, - 'config' => $config, - 'versions' => $versions, - 'upgradeable' => self::isUpgradeable($config, $versions), - ]; - } - return $apps; - }); - return Base::retSuccess("success", $apps); - } - - /** - * 获取应用信息(比列表多返回了 document) - * - * @param string $appName 应用名称 - * @return array - */ - public static function appInfo(string $appName): array - { - $info = self::getAppInfo($appName); - $config = self::getAppConfig($appName); - $versions = self::getAvailableVersions($appName); - return Base::retSuccess("success", [ - 'name' => $appName, - 'info' => $info, - 'config' => $config, - 'versions' => $versions, - 'upgradeable' => self::isUpgradeable($config, $versions), - 'document' => self::getAppDocument($appName), - ]); - } - - /** - * 执行docker-compose up命令 - * - * @param string $appName - * @param string $version - * @param string $command - * @return array - */ - public static function dockerComposeUp(string $appName, string $version = 'latest', string $command = 'up'): array - { - // 结果集合 - $RESULTS = []; - - // 获取版本信息 - $versions = self::getAvailableVersions($appName); - if (empty($versions)) { - return Base::retError("没有可用的版本"); - } - if (strtolower($version) !== 'latest') { - $versions = array_filter($versions, function ($item) use ($version) { - return $item['version'] === ltrim($version, 'v'); - }); - } - $versionInfo = array_shift($versions); - if (empty($versionInfo)) { - return Base::retError("没有找到版本 {$version}"); - } - - // 获取安装配置信息 - $appConfig = self::getAppConfig($appName); - - // 检查是否需要卸载旧版本 - if ($command === 'up' && $appConfig['status'] === 'installed' && $appConfig['require_uninstalls']) { - foreach ($appConfig['require_uninstalls'] as $requireUninstall) { - if (version_compare($versionInfo['version'], $requireUninstall['version'], $requireUninstall['operator'])) { - $reason = !empty($requireUninstall['reason']) ? "(原因:{$requireUninstall['reason']})" : ''; - return Base::retError("更新版本 {$versionInfo['version']},需要先卸载已安装的版本{$reason}"); - } - } - } - - // 生成docker-compose.yml文件 - $composeFile = base_path('docker/appstore/apps/' . $appName . '/' . $versionInfo['version'] . '/docker-compose.yml'); - $res = self::generateDockerComposeYml($composeFile, $appConfig['params'], $appConfig['resources']); - if (Base::isError($res)) { - return $res; - } - $RESULTS['generate'] = $res['data']; - - // 生成nginx配置文件 - $nginxSourceFile = base_path('docker/appstore/apps/' . $appName . '/' . $versionInfo['version'] . '/nginx.conf'); - $nginxTargetFile = base_path('docker/appstore/configs/' . $appName . '/nginx.conf'); - $nginxContent = ''; - if ($command === 'up' && file_exists($nginxSourceFile)) { - $nginxContent = file_get_contents($nginxSourceFile); - } - file_put_contents($nginxTargetFile, $nginxContent); - - // 保存信息到配置信息 - $prefix = $command === 'up' ? 'install' : 'uninstall'; - $updateConfig = ['status' => $prefix . 'ing']; - $updateConfig[$prefix . '_num'] = intval($appConfig[$prefix . '_num']) + 1; - $updateConfig[$prefix . '_version'] = $versionInfo['version']; - $updateConfig[$prefix . '_at'] = date('Y-m-d H:i:s'); - self::saveAppConfig($appName, $updateConfig); - - // 执行docker-compose命令 - $curlPath = "app/{$command}/{$appName}"; - if ($command === 'up') { - $curlPath .= "?callback_url=" . urlencode("http://nginx/api/apps/install/callback?install_num=" . $updateConfig[$prefix . '_num']); - } - $res = self::curl($curlPath); - if (Base::isError($res)) { - self::saveAppConfig($appName, ['status' => 'error']); - return $res; - } - $RESULTS['compose'] = $res['data']; - - // 返回结果 - return Base::retSuccess("success", $RESULTS); - } - - /** - * 执行docker-compose down命令 - * - * @param string $appName - * @param string $version - * @return array - */ - public static function dockerComposeDown(string $appName, string $version = 'latest'): array - { - $res = self::dockerComposeUp($appName, $version, 'down'); - if (Base::isError($res)) { - return $res; - } - - // 最后一步处理 - return self::dockerComposeFinalize($appName, 'not_installed'); - } - - /** - * 更新docker-compose状态(安装或卸载之后最后一步处理) - * - * @param string $appName - * @param string $status - * @return array - */ - public static function dockerComposeFinalize(string $appName, string $status): array - { - // 获取当前应用信息 - $appInfo = self::getAppConfig($appName); - - // 只有在安装中的状态才能更新 - if (!in_array($appInfo['status'], ['installing', 'uninstalling'])) { - return Base::retError('当前状态不允许更新'); - } - - // 保存配置 - if (!self::saveAppConfig($appName, ['status' => $status])) { - return Base::retError('更新状态失败'); - } - - // 处理安装成功或卸载成功后的操作 - $message = '更新成功'; - switch ($status) { - case 'installed': - $message = '安装成功'; - break; - - case 'not_installed': - $message = '卸载成功'; - break; - - case 'error': - if ($appInfo['status'] === 'installing') { - $message = '安装失败'; - self::removeNginxConfig($appName); - } else { - $message = '卸载失败'; - } - break; - } - - // 返回结果 - return Base::retSuccess($message); - } - - /** - * 删除nginx配置文件 - * @param string $appName - * @return void - */ - private static function removeNginxConfig(string $appName): void - { - $nginxFile = base_path('docker/appstore/configs/' . $appName . '/nginx.conf'); - if (file_exists($nginxFile)) { - unlink($nginxFile); - } - } - - /** - * 获取应用信息 - * - * @param string $appName 应用名称 - * @return array - */ - public static function getAppInfo(string $appName): array - { - $baseDir = base_path('docker/appstore/apps/' . $appName); - $info = [ - 'name' => $appName, - 'description' => '', - 'tags' => [], - 'icon' => self::processAppIcon($appName, ['logo.svg', 'logo.png', 'icon.svg', 'icon.png']), - 'author' => '', - 'website' => '', - 'github' => '', - 'document' => '', - 'fields' => [], - 'require_uninstalls' => [], - ]; - - if (file_exists($baseDir . '/config.yml')) { - $configData = Yaml::parseFile($baseDir . '/config.yml'); - - // 处理名称 - if (isset($configData['name'])) { - $info['name'] = self::getMultiLanguageField($configData['name']) ?: $appName; - } - - // 处理描述 - if (isset($configData['description'])) { - $info['description'] = self::getMultiLanguageField($configData['description']); - } - - // 处理标签 - if (isset($configData['tags']) && is_array($configData['tags'])) { - foreach ($configData['tags'] as $tag) { - if (trim($tag)) { - $info['tags'][] = trim($tag); - } - } - } - - // 处理字段 - if (isset($configData['fields']) && is_array($configData['fields'])) { - $appConfig = self::getAppConfig($appName); - - $fields = []; - foreach ($configData['fields'] as $field) { - // 检查必需的name字段及其格式 - if (!isset($field['name'])) { - continue; - } - - // 验证字段名格式,必须以字母或下划线开头,只允许大小写字母、数字和下划线 - if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $field['name'])) { - continue; - } - - // 标准化字段结构 - $normalizedField = [ - 'name' => $field['name'], - 'type' => $field['type'] ?? 'text', - 'default' => $appConfig['params'][$field['name']] ?? $field['default'] ?? '', - 'label' => self::getMultiLanguageField($field['label'] ?? ''), - 'placeholder' => self::getMultiLanguageField($field['placeholder'] ?? ''), - 'required' => $field['required'] ?? false, - ]; - - // 处理默认值 - if ($normalizedField['type'] === 'number') { - $normalizedField['default'] = intval($normalizedField['default']); - } - - // 处理 select 类型的选项 - if ($normalizedField['type'] === 'select' && isset($field['options']) && is_array($field['options'])) { - $selectOptions = []; - foreach ($field['options'] as $option) { - $selectOptions[] = [ - 'label' => self::getMultiLanguageField($option['label'] ?? ''), - 'value' => $option['value'] ?? '', - ]; - } - $normalizedField['options'] = $selectOptions; - } - - // 处理其他属性 - foreach ($field as $key => $value) { - if (!in_array($key, ['name', 'type', 'default', 'label', 'placeholder', 'required', 'options'])) { - $normalizedField[$key] = $value; - } - } - - $fields[] = $normalizedField; - } - $info['fields'] = $fields; - } - - // 处理 require_uninstalls 配置 - if (isset($configData['require_uninstalls']) && is_array($configData['require_uninstalls'])) { - $requireUninstalls = []; - foreach ($configData['require_uninstalls'] as $item) { - // 处理不同格式的配置 - if (is_array($item)) { - // 完整格式: {version: '2.0.0', reason: '原因'} - if (isset($item['version']) && preg_match('/^\s*([<>=!]*)\s*(.+)$/', $item['version'], $matches)) { - $requireUninstalls[] = [ - 'version' => $matches[2], - 'operator' => $matches[1] ?: '=', - 'reason' => self::getMultiLanguageField($item['reason']) - ]; - } - } else { - // 简化格式: 直接是版本号字符串 - $requireUninstalls[] = [ - 'version' => $item, - 'operator' => '=', - 'reason' => '' - ]; - } - } - $info['require_uninstalls'] = $requireUninstalls; - } - - // 处理其他标准字段 - foreach (['author', 'website', 'github', 'document'] as $field) { - if (isset($configData[$field])) { - $info[$field] = $configData[$field]; - } - } - } - - return $info; - } - - /** - * 获取应用的菜单配置 - * - * @param bool $cache 是否使用缓存,默认true - * @return array - */ - public static function getAppMenuItems(bool $cache = true): array - { - if ($cache === false) { - Cache::forget(self::CACHE_KEYS['menu']); - } - $res = Cache::remember(self::CACHE_KEYS['menu'], now()->addHour(), function () { - return self::menuGetAll(); - }); - if (Base::isError($res)) { - Cache::forget(self::CACHE_KEYS['menu']); - } - return $res; - } - - /** - * 获取单个应用的菜单配置 - * - * @param string $appName 应用名称 - * @return array - */ - private static function menuGetSingle(string $appName): array - { - $baseDir = base_path('docker/appstore/apps/' . $appName); - $menuItems = []; - - if (!file_exists($baseDir . '/config.yml')) { - return Base::retSuccess("success", $menuItems); - } - - try { - $configData = Yaml::parseFile($baseDir . '/config.yml'); - if (isset($configData['menu_items']) && is_array($configData['menu_items'])) { - foreach ($configData['menu_items'] as $menu) { - $normalizedMenu = self::menuNormalize($menu, $appName); - if ($normalizedMenu) { - $menuItems[] = $normalizedMenu; - } - } - } - } catch (ParseException $e) { - return Base::retError('配置文件解析失败:' . $e->getMessage()); - } - - return Base::retSuccess("success", $menuItems); - } - - /** - * 获取所有已安装应用的菜单配置 - * - * @return array - */ - private static function menuGetAll(): array - { - $allMenuItems = []; - $baseDir = base_path('docker/appstore/apps'); - - if (!is_dir($baseDir)) { - return Base::retSuccess("success", $allMenuItems); - } - - // 获取所有已安装的应用配置 - $installedApps = []; - $dirs = scandir($baseDir); - foreach ($dirs as $appName) { - if ($appName === '.' || $appName === '..' || str_starts_with($appName, '.')) { - continue; - } - - $appConfig = self::getAppConfig($appName); - if ($appConfig['status'] !== 'installed') { - continue; - } - - $installedApps[$appName] = strtotime($appConfig['install_at'] ?? ''); - } - - // 按安装时间排序应用 - uasort($installedApps, function ($timeA, $timeB) { - return $timeB <=> $timeA; // 降序排列 - }); - - // 按排序后的顺序获取菜单 - foreach ($installedApps as $appName => $installAt) { - $appMenuItems = self::menuGetSingle($appName); - if (Base::isSuccess($appMenuItems)) { - $allMenuItems = array_merge($allMenuItems, $appMenuItems['data']); - } - } - - return Base::retSuccess("success", $allMenuItems); - } - - /** - * 标准化菜单配置 - * - * @param array $menu 原始菜单配置 - * @param string $appName 应用名称 - * @return array|null 标准化后的菜单配置,配置无效时返回null - */ - private static function menuNormalize(array $menu, string $appName): ?array - { - // 检查必需的字段 - if (!isset($menu['location']) || !isset($menu['url'])) { - return null; - } - - // 基础配置 - $normalizedMenu = [ - 'app_name' => $appName, - 'location' => $menu['location'], - 'url' => $menu['url'], - 'key' => $menu['key'] ?? substr(md5($menu['url']), 0, 16), - 'icon' => self::processAppIcon($appName, [$menu['icon'] ?? '']), - 'label' => $menu['label'] ?? '', - ]; - - // 处理可选的UI配置 - $optionalConfigs = ['transparent', 'autoDarkTheme', 'keepAlive', 'disableScopecss']; - foreach ($optionalConfigs as $config) { - if (isset($menu[$config])) { - $normalizedMenu[$config] = $menu[$config]; - } - } - - return $normalizedMenu; - } - - /** - * 获取应用配置信息 - * - * @param string $appName 应用名称 - * @return array 应用配置信息 - */ - public static function getAppConfig(string $appName): array - { - $baseDir = base_path('docker/appstore/configs/' . $appName); - $configFile = $baseDir . '/config.json'; - - $defaultInfo = [ - 'install_at' => '', // 最后一次安装的时间 - 'install_num' => 0, // 安装的次数 - 'install_version' => '', // 最后一次安装的版本 - 'status' => 'not_installed', // 应用状态: installing, installed, uninstalling, not_installed, error - 'params' => [], // 用户自定义参数值 - 'resources' => [ - 'cpu_limit' => '', // CPU限制,例如 '0.5' 或 '2' - 'memory_limit' => '' // 内存限制,例如 '512M' 或 '2G' - ], - ]; - - if (file_exists($configFile)) { - $appConfig = json_decode(file_get_contents($configFile), true); - if (json_last_error() === JSON_ERROR_NONE && is_array($appConfig)) { - $defaultInfo = array_merge($defaultInfo, $appConfig); - } - } else { - Base::makeDir($baseDir); - file_put_contents($configFile, json_encode($defaultInfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - } - - // 确保 status 状态 - if (!in_array($defaultInfo['status'], ['installing', 'installed', 'uninstalling', 'not_installed', 'error'])) { - $defaultInfo['status'] = 'not_installed'; - } - - // 确保 params 是数组 - if (!is_array($defaultInfo['params'])) { - $defaultInfo['params'] = []; - } - - // 添加 DooTask 版本信息 - $defaultInfo['params']['DOOTASK_VERSION'] = Base::getVersion(); - - // 确保 resources 完整 - if (!is_array($defaultInfo['resources'])) { - $defaultInfo['resources'] = []; - } - $defaultInfo['resources']['cpu_limit'] = $defaultInfo['resources']['cpu_limit'] ?? ''; - $defaultInfo['resources']['memory_limit'] = $defaultInfo['resources']['memory_limit'] ?? ''; - - // 返回应用配置信息 - return $defaultInfo; - } - - /** - * 保存应用配置信息 - * - * @param string $appName 应用名称 - * @param array $data 要更新的数据 - * @param bool $merge 是否与现有配置合并,默认true - * @return bool 保存是否成功 - */ - public static function saveAppConfig(string $appName, array $data, bool $merge = true): bool - { - $baseDir = base_path('docker/appstore/configs/' . $appName); - $configFile = $baseDir . '/config.json'; - - // 初始化数据 - $appConfig = []; - - // 如果需要合并,先读取现有配置 - if ($merge && file_exists($configFile)) { - $existingData = json_decode(file_get_contents($configFile), true); - if (json_last_error() === JSON_ERROR_NONE && is_array($existingData)) { - $appConfig = $existingData; - } - } - - // 更新数据 - foreach ($data as $key => $value) { - if (is_array($value) && isset($appConfig[$key]) && is_array($appConfig[$key])) { - // 如果是嵌套数组,进行深度合并 - $appConfig[$key] = array_replace_recursive($appConfig[$key], $value); - } else { - // 普通值直接覆盖 - $appConfig[$key] = $value; - } - } - - // 确保目录存在 - if (!is_dir($baseDir)) { - mkdir($baseDir, 0755, true); - } - - // 清理缓存 - self::clearCache(); - - // 写入文件 - return (bool)file_put_contents( - $configFile, - json_encode($appConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) - ); - } - - /** - * 获取应用的日志 - * - * @param string $appName - * @param int $lines 获取的行数,默认50行, 最大2000行 - * @return array - */ - public static function getAppLog(string $appName, int $lines = 50): array - { - // 日志文件路径 - $logFile = base_path('docker/appstore/logs/' . $appName . '.log'); - - // 检查日志文件是否存在 - if (!file_exists($logFile)) { - return []; - } - - // 限制获取行数 - if ($lines <= 0) { - $lines = 50; - } else if ($lines > 2000) { - $lines = 2000; - } - - // 读取日志文件最后几行 - $output = []; - $cmd = 'tail -n ' . $lines . ' ' . escapeshellarg($logFile); - exec($cmd, $output); - - // 返回日志内容 - return $output; - } - /** * 判断应用是否已安装 * - * @param string $appName 应用名称 + * @param string $appId 应用ID(名称) * @return bool 如果应用已安装返回 true,否则返回 false */ - public static function isInstalled(string $appName): bool + public static function isInstalled(string $appId): bool { - $array = Base::json2array(Cache::get(self::CACHE_KEYS['installed'], [])); - if (isset($array[$appName])) { - return $array[$appName]; + $key = 'app_installed_' . $appId; + if (RequestContext::has($key)) { + return RequestContext::get($key); } - $appConfig = self::getAppConfig($appName); - $array[$appName] = $appConfig['status'] === 'installed'; - Cache::put(self::CACHE_KEYS['installed'], Base::array2json($array)); - - return $array[$appName]; - } - - /** - * 更新应用列表 - * - * @return array - */ - public static function appListUpdate(): array - { - // 检查是否正在更新 - $cacheTmp = 'appstore_update_running'; - if (Cache::has($cacheTmp)) { - return Base::retError('应用列表正在更新中,请稍后再试'); - } - $onFailure = function (string $message) use ($cacheTmp) { - Cache::forget($cacheTmp); - return Base::retError($message); - }; - $onSuccess = function (string $message, array $data = []) use ($cacheTmp) { - self::clearCache(); - Cache::forget($cacheTmp); - return Base::retSuccess($message, $data); - }; - - // 设置更新状态 - Cache::put($cacheTmp, true, 180); // 3分钟有效期 - - // 临时目录 - $tempDir = base_path('docker/appstore/temp/sources'); - $zipFile = $tempDir . '/sources.zip'; - - // 清空临时目录 - if (is_dir($tempDir)) { - Base::deleteDirAndFile($tempDir, true); - } else { - Base::makeDir($tempDir); + $configFile = base_path('docker/appstore/config/' . $appId . '/config.yml'); + $installed = false; + if (file_exists($configFile)) { + $configData = Yaml::parseFile($configFile); + $installed = $configData['status'] === 'installed'; } - try { - // 下载源列表 - $res = Ihttp::ihttp_request(self::SOURCES_URL); - if (Base::isError($res)) { - return $onFailure('下载源列表失败'); - } - file_put_contents($zipFile, $res['data']); - - // 解压文件 - $zip = new \ZipArchive(); - if ($zip->open($zipFile) !== true) { - return $onFailure('文件打开失败'); - } - $zip->extractTo($tempDir); - $zip->close(); - unlink($zipFile); - - // 遍历目录 - $dirs = scandir($tempDir); - $results = [ - 'success' => [], - 'failed' => [] - ]; - - foreach ($dirs as $appName) { - // 跳过当前目录、父目录和隐藏文件 - if ($appName === '.' || $appName === '..' || str_starts_with($appName, '.')) { - continue; - } - - $sourceDir = $tempDir . '/' . $appName; - if (!is_dir($sourceDir)) { - continue; - } - - // 检查config.yml文件 - $configFile = $sourceDir . '/config.yml'; - if (!file_exists($configFile)) { - $results['failed'][] = [ - 'app_name' => $appName, - 'reason' => '未找到config.yml配置文件' - ]; - continue; - } - - // 解析配置文件 - try { - $configData = Yaml::parseFile($configFile); - } catch (ParseException $e) { - $results['failed'][] = [ - 'app_name' => $appName, - 'reason' => 'YAML解析失败:' . $e->getMessage() - ]; - continue; - } - - // 检查name字段 - if (empty($configData['name'])) { - $results['failed'][] = [ - 'app_name' => $appName, - 'reason' => '配置文件不正确' - ]; - continue; - } - - // 使用目录名作为应用名称 - $targetDir = base_path('docker/appstore/apps/' . $appName); - - // 复制目录 - if (!self::copyDirAndFile($sourceDir, $targetDir, true)) { - $results['failed'][] = [ - 'app_name' => $appName, - 'reason' => '复制文件失败' - ]; - continue; - } - - $results['success'][] = [ - 'app_name' => $appName - ]; - } - - // 清理临时目录 - Base::deleteDirAndFile($tempDir, true); - return $onSuccess('更新成功', $results); - } catch (\Exception $e) { - // 清理临时目录 - Base::deleteDirAndFile($tempDir, true); - return $onFailure('更新失败:' . $e->getMessage()); - } - } - - /** - * 下载应用 - * - * @param string $url 应用url - * @return array 下载结果 - */ - public static function downloadApp(string $url): array - { - // 检查是否正在下载 - $cacheTmp = 'appstore_download_running'; - if (Cache::has($cacheTmp)) { - return Base::retError('应用正在下载中,请稍后再试'); - } - $onFailure = function (string $message) use ($cacheTmp) { - Cache::forget($cacheTmp); - return Base::retError($message); - }; - $onSuccess = function (string $message, array $data = []) use ($cacheTmp) { - self::clearCache(); - Cache::forget($cacheTmp); - return Base::retSuccess($message, $data); - }; - - // 设置下载状态 - Cache::put($cacheTmp, true, 180); // 3分钟有效期 - - // 验证URL格式 - if (!filter_var($url, FILTER_VALIDATE_URL)) { - return $onFailure('URL格式不正确'); - } - - // 验证URL协议 - $scheme = parse_url($url, PHP_URL_SCHEME); - if (!in_array($scheme, ['http', 'https', 'git'])) { - return $onFailure('不支持的URL协议,仅支持http、https和git协议'); - } - - // 临时目录 - $tempDir = base_path('docker/appstore/temp/' . md5($url)); - - // 清空临时目录 - if (is_dir($tempDir)) { - Base::deleteDirAndFile($tempDir, true); - } else { - Base::makeDir($tempDir); - } - - // 判断URL类型 - $isGit = str_ends_with($url, '.git') || str_contains($url, 'github.com') || str_contains($url, 'gitlab.com'); - - try { - if ($isGit) { - // 克隆Git仓库 - $cmd = sprintf('cd %s && git clone --depth=1 %s .', escapeshellarg($tempDir), escapeshellarg($url)); - exec($cmd, $output, $returnVar); - if ($returnVar !== 0) { - return $onFailure('Git克隆失败'); - } - } else { - // 下载ZIP文件 - $zipFile = $tempDir . '/app.zip'; - $res = Ihttp::ihttp_request($url); - if (Base::isError($res)) { - return $onFailure('下载失败'); - } - file_put_contents($zipFile, $res['data']); - - // 解压ZIP文件 - $zip = new \ZipArchive(); - if ($zip->open($zipFile) !== true) { - return $onFailure('文件打开失败'); - } - $zip->extractTo($tempDir); - $zip->close(); - unlink($zipFile); - } - - // 检查config.yml文件 - $configFile = $tempDir . '/config.yml'; - if (!file_exists($configFile)) { - return $onFailure('未找到config.yml配置文件'); - } - - // 解析配置文件 - try { - $configData = Yaml::parseFile($configFile); - } catch (ParseException $e) { - return $onFailure('YAML解析失败:' . $e->getMessage()); - } - - // 检查name字段 - if (empty($configData['name'])) { - return $onFailure('配置文件不正确'); - } - - // 处理应用名称 - $appName = Base::camel2snake(Base::cn2pinyin($configData['name'], '_')); - if (in_array($appName, self::PROTECTED_NAMES)) { - return Base::retError('服务名称 "' . $appName . '" 被保护,不能使用'); - } - $targetDir = base_path('docker/appstore/apps/' . $appName); - $targetConfigFile = $targetDir . '/config.json'; - - // 检查目标是否存在 - if (file_exists($targetConfigFile)) { - $targetConfigData = json_decode(file_get_contents($targetConfigFile), true); - if (is_array($targetConfigData)) { - $status = $targetConfigData['status']; - $errorMessages = [ - 'installed' => '应用已存在,请先卸载后再安装', - 'installing' => '应用正在安装中,请稍后再试', - 'uninstalling' => '应用正在卸载中,请稍后再试' - ]; - if (isset($errorMessages[$status])) { - return $onFailure($errorMessages[$status]); - } - } - Base::deleteDirAndFile($targetDir); - } - - // 移动文件到目标目录 - if (!rename($tempDir, $targetDir)) { - return $onFailure('移动文件失败'); - } - - return $onSuccess('下载成功', ['app_name' => $appName]); - } catch (\Exception $e) { - // 清理临时目录 - Base::deleteDirAndFile($tempDir, true); - return $onFailure('下载失败:' . $e->getMessage()); - } - } - - /** - * 获取应用的文档(README) - * - * @param string $appName 应用名称 - * @return string 文档内容,如果未找到则返回空字符串 - */ - private static function getAppDocument(string $appName): string - { - $baseDir = base_path('docker/appstore/apps/' . $appName); - $lang = strtoupper(Base::headerOrInput('language')); - - // 使用 glob 遍历目录 - $files = glob($baseDir . '/*'); - - // 正则模式,包括语言特定和通用的 README 文件 - $readmePatterns = [ - "/^README(_|-|\.)?{$lang}\.md$/i", // README_zh.md, README-zh.md, README.zh.md - ]; - if ($lang == 'ZH') { - $readmePatterns[] = "/^README(_|-|\.)?CN\.md$/i"; // README_CN.md, README-cn.md, README.cn.md - } - if ($lang == 'ZH-CHT') { - $readmePatterns[] = "/^README(_|-|\.)?TW\.md$/i"; // README_TW.md, README-tw.md, README.tw.md - } - $readmePatterns[] = "/^README\.md$/i"; // README.md - - // 遍历所有 README 模式进行匹配 - foreach ($readmePatterns as $pattern) { - foreach ($files as $filePath) { - $fileName = basename($filePath); - if (preg_match($pattern, $fileName)) { - return file_get_contents($filePath); - } - } - } - - return ''; - } - - /** - * 生成docker-compose.yml文件配置 - * - * @param string $filePath docker-compose.yml文件路径 - * @param array $params 可选参数,替换docker-compose.yml中的${XXX}变量,示例:{'APP_ID' => '123'} - * @param array $resources 可选资源限制,示例:{'cpu_limit' => '0.5', 'memory_limit' => '512M'} - * @return array - */ - private static function generateDockerComposeYml(string $filePath, array $params = [], array $resources = []): array - { - // 应用名称 - $appName = basename(dirname($filePath, 2)); - - // 服务名称 - $serviceName = 'dootask-app-' . $appName; - - // 网络名称 - $networkName = 'dootask-networks-' . env('APP_ID'); - - // 主机路径 - $hostPwd = '${HOST_PWD}/docker/appstore/apps/' . $appName . '/' . basename(dirname($filePath)); - - // 保存路径 - $savePath = base_path('docker/appstore/configs/' . $appName . '/docker-compose.yml'); - - try { - // 读取文件内容 - $fileContent = file_get_contents($filePath); - - // 处理特殊环境变量 - $fileContent = str_replace('${HOST_PWD}', '', $fileContent); - $fileContent = str_replace('${PUBLIC_PATH}', '${HOST_PWD}/public', $fileContent); - - // 解析YAML文件 - $content = Yaml::parse($fileContent); - - // 确保services部分存在 - if (!isset($content['services'])) { - return Base::retError('Docker compose文件缺少services配置'); - } - - // 设置服务名称 - $content['name'] = $serviceName; - - // 删除所有现有网络配置 - unset($content['networks']); - - // 添加网络配置 - $content['networks'][$networkName] = ['external' => true]; - - // 检查服务名称是否被保护 - foreach ($content['services'] as $name => $service) { - if (in_array($name, self::PROTECTED_NAMES)) { - return Base::retError('服务名称 "' . $name . '" 被保护,不能使用'); - } - } - - foreach ($content['services'] as &$service) { - // 确保所有服务都有网络配置 - $service['networks'] = [$networkName]; - - // 处理现有的volumes配置 - if (isset($service['volumes'])) { - $service['volumes'] = self::volumeProcessConfigurations($service['volumes'], $hostPwd); - } - - // 处理资源限制 - if (isset($resources['cpu_limit']) && $resources['cpu_limit']) { - $service['deploy']['resources']['limits']['cpus'] = $resources['cpu_limit']; - } - if (isset($resources['memory_limit']) && $resources['memory_limit']) { - $service['deploy']['resources']['limits']['memory'] = $resources['memory_limit']; - } - } - - // 生成YAML内容 - $yamlContent = Yaml::dump($content, 8, 2); - - // 替换${XXX}格式变量 - $yamlContent = preg_replace_callback('/\$\{(.*?)}/', function ($matches) use ($params) { - return $params[$matches[1]] ?? $matches[0]; - }, $yamlContent); - - // 保存文件 - if (file_put_contents($savePath, $yamlContent) === false) { - return Base::retError('无法写入配置文件'); - } - - // 返回成功 - return Base::retSuccess('success', [ - 'file' => $savePath, - ]); - } catch (ParseException $e) { - // YAML解析错误 - return Base::retError('YAML parsing error:' . $e->getMessage()); - } - } - - /** - * 处理卷挂载配置 - * - * @param array $volumes 原始卷挂载配置数组 - * @param string $hostPwd 主机工作目录路径 - * @return array 处理后的卷挂载配置数组 - */ - public static function volumeProcessConfigurations(array $volumes, string $hostPwd): array - { - return array_map(function ($volume) use ($hostPwd) { - // 短语法格式:字符串形式如 "./src:/app" - if (is_string($volume)) { - return self::volumeProcessShortSyntax($volume, $hostPwd); - } // 长语法格式:数组形式包含 source 键 - elseif (is_array($volume) && isset($volume['source'])) { - return self::volumeProcessLongSyntax($volume, $hostPwd); - } - // 其他格式保持不变 - return $volume; - }, $volumes); - } - - /** - * 处理短语法格式的卷挂载 - * - * @param string $volume 原始卷配置字符串 - * @param string $hostPwd 主机工作目录路径 - * @return string 处理后的卷配置字符串 - */ - private static function volumeProcessShortSyntax(string $volume, string $hostPwd): string - { - $parts = explode(':', $volume, 3); - $sourcePath = $parts[0]; - - $newSourcePath = self::volumeConvertPath($sourcePath, $hostPwd); - - // 如果路径已更改,重建挂载配置 - if ($newSourcePath !== $sourcePath) { - $parts[0] = $newSourcePath; - return implode(':', $parts); - } - - return $volume; - } - - /** - * 处理长语法格式的卷挂载 - * - * @param array $volume 原始卷配置数组 - * @param string $hostPwd 主机工作目录路径 - * @return array 处理后的卷配置数组 - */ - private static function volumeProcessLongSyntax(array $volume, string $hostPwd): array - { - $newSourcePath = self::volumeConvertPath($volume['source'], $hostPwd); - - // 如果路径已更改,更新source - if ($newSourcePath !== $volume['source']) { - $volume['source'] = $newSourcePath; - } - - return $volume; - } - - /** - * 将相对路径转换为绝对路径 - * - * @param string $path 原始路径 - * @param string $hostPwd 主机工作目录路径 - * @return string 处理后的绝对路径 - */ - private static function volumeConvertPath(string $path, string $hostPwd): string - { - // 判断是否为相对路径,Docker卷挂载有以下几种情况: - // 1. 环境变量路径:包含${PWD}的路径,如 "${PWD}/data:/app/data" - // 这也是一种相对路径表示方式,需要标准化处理 - // 2. 显式相对路径:以./或../开头的路径,如 "./data:/app/data" 或 "../logs:/var/log" - // 3. 隐式相对路径:不以/开头,但中间包含/的路径,如 "data/logs:/app/logs" - // (纯名称如"data"没有/,通常是命名卷而非相对路径) - // 4. 绝对路径:以/开头的路径,如 "/var/run/docker.sock:/var/run/docker.sock" - // 绝对路径已经是完整路径,不需要转换 - // - // 注:Docker Compose在解析路径时,相对路径是相对于docker-compose.yml文件所在目录的 - // 我们需要将这些相对路径转换为绝对路径,以确保在不同环境中能正确挂载目录 - - // 处理${PWD}路径 - if (str_contains($path, '${PWD}')) { - // 将${PWD}替换为我们的标准路径格式 - return str_replace('${PWD}', $hostPwd, $path); - } - - // 处理./或../开头的显式相对路径 - if (str_starts_with($path, './') || str_starts_with($path, '../')) { - return $hostPwd . '/' . ltrim($path, './'); - } - - // 处理不以/开头的路径,且要么包含/(明确是路径而非命名卷) - if (!str_starts_with($path, '/') && str_contains($path, '/')) { - return $hostPwd . '/' . $path; - } - - // 命名卷或绝对路径,保持不变 - return $path; - } - - /** - * 获取多语言字段 - * - * @param mixed $field 要处理的字段值 - * @return mixed 处理后的字段值 - */ - public static function getMultiLanguageField(mixed $field): mixed - { - // 判断单语言字段,直接返回 - if (!is_array($field)) { - return $field; - } - - // 空数组检查 - if (empty($field)) { - return ""; - } - - $lang = Base::headerOrInput('language'); - - // 使用null合并运算符简化 - return $field[$lang] ?? $field[array_key_first($field)]; - } - - /** - * 检查应用是否可升级 - * - * @param array $config 应用配置 - * @param array $versions 可用版本列表 - * @return bool 如果可升级返回 true,否则返回 false - */ - public static function isUpgradeable(array $config, array $versions): bool - { - $upgradeable = false; - if ($config['status'] === 'installed' && !empty($versions)) { - foreach ($versions as $version) { - if (version_compare($config['install_version'], $version['version'], '<')) { - $upgradeable = true; - break; - } - } - } - return $upgradeable; - } - - /** - * 处理应用图标 - * - * @param string $appName 应用名称 - * @param array $iconFiles 待处理的图标文件名数组 - * @return string 处理后的图标URL,如果没有可用图标则返回空字符串 - */ - private static function processAppIcon(string $appName, array $iconFiles): string - { - $baseDir = base_path('docker/appstore/apps/' . $appName); - $iconDir = public_path('uploads/file/appstore/' . $appName); - - foreach ($iconFiles as $iconFile) { - // 如果图标为空,则跳过 - if (empty($iconFile)) { - continue; - } - - // 检查是否为URL方式(以http或https开头) - if (preg_match('/^https?:\/\//i', $iconFile)) { - return $iconFile; - } - - // 处理图标文件路径 - $iconFile = preg_replace('/^\/|^(\.\.\/)+|\.\//i', '', $iconFile); - $iconPath = $baseDir . '/' . $iconFile; - - if (file_exists($iconPath)) { - // 创建目标目录、路径 - $targetName = str_replace(['/', '\\'], '_', $iconFile); - $targetFile = $iconDir . '/' . $targetName; - - // 判断目标文件是否存在,或源文件是否比目标文件新 - if (!file_exists($targetFile) || filemtime($iconPath) > filemtime($targetFile)) { - Base::makeDir($iconDir); - copy($iconPath, $targetFile); - } - - // 返回图标URL - return Base::fillUrl('uploads/file/appstore/' . $appName . '/' . $targetName); - } - } - - return ''; - } - - /** - * 获取应用的可用版本列表 - * - * @param string $appName 应用名称 - * @return array 按语义化版本排序(从新到旧)的版本号数组 - */ - public static function getAvailableVersions(string $appName): array - { - $baseDir = base_path('docker/appstore/apps/' . $appName); - $versions = []; - - // 检查应用目录是否存在 - if (!is_dir($baseDir)) { - return $versions; - } - - // 遍历应用目录 - $dirs = scandir($baseDir); - foreach ($dirs as $dir) { - // 跳过当前目录、父目录和隐藏文件 - if ($dir === '.' || $dir === '..' || str_starts_with($dir, '.')) { - continue; - } - - $fullPath = $baseDir . '/' . $dir; - - // 检查是否为目录 - if (!is_dir($fullPath)) { - continue; - } - - // 检查是否存在docker-compose.yml文件 - $dockerComposeFile = $fullPath . '/docker-compose.yml'; - if (!file_exists($dockerComposeFile)) { - continue; - } - - // 检查目录名是否符合版本号格式 (如: 1.0.0, v1.0.0, 1.0, v1.0, 等) - // 支持的格式: x.y.z, vx.y.z, x.y, vx.y - if (preg_match('/^v?\d+(\.\d+){1,2}$/', $dir)) { - $versions[] = [ - 'version' => $dir, - ]; - } - } - - // 按版本号排序(从新到旧) - usort($versions, function ($a, $b) { - // 将版本号拆分为数组 - $partsA = explode('.', $a['version']); - $partsB = explode('.', $b['version']); - - // 比较主版本号 - if ($partsA[0] != $partsB[0]) { - return $partsB[0] <=> $partsA[0]; // 降序排列 - } - - // 比较次版本号(如果存在) - if (isset($partsA[1]) && isset($partsB[1]) && $partsA[1] != $partsB[1]) { - return $partsB[1] <=> $partsA[1]; - } - - // 比较修订版本号(如果存在) - if (isset($partsA[2]) && isset($partsB[2])) { - return $partsB[2] <=> $partsA[2]; - } elseif (isset($partsA[2])) { - return -1; // A有修订版本号,B没有 - } elseif (isset($partsB[2])) { - return 1; // B有修订版本号,A没有 - } - - return 0; // 版本号相同 - }); - - return $versions; - } - - /** - * 复制目录和文件 - * - * @param string $sourceDir 源目录 - * @param string $targetDir 目标目录 - * @param bool $recursive 是否递归复制 - * @return bool - */ - private static function copyDirAndFile(string $sourceDir, string $targetDir, bool $recursive = false): bool - { - if (!is_dir($sourceDir)) { - return false; - } - - // 创建目标目录 - if (!is_dir($targetDir)) { - if (!mkdir($targetDir, 0755, true)) { - return false; - } - } - - // 打开源目录 - $dir = opendir($sourceDir); - if (!$dir) { - return false; - } - - // 遍历源目录 - while (($file = readdir($dir)) !== false) { - // 跳过当前目录和父目录 - if ($file === '.' || $file === '..') { - continue; - } - - $sourceFile = $sourceDir . '/' . $file; - $targetFile = $targetDir . '/' . $file; - - if (is_dir($sourceFile)) { - // 如果是目录且需要递归复制 - if ($recursive) { - if (!self::copyDirAndFile($sourceFile, $targetFile, true)) { - closedir($dir); - return false; - } - } - } else { - // 复制文件 - if (!copy($sourceFile, $targetFile)) { - closedir($dir); - return false; - } - // 保持文件权限 - chmod($targetFile, fileperms($sourceFile)); - } - } - - closedir($dir); - return true; - } - - /** - * 清除缓存 - * - * @return void - */ - private static function clearCache(): void - { - foreach (self::CACHE_KEYS as $key) { - Cache::forget($key); - } - } - - /** - * 执行curl请求 - * - * @param $path - * @return array - */ - private static function curl($path): array - { - $url = "http://appstore/api/{$path}"; - $extra = [ - 'Content-Type' => 'application/json', - 'Authorization' => 'Bearer ' . md5(env('APP_KEY')), - ]; - - // 执行请求 - $res = Ihttp::ihttp_request($url, [], $extra); - if (Base::isError($res)) { - return Base::retError("请求错误", $res); - } - - // 解析响应 - $resData = Base::json2array($res['data']); - if ($resData['code'] != 200) { - return Base::retError("请求失败", $resData); - } - - // 返回结果 - return Base::retSuccess("success", $resData['data']); + return RequestContext::save($key, $installed); } } diff --git a/routes/web.php b/routes/web.php index 286040822..e3a58740c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,7 +2,6 @@ use Illuminate\Support\Facades\Route; use App\Http\Controllers\IndexController; -use App\Http\Controllers\Api\AppsController; use App\Http\Controllers\Api\TestController; use App\Http\Controllers\Api\FileController; use App\Http\Controllers\Api\UsersController; @@ -57,9 +56,6 @@ Route::prefix('api')->middleware(['webapi'])->group(function () { // 投诉 Route::any('complaint/{method}', ComplaintController::class); Route::any('complaint/{method}/{action}', ComplaintController::class); - // 应用 - Route::any('apps/{method}', AppsController::class); - Route::any('apps/{method}/{action}', AppsController::class); // 测试 Route::any('test/{method}', TestController::class); Route::any('test/{method}/{action}', TestController::class);