Initial commit: R0Installer source, patch generator, API backend and build scripts
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+286
@@ -0,0 +1,286 @@
|
||||
<?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;
|
||||
}
|
||||
Reference in New Issue
Block a user