select('projects', [ 'project_id', 'display_name', 'description', 'icon_url', 'enabled' ]); $projects = []; foreach ($rows as $row) { $row['enabled'] = (bool)$row['enabled']; $projects[] = $row; } json_response(['projects' => $projects]); } function handleGetProjectConfig($db, $projectId) { $row = $db->get('projects', [ 'project_id', 'display_name', 'config_json' ], ['project_id' => $projectId]); if (!$row) { json_error('项目不存在', 1001); } $config = json_decode($row['config_json'], true); if (!$config) { json_error('项目配置解析失败', 1003); } $config['project_id'] = $row['project_id']; $config['display_name'] = $row['display_name']; json_response($config); } function handleGetInstallerConfig($db) { $row = $db->get('installer_config', '*', ['config_key' => 'global']); if (!$row) { json_error('配置不存在', 1003); } $filenameRules = json_decode($row['filename_rules'], true) ?: []; $commandRules = json_decode($row['command_rules'], true) ?: []; json_response([ 'installer_version' => $row['installer_version'], 'min_client_version' => $row['min_client_version'], 'filename_rules' => $filenameRules, 'command_rules' => $commandRules, 'default_project_id' => $row['default_project'], 'default_install_mode' => $row['default_mode'], ]); } function handleGetVersionIndex($db, $projectId) { if (empty($projectId)) { json_error('缺少 project 参数', 1002); } $row = $db->get('project_versions', '*', [ 'project_id' => $projectId, 'is_latest' => 1, ]); if (!$row) { $row = $db->get('project_versions', '*', [ 'project_id' => $projectId, 'ORDER' => ['id' => 'DESC'], 'LIMIT' => 1, ]); } if (!$row) { json_error('未找到版本信息', 1001); } $data = [ 'project_id' => $row['project_id'], 'version' => $row['version'], 'download_url_cdn' => $row['download_url'], 'threads' => (int)$row['threads'], 'changelog' => $row['changelog'] ?? '', ]; $extraDownloads = json_decode($row['extra_downloads'] ?? '{}', true); if ($extraDownloads) { foreach ($extraDownloads as $key => $val) { $data[$key] = $val; } } $runtimeUrls = json_decode($row['runtime_urls'] ?? '{}', true); if ($runtimeUrls) { $data['runtime_download_url'] = $runtimeUrls; } json_response($data); } function handleUpdateCheck($db, $projectId, $currentVersion) { if (empty($projectId)) { json_error('缺少 project 参数', 1002); } if (empty($currentVersion)) { json_error('缺少 v 参数', 1002); } $latestRow = $db->get('project_versions', '*', [ 'project_id' => $projectId, 'is_latest' => 1, ]); if (!$latestRow) { $latestRow = $db->get('project_versions', '*', [ 'project_id' => $projectId, 'ORDER' => ['id' => 'DESC'], 'LIMIT' => 1, ]); } if (!$latestRow) { json_response([ 'has_update' => false, 'latest_version' => $currentVersion, 'current_version' => $currentVersion, ]); return; } $latestVersion = $latestRow['version']; if (version_compare($currentVersion, $latestVersion, '>=')) { json_response([ 'has_update' => false, 'latest_version' => $latestVersion, 'current_version' => $currentVersion, ]); return; } $patches = $db->select('project_patches', '*', [ 'project_id' => $projectId, 'ORDER' => ['id' => 'ASC'], ]); $chain = buildPatchChain($patches, $currentVersion, $latestVersion); // 是否静默更新由“目标版本”(即最新版本)决定,云端按版本控制。 $silent = (bool)($latestRow['silent_update'] ?? 0); if ($chain && count($chain) === 1) { $p = $chain[0]; json_response([ 'has_update' => true, 'latest_version' => $latestVersion, 'current_version' => $currentVersion, 'update_type' => 'incremental', 'download_url' => $p['download_url'], 'file_size' => (string)$p['file_size'], 'total_size' => (string)$p['file_size'], 'changelog' => $p['changelog'] ?? '', 'threads' => (int)$latestRow['threads'], 'silent' => $silent, ]); return; } if ($chain && count($chain) > 1) { $totalSize = 0; $updates = []; foreach ($chain as $p) { $totalSize += (int)$p['file_size']; $updates[] = [ 'from_version' => $p['from_version'], 'to_version' => $p['to_version'], 'download_url' => $p['download_url'], 'file_size' => (string)$p['file_size'], 'changelog' => $p['changelog'] ?? '', ]; } json_response([ 'has_update' => true, 'latest_version' => $latestVersion, 'current_version' => $currentVersion, 'update_type' => 'multi_incremental', 'total_size' => (string)$totalSize, 'changelog' => '多版本增量更新', 'threads' => (int)$latestRow['threads'], 'incremental_updates' => $updates, 'silent' => $silent, ]); return; } json_response([ 'has_update' => true, 'latest_version' => $latestVersion, 'current_version' => $currentVersion, 'update_type' => 'full', 'download_url' => $latestRow['download_url'], 'file_size' => (string)$latestRow['file_size'], 'total_size' => (string)$latestRow['file_size'], 'changelog' => $latestRow['changelog'] ?? '', 'threads' => (int)$latestRow['threads'], 'silent' => $silent, ]); } function buildPatchChain($patches, $from, $to) { $graph = []; foreach ($patches as $p) { $graph[$p['from_version']][] = $p; } $visited = []; $queue = [[$from, []]]; while (!empty($queue)) { list($current, $path) = array_shift($queue); if ($current === $to) { return $path; } if (isset($visited[$current])) continue; $visited[$current] = true; if (isset($graph[$current])) { foreach ($graph[$current] as $edge) { if (!isset($visited[$edge['to_version']])) { $newPath = $path; $newPath[] = $edge; $queue[] = [$edge['to_version'], $newPath]; } } } } return null; }