6124d6060c
Co-authored-by: Cursor <cursoragent@cursor.com>
287 lines
7.8 KiB
PHP
287 lines
7.8 KiB
PHP
<?php
|
|
/**
|
|
* R0Installer 云端配置 API 入口
|
|
*
|
|
* Nginx 配置示例:
|
|
* location /v3/ {
|
|
* try_files $uri $uri/ /api/index.php?$query_string;
|
|
* }
|
|
*
|
|
* Apache .htaccess:
|
|
* RewriteEngine On
|
|
* RewriteRule ^v3/(.*)$ api/index.php [QSA,L]
|
|
*/
|
|
|
|
require_once __DIR__ . '/db.php';
|
|
require_once __DIR__ . '/helpers.php';
|
|
|
|
$path = get_request_path();
|
|
$db = getDb();
|
|
|
|
// 路由匹配
|
|
if ($path === '/v3/config/projects') {
|
|
handleGetProjects($db);
|
|
}
|
|
elseif (preg_match('#^/v3/config/project/([a-zA-Z0-9_]+)$#', $path, $m)) {
|
|
handleGetProjectConfig($db, $m[1]);
|
|
}
|
|
elseif ($path === '/v3/config/installer') {
|
|
handleGetInstallerConfig($db);
|
|
}
|
|
elseif ($path === '/v3/version/index') {
|
|
$projectId = $_GET['project'] ?? '';
|
|
handleGetVersionIndex($db, $projectId);
|
|
}
|
|
elseif ($path === '/v3/update/check') {
|
|
$projectId = $_GET['project'] ?? '';
|
|
$version = $_GET['v'] ?? '';
|
|
handleUpdateCheck($db, $projectId, $version);
|
|
}
|
|
else {
|
|
json_error('Not Found', 404);
|
|
}
|
|
|
|
// ========== API 处理函数 ==========
|
|
|
|
function handleGetProjects($db) {
|
|
$rows = $db->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;
|
|
}
|