diff --git a/api-docs/reboot.php b/api-docs/reboot.php new file mode 100644 index 0000000..3d3ba9f --- /dev/null +++ b/api-docs/reboot.php @@ -0,0 +1,86 @@ +// 腾讯云API签名v3实现示例 +// 本代码基于腾讯云API签名v3文档实现: https://cloud.tencent.com/document/product/213/30654 +// 请严格按照文档说明使用,不建议随意修改签名相关代码 + $authorization, + "Content-Type" => "application/json; charset=utf-8", + "Host" => $host, + "X-TC-Action" => $action, + "X-TC-Timestamp" => $timestamp, + "X-TC-Version" => $version +]; +if ($req_region) { + $headers["X-TC-Region"] = $req_region; +} +if ($token) { + $headers["X-TC-Token"] = $token; +} + +try { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $endpoint); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(function ($k, $v) { return "$k: $v"; }, array_keys($headers), $headers)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + curl_close($ch); + + echo $response; +} catch (Exception $err) { + echo $err->getMessage(); +} + +?> \ No newline at end of file diff --git a/api-docs/shutdown.php b/api-docs/shutdown.php new file mode 100644 index 0000000..d772b25 --- /dev/null +++ b/api-docs/shutdown.php @@ -0,0 +1,86 @@ +// 腾讯云API签名v3实现示例 +// 本代码基于腾讯云API签名v3文档实现: https://cloud.tencent.com/document/product/213/30654 +// 请严格按照文档说明使用,不建议随意修改签名相关代码 + $authorization, + "Content-Type" => "application/json; charset=utf-8", + "Host" => $host, + "X-TC-Action" => $action, + "X-TC-Timestamp" => $timestamp, + "X-TC-Version" => $version +]; +if ($req_region) { + $headers["X-TC-Region"] = $req_region; +} +if ($token) { + $headers["X-TC-Token"] = $token; +} + +try { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $endpoint); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(function ($k, $v) { return "$k: $v"; }, array_keys($headers), $headers)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + curl_close($ch); + + echo $response; +} catch (Exception $err) { + echo $err->getMessage(); +} + +?> \ No newline at end of file diff --git a/api-docs/start.php b/api-docs/start.php new file mode 100644 index 0000000..222f473 --- /dev/null +++ b/api-docs/start.php @@ -0,0 +1,86 @@ +// 腾讯云API签名v3实现示例 +// 本代码基于腾讯云API签名v3文档实现: https://cloud.tencent.com/document/product/213/30654 +// 请严格按照文档说明使用,不建议随意修改签名相关代码 + $authorization, + "Content-Type" => "application/json; charset=utf-8", + "Host" => $host, + "X-TC-Action" => $action, + "X-TC-Timestamp" => $timestamp, + "X-TC-Version" => $version +]; +if ($req_region) { + $headers["X-TC-Region"] = $req_region; +} +if ($token) { + $headers["X-TC-Token"] = $token; +} + +try { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $endpoint); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(function ($k, $v) { return "$k: $v"; }, array_keys($headers), $headers)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + curl_close($ch); + + echo $response; +} catch (Exception $err) { + echo $err->getMessage(); +} + +?> \ No newline at end of file diff --git a/config_add.php b/config_add.php index 5145895..bf7087f 100644 --- a/config_add.php +++ b/config_add.php @@ -134,16 +134,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $apiKey = trim($_POST['api_key']); $autoMonitor = isset($_POST['auto_monitor']) ? 1 : 0; - if (empty($apiLabel) || empty($siteType) || empty($siteUrl) || empty($account) || empty($apiKey)) { - $message = '
❌ API标识、网站类型、网站链接、账户和密钥不能为空!
'; - } else { - // 验证URL格式:不允许包含路径 - $parsedUrl = parse_url($siteUrl); - if (!$parsedUrl || !isset($parsedUrl['scheme']) || !isset($parsedUrl['host'])) { - $message = '
❌ 网站链接格式错误!请输入完整的URL(例如: https://www.example.com)
'; - } elseif (isset($parsedUrl['path']) && $parsedUrl['path'] !== '/') { - $message = '
❌ 网站链接严禁包含路径部分!只允许输入根域名(例如: https://www.example.com),不要添加 /v1、/api 等路径
'; + // 将前端类型转换为数据库类型 + $dbSiteType = $siteType; + if ($siteType === 'tencent_cvm' || $siteType === 'tencent_lh') { + $dbSiteType = 'tencent'; + } + + // 验证必填字段(腾讯云需要siteUrl) + if ($siteType === 'tencent_cvm' || $siteType === 'tencent_lh') { + // 腾讯云:siteUrl必填(API域名) + if (empty($apiLabel) || empty($siteType) || empty($siteUrl) || empty($account) || empty($apiKey)) { + $message = '
❌ API标识、网站类型、API地址、SecretId和SecretKey不能为空!
'; } else { + $db = getVpsDB(); // 检查API标识是否已存在 @@ -153,11 +156,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } else { $configId = $db->insert( 'INSERT INTO configs (api_label, site_type, site_url, account, api_key, auto_monitor) VALUES (?, ?, ?, ?, ?, ?)', - [$apiLabel, $siteType, $siteUrl, $account, $apiKey, $autoMonitor] + [$apiLabel, $dbSiteType, $siteUrl, $account, $apiKey, $autoMonitor] ); if ($configId) { - $message = '
✅ 配置添加成功!ID: ' . $configId . '
'; + $typeName = ($siteType === 'tencent_cvm') ? 'CVM云服务器' : '轻量应用服务器'; + $message = '
✅ 腾讯云' . $typeName . '配置添加成功!ID: ' . $configId . '
'; // 如果是首次添加配置,提示运行install.sh $configCount = $db->queryOne('SELECT COUNT(*) as count FROM configs')['count']; @@ -193,12 +197,92 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } // 自动刷新VPS列表 - if ($siteType === 'mofang') { + if ($dbSiteType === 'mofang') { refreshVpsListForConfig($configId); $message .= '
✅ 已自动获取VPS列表
'; + } elseif ($dbSiteType === 'tencent') { + // 腾讯云需要区分CVM和轻量服务器 + require_once __DIR__ . '/tencentcloud.php'; + if ($siteType === 'tencent_cvm') { + refreshTencentCvmListForConfig($configId); + } else { + refreshTencentLighthouseListForConfig($configId); + } + $message .= '
✅ 已自动获取实例列表(遍历所有区域)
'; } } else { - $message = '
❌ 配置添加失败!
'; + $message = '
❌ 配置添加失败,请重试
'; + } + } + } + } else { + // 其他平台:siteUrl必填 + if (empty($apiLabel) || empty($siteType) || empty($siteUrl) || empty($account) || empty($apiKey)) { + $message = '
❌ API标识、网站类型、网站链接、账户和密钥不能为空!
'; + } else { + // 验证URL格式:不允许包含路径 + $parsedUrl = parse_url($siteUrl); + if (!$parsedUrl || !isset($parsedUrl['scheme']) || !isset($parsedUrl['host'])) { + $message = '
❌ 网站链接格式错误!请输入完整的URL(例如: https://www.example.com)
'; + } elseif (isset($parsedUrl['path']) && $parsedUrl['path'] !== '/') { + $message = '
❌ 网站链接严禁包含路径部分!只允许输入根域名(例如: https://www.example.com),不要添加 /v1、/api 等路径
'; + } else { + $db = getVpsDB(); + + // 检查API标识是否已存在 + $existingConfig = $db->queryOne('SELECT id FROM configs WHERE api_label = ?', [$apiLabel]); + if ($existingConfig) { + $message = '
❌ API标识 "' . htmlspecialchars($apiLabel) . '" 已存在,请使用其他标识!
'; + } else { + $configId = $db->insert( + 'INSERT INTO configs (api_label, site_type, site_url, account, api_key, auto_monitor) VALUES (?, ?, ?, ?, ?, ?)', + [$apiLabel, $siteType, $siteUrl, $account, $apiKey, $autoMonitor] + ); + + if ($configId) { + $message = '
✅ 配置添加成功!ID: ' . $configId . '
'; + + // 如果是首次添加配置,提示运行install.sh + $configCount = $db->queryOne('SELECT COUNT(*) as count FROM configs')['count']; + if ($configCount == 1) { + $message .= '
💡 这是第一个配置,请运行 app/install.sh 安装监控服务
'; + + // 尝试自动执行install.sh(需要PHP有执行权限) + $installScript = __DIR__ . '/app/install.sh'; + if (file_exists($installScript)) { + // 检查exec函数是否可用 + if (!function_exists('exec')) { + $message .= '
⚠️ exec函数被禁用,无法自动安装监控服务。请手动执行: sudo bash app/install.sh
'; + } else { + // 先设置可执行权限 + chmod($installScript, 0755); + + // 执行安装脚本 + $output = []; + $returnVar = 0; + exec("bash {$installScript} 2>&1", $output, $returnVar); + + if ($returnVar === 0) { + $message .= '
✅ 监控服务已自动安装并启动
'; + } else { + $message .= '
⚠️ 自动安装失败,请手动执行: sudo bash app/install.sh
'; + // 输出错误信息以便调试 + if (!empty($output)) { + $message .= '
查看错误详情
' . htmlspecialchars(implode("\n", $output)) . '
'; + } + } + } + } + } + + // 自动刷新VPS列表 + if ($siteType === 'mofang') { + refreshVpsListForConfig($configId); + $message .= '
✅ 已自动获取VPS列表
'; + } + } else { + $message = '
❌ 配置添加失败,请重试
'; + } } } } @@ -304,7 +388,8 @@ $configs = $db->query('SELECT * FROM configs ORDER BY id'); @@ -317,13 +402,13 @@ $configs = $db->query('SELECT * FROM configs ORDER BY id');
- - + +
- - + +
@@ -408,17 +493,56 @@ $configs = $db->query('SELECT * FROM configs ORDER BY id'); function toggleSiteUrl(siteType) { const urlGroup = document.getElementById('site_url_group'); const urlInput = document.getElementById('site_url_input'); + const accountLabel = document.querySelector('label[for="account_input"]') || document.querySelector('input[name="account"]').previousElementSibling; + const keyLabel = document.querySelector('label[for="api_key_input"]') || document.querySelector('input[name="api_key"]').previousElementSibling; if (siteType === 'mofang') { + // 魔方平台 + urlGroup.style.display = 'block'; urlInput.placeholder = '例如: https://www.heyunidc.cn'; + urlInput.required = true; + accountLabel.innerHTML = '账户 * (邮箱或手机号)'; + keyLabel.textContent = 'API密钥 *'; } else if (siteType === 'aliyun') { + // 阿里云 + urlGroup.style.display = 'block'; urlInput.placeholder = '例如: https://ecs.aliyuncs.com'; - } else if (siteType === 'tencent') { - urlInput.placeholder = '例如: https://cvm.tencentcloudapi.com'; + urlInput.required = true; + accountLabel.innerHTML = '账户 * (AccessKey ID)'; + keyLabel.textContent = 'API密钥 *'; + } else if (siteType === 'tencent_cvm') { + // CVM云服务器 + urlGroup.style.display = 'block'; + urlInput.value = 'https://cvm.tencentcloudapi.com'; + urlInput.placeholder = 'https://cvm.tencentcloudapi.com'; + urlInput.required = true; + accountLabel.innerHTML = 'SecretId * (腾讯云API密钥ID)'; + keyLabel.textContent = 'SecretKey *'; + } else if (siteType === 'tencent_lh') { + // 轻量应用服务器 + urlGroup.style.display = 'block'; + urlInput.value = 'https://lighthouse.tencentcloudapi.com'; + urlInput.placeholder = 'https://lighthouse.tencentcloudapi.com'; + urlInput.required = true; + accountLabel.innerHTML = 'SecretId * (腾讯云API密钥ID)'; + keyLabel.textContent = 'SecretKey *'; } else { + // 其他类型 + urlGroup.style.display = 'block'; urlInput.placeholder = '例如: https://www.example.com'; + urlInput.required = true; + accountLabel.innerHTML = '账户 *'; + keyLabel.textContent = 'API密钥 *'; } } + + // 页面加载时初始化 + document.addEventListener('DOMContentLoaded', function() { + const siteTypeSelect = document.querySelector('select[name="site_type"]'); + if (siteTypeSelect) { + toggleSiteUrl(siteTypeSelect.value); + } + }); diff --git a/diagnose_tencent.php b/diagnose_tencent.php new file mode 100644 index 0000000..595d1ff --- /dev/null +++ b/diagnose_tencent.php @@ -0,0 +1,108 @@ +query('SELECT * FROM configs WHERE site_type = ?', ['tencent']); + +if (empty($configs)) { + echo "❌ 未找到腾讯云配置\n"; + exit; +} + +echo "✅ 找到 " . count($configs) . " 个腾讯云配置\n\n"; + +foreach ($configs as $configIndex => $config) { + echo "--- 配置 #" . ($configIndex + 1) . " ---\n"; + echo "ID: {$config['id']}\n"; + echo "API标识: {$config['api_label']}\n"; + echo "API域名: {$config['site_url']}\n"; + echo "SecretId: " . substr($config['account'], 0, 15) . "...\n"; + echo "\n"; + + // 2. 测试区域获取 + echo "【步骤2】测试区域获取...\n"; + try { + $regions = getTencentRegions($config['id'], 'lighthouse'); + echo "✅ 获取到 " . count($regions) . " 个区域\n"; + echo "前3个区域: " . implode(', ', array_slice($regions, 0, 3)) . "\n\n"; + } catch (Exception $e) { + echo "❌ 区域获取失败: " . $e->getMessage() . "\n\n"; + continue; + } + + // 3. 测试实例列表获取 + echo "【步骤3】测试实例列表获取...\n"; + if (!empty($regions)) { + $testRegion = $regions[0]; + echo "测试区域: {$testRegion}\n"; + + $result = tencentGetLighthouseList($config['id'], $testRegion, 0, 10); + + if (!$result) { + echo "❌ API返回null\n\n"; + continue; + } + + echo "HTTP状态码: {$result['status']}\n"; + + if (isset($result['error'])) { + echo "❌ API错误: {$result['error']}\n"; + if (isset($result['code'])) { + echo "错误代码: {$result['code']}\n"; + } + if (isset($result['request_id'])) { + echo "Request ID: {$result['request_id']}\n"; + } + echo "\n"; + continue; + } + + if ($result['status'] !== 200) { + echo "❌ HTTP状态码异常: {$result['status']}\n\n"; + continue; + } + + $instances = $result['data']['InstanceSet'] ?? []; + $totalCount = $result['data']['TotalCount'] ?? 0; + + echo "✅ API调用成功\n"; + echo "总实例数: {$totalCount}\n"; + echo "当前页实例数: " . count($instances) . "\n\n"; + + if (!empty($instances)) { + echo "【步骤4】检查第一个实例数据...\n"; + $instance = $instances[0]; + echo "实例ID: " . ($instance['InstanceId'] ?? 'N/A') . "\n"; + echo "实例名称: " . ($instance['InstanceName'] ?? 'N/A') . "\n"; + echo "状态: " . ($instance['InstanceState'] ?? 'N/A') . "\n"; + echo "CPU: " . ($instance['CPU'] ?? 'N/A') . "核\n"; + echo "内存: " . ($instance['Memory'] ?? 'N/A') . "GB\n"; + + // 检查带宽 + if (isset($instance['InternetAccessible'])) { + echo "InternetAccessible: 存在\n"; + if (isset($instance['InternetAccessible']['InternetMaxBandwidthOut'])) { + echo "带宽: " . $instance['InternetAccessible']['InternetMaxBandwidthOut'] . "Mbps\n"; + } else { + echo "带宽字段: 不存在\n"; + } + } else { + echo "InternetAccessible: 不存在\n"; + } + + echo "\n完整数据结构:\n"; + echo json_encode($instance, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n"; + } else { + echo "⚠️ 该区域没有实例\n"; + } + } + + echo "\n" . str_repeat("=", 60) . "\n\n"; +} + +echo "诊断完成!\n"; +?> diff --git a/index.php b/index.php index 1a47463..7c9ab0f 100644 --- a/index.php +++ b/index.php @@ -2,6 +2,7 @@ require_once __DIR__ . '/app/db_helper.php'; require_once __DIR__ . '/app/logger.php'; require_once __DIR__ . '/mofangidc.php'; +require_once __DIR__ . '/tencentcloud.php'; // 存储API_PASS $tokenFile = __DIR__ . '/app/pass.php'; @@ -34,23 +35,49 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($action && $hostId > 0 && $configId > 0) { $result = null; - if ($action === 'reboot') { - $result = mofangHardReboot($configId, $hostId); - } elseif ($action === 'on') { - $result = mofangPowerOn($configId, $hostId); - } elseif ($action === 'off') { - $result = mofangPowerOff($configId, $hostId); + // 获取配置信息以确定平台类型 + $db = getVpsDB(); + $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); + + if ($config) { + if ($config['site_type'] === 'mofang') { + if ($action === 'reboot') { + $result = mofangHardReboot($configId, $hostId); + } elseif ($action === 'on') { + $result = mofangPowerOn($configId, $hostId); + } elseif ($action === 'off') { + $result = mofangPowerOff($configId, $hostId); + } + } elseif ($config['site_type'] === 'tencent') { + // 腾讯云智能操作(自动识别CVM或轻量服务器) + if ($action === 'reboot') { + $result = tencentSmartReboot($configId, $hostId); + } elseif ($action === 'on') { + $result = tencentSmartPowerOn($configId, $hostId); + } elseif ($action === 'off') { + $result = tencentSmartPowerOff($configId, $hostId); + } + } } if ($result && isset($result['status']) && $result['status'] === 200) { $message = '

✅ 操作成功

VPS #' . $hostId . ' 已成功执行操作

'; } else { - $errorMsg = $result['msg'] ?? '未知错误'; + $errorMsg = $result['error'] ?? $result['msg'] ?? '未知错误'; $message = '

❌ 操作失败

' . htmlspecialchars($errorMsg) . '

'; } } elseif ($action === 'refresh_vps_list') { // 手动刷新VPS列表 - $count = refreshAllVpsLists(); + $count = 0; + + // 刷新魔方平台的VPS列表 + $mofangCount = refreshAllVpsLists(); + $count += $mofangCount; + + // 刷新腾讯云的CVM和轻量应用服务器列表 + $tencentCount = refreshAllTencentVpsLists(); + $count += $tencentCount; + $message = '

✅ 刷新完成

已成功刷新 ' . $count . ' 个配置的VPS列表

'; // 获取每个VPS的详细信息并更新数据库 @@ -59,47 +86,47 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $configs = $configDb->query('SELECT * FROM configs'); foreach ($configs as $config) { - if ($config['site_type'] !== 'mofang') { - continue; // 目前只支持魔方平台 - } - - // 获取该配置下的所有VPS - $vpsItems = $listDb->query('SELECT vps_id FROM vps_list WHERE config_id = ?', [$config['id']]); - - foreach ($vpsItems as $vpsItem) { - $vpsId = $vpsItem['vps_id']; + if ($config['site_type'] === 'mofang') { + // 获取该配置下的所有VPS + $vpsItems = $listDb->query('SELECT vps_id FROM vps_list WHERE config_id = ?', [$config['id']]); - // 获取VPS详细信息 - $details = mofangGetVpsDetails($config['id'], $vpsId); - - if ($details && isset($details['status']) && $details['status'] === 200 && isset($details['data']['host'])) { - $host = $details['data']['host']; + foreach ($vpsItems as $vpsItem) { + $vpsId = $vpsItem['vps_id']; - // 解析CPU和内存信息 - $cpuCores = null; - $memorySize = null; + // 获取VPS详细信息 + $details = mofangGetVpsDetails($config['id'], $vpsId); - if (isset($host['config_option']) && is_array($host['config_option'])) { - foreach ($host['config_option'] as $option) { - if ($option['key'] === 'cpu' && isset($option['value'])) { - // 提取数字,例如 "16核" -> 16 - preg_match('/(\d+)/', $option['value'], $matches); - if (!empty($matches)) { - $cpuCores = intval($matches[1]); + if ($details && isset($details['status']) && $details['status'] === 200 && isset($details['data']['host'])) { + $host = $details['data']['host']; + + // 解析CPU和内存信息 + $cpuCores = null; + $memorySize = null; + + if (isset($host['config_option']) && is_array($host['config_option'])) { + foreach ($host['config_option'] as $option) { + if ($option['key'] === 'cpu' && isset($option['value'])) { + preg_match('/(\d+)/', $option['value'], $matches); + if (!empty($matches)) { + $cpuCores = intval($matches[1]); + } + } + if ($option['key'] === 'memory' && isset($option['value'])) { + $memorySize = $option['value']; } } - if ($option['key'] === 'memory' && isset($option['value'])) { - $memorySize = $option['value']; // 例如 "16G" - } } + + // 更新数据库 + $listDb->execute( + 'UPDATE vps_list SET cpu_cores = ?, memory_size = ?, last_check = CURRENT_TIMESTAMP WHERE config_id = ? AND vps_id = ?', + [$cpuCores, $memorySize, $config['id'], $vpsId] + ); } - - // 更新数据库 - $listDb->execute( - 'UPDATE vps_list SET cpu_cores = ?, memory_size = ?, last_check = CURRENT_TIMESTAMP WHERE config_id = ? AND vps_id = ?', - [$cpuCores, $memorySize, $config['id'], $vpsId] - ); } + } elseif ($config['site_type'] === 'tencent') { + // 腾讯云实例已在refreshAllTencentVpsLists中更新了详细信息,这里不需要额外处理 + Logger::info("[refresh_vps_list] 腾讯云配置 {$config['id']} 已刷新", 'index.php'); } } @@ -111,10 +138,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $db = getVpsDB(); $configs = $db->query('SELECT * FROM configs ORDER BY id'); -// 构建配置ID到api_label的映射 +// 构建配置ID到配置的映射 $configMap = []; foreach ($configs as $config) { - $configMap[$config['id']] = $config['api_label']; + $configMap[$config['id']] = $config; // 存储完整配置信息 } // 获取所有VPS列表(从vpslist.db查询) @@ -124,7 +151,7 @@ $vpsList = $listDb->query('SELECT * FROM vps_list ORDER BY config_id, vps_id'); // 为每个VPS添加api_label foreach ($vpsList as &$vps) { $configId = $vps['config_id']; - $vps['api_label'] = $configMap[$configId] ?? 'Unknown'; + $vps['api_label'] = $configMap[$configId]['api_label'] ?? 'Unknown'; } unset($vps); @@ -503,9 +530,23 @@ foreach ($vpsList as $vps) { + + + + diff --git a/tencentcloud.php b/tencentcloud.php new file mode 100644 index 0000000..272a32f --- /dev/null +++ b/tencentcloud.php @@ -0,0 +1,747 @@ + $value) { + if ($queryStr !== '') { + $queryStr .= '&'; + } + $queryStr .= rawurlencode($key) . '=' . rawurlencode($value); + } + $signStr .= $queryStr; + + // 计算HMAC-SHA1签名 + $signature = base64_encode(hash_hmac('sha1', $signStr, $secretKey, true)); + + return $signature; +} + +/** + * 发送腾讯云API请求 + * @param string $configId 配置ID + * @param string $service 服务类型: cvm 或 lighthouse + * @param string $action API动作 + * @param array $params 请求参数 + * @param string $region 区域(可选,默认使用配置中的区域) + * @return array|null + */ +function tencentApiRequest($configId, $service, $action, $params = [], $region = null) { + $db = getVpsDB(); + $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); + + if (!$config) { + Logger::error("配置ID {$configId} 不存在", 'tencentApiRequest'); + return null; + } + + $secretId = $config['account']; + $secretKey = $config['api_key']; + + // 确定使用的区域 + if (!$region) { + $region = 'ap-guangzhou'; + } + + // 确定API域名 + $host = ($service === 'lighthouse') ? 'lighthouse.tencentcloudapi.com' : 'cvm.tencentcloudapi.com'; + + // 公共参数 + $commonParams = [ + 'Action' => $action, + 'Version' => ($service === 'lighthouse') ? '2020-03-24' : '2017-03-12', + 'Region' => $region, + 'SecretId' => $secretId, + 'Timestamp' => time(), + 'Nonce' => rand(10000, 99999), + ]; + + // 合并参数 + $allParams = array_merge($commonParams, $params); + + // 生成签名 + $signature = generateTencentSignature( + 'GET', + $host, + '/', + $allParams, + $secretKey + ); + + $allParams['Signature'] = $signature; + + // 构建URL + $url = 'https://' . $host . '/?' . http_build_query($allParams); + + // 发送请求 + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curlError = curl_error($ch); + curl_close($ch); + + if ($curlError) { + Logger::error("cURL错误: {$curlError}", 'tencentApiRequest'); + return ['status' => 0, 'error' => $curlError]; + } + + if ($httpCode !== 200) { + Logger::error("HTTP请求失败: {$httpCode}", 'tencentApiRequest'); + return ['status' => $httpCode, 'error' => 'HTTP请求失败']; + } + + $result = json_decode($response, true); + + if (!$result) { + Logger::error("JSON解析失败", 'tencentApiRequest'); + return ['status' => 500, 'error' => '响应解析失败']; + } + + // 检查响应 + if (isset($result['Response']['Error'])) { + $error = $result['Response']['Error']; + Logger::error("API错误: {$error['Code']} - {$error['Message']}", 'tencentApiRequest'); + return ['status' => 400, 'error' => $error['Message'], 'code' => $error['Code']]; + } + + return ['status' => 200, 'data' => $result['Response'], 'service' => $service, 'region' => $region]; +} + +/** + * 获取所有可用区域列表 + */ +function getTencentRegions($configId, $service) { + return [ + 'ap-beijing', // 北京 + 'ap-chengdu', // 成都 + 'ap-guangzhou', // 广州 + 'ap-hongkong', // 香港 + 'ap-nanjing', // 南京 + 'ap-shanghai', // 上海 + 'ap-singapore', // 新加坡 + 'ap-tokyo', // 东京 + 'eu-frankfurt', // 法兰克福 + 'na-siliconvalley', // 硅谷 + 'na-toronto', // 多伦多 + ]; +} + +/** + * 获取CVM实例列表(单个区域) + */ +function tencentGetCvmList($configId, $region, $offset = 0, $limit = 100) { + $params = [ + 'Offset' => $offset, + 'Limit' => $limit, + ]; + + return tencentApiRequest($configId, 'cvm', 'DescribeInstances', $params, $region); +} + +/** + * 获取轻量应用服务器列表(单个区域) + */ +function tencentGetLighthouseList($configId, $region, $offset = 0, $limit = 100) { + $params = [ + 'Offset' => $offset, + 'Limit' => $limit, + ]; + + return tencentApiRequest($configId, 'lighthouse', 'DescribeInstances', $params, $region); +} + +/** + * 获取CVM实例详细信息 + */ +function tencentGetCvmDetails($configId, $instanceId, $region = null, $updateDb = true) { + $params = [ + 'InstanceIds.0' => $instanceId, + ]; + + $result = tencentApiRequest($configId, 'cvm', 'DescribeInstances', $params, $region); + + // 如果获取成功且需要更新数据库 + if ($updateDb && $result && isset($result['status']) && $result['status'] === 200) { + $instances = $result['data']['InstanceSet'] ?? []; + + if (!empty($instances)) { + updateCvmToDatabase($configId, $instances[0], 'cvm'); + } + } + + return $result; +} + +/** + * 获取轻量应用服务器详细信息 + */ +function tencentGetLighthouseDetails($configId, $instanceId, $region = null, $updateDb = true) { + $params = [ + 'InstanceIds.0' => $instanceId, + ]; + + $result = tencentApiRequest($configId, 'lighthouse', 'DescribeInstances', $params, $region); + + // 如果获取成功且需要更新数据库 + if ($updateDb && $result && isset($result['status']) && $result['status'] === 200) { + $instances = $result['data']['InstanceSet'] ?? []; + + if (!empty($instances)) { + updateCvmToDatabase($configId, $instances[0], 'lighthouse'); + } + } + + return $result; +} + +/** + * 获取CVM实例状态 + */ +function tencentGetCvmStatus($configId, $instanceId, $region = null, $updateDb = true) { + $params = [ + 'InstanceIds.0' => $instanceId, + ]; + + $result = tencentApiRequest($configId, 'cvm', 'DescribeInstancesStatus', $params, $region); + + // 如果获取成功且需要更新数据库 + if ($updateDb && $result && isset($result['status']) && $result['status'] === 200) { + $statusSet = $result['data']['InstanceStatusSet'] ?? []; + + if (!empty($statusSet)) { + $statusInfo = $statusSet[0]; + $rawStatus = $statusInfo['InstanceState'] ?? 'UNKNOWN'; + + // 状态映射 + $statusMap = [ + 'PENDING' => 'process', + 'LAUNCHING' => 'process', + 'RUNNING' => 'on', + 'STOPPING' => 'process', + 'STOPPED' => 'off', + 'REBOOTING' => 'process', + 'SHUTDOWN' => 'off', + 'TERMINATING' => 'process', + 'UNKNOWN' => 'unknown' + ]; + + $status = $statusMap[$rawStatus] ?? 'unknown'; + + updateVpsStatusInDatabase($configId, $instanceId, $status); + } + } + + return $result; +} + +/** + * 获取轻量应用服务器状态 + * 注意:轻量服务器没有独立的DescribeInstancesStatus接口,需要使用DescribeInstances + */ +function tencentGetLighthouseStatus($configId, $instanceId, $region = null, $updateDb = true) { + // 轻量服务器使用DescribeInstances接口获取状态 + $params = [ + 'InstanceIds.0' => $instanceId, + ]; + + $result = tencentApiRequest($configId, 'lighthouse', 'DescribeInstances', $params, $region); + + // 如果获取成功且需要更新数据库 + if ($updateDb && $result && isset($result['status']) && $result['status'] === 200) { + $instances = $result['data']['InstanceSet'] ?? []; + + if (!empty($instances)) { + $instanceInfo = $instances[0]; + $rawStatus = $instanceInfo['InstanceState'] ?? 'UNKNOWN'; + + // 状态映射(轻量服务器状态与CVM类似) + $statusMap = [ + 'PENDING' => 'process', + 'LAUNCHING' => 'process', + 'RUNNING' => 'on', + 'STOPPING' => 'process', + 'STOPPED' => 'off', + 'REBOOTING' => 'process', + 'SHUTDOWN' => 'off', + 'TERMINATING' => 'process', + 'UNKNOWN' => 'unknown' + ]; + + $status = $statusMap[$rawStatus] ?? 'unknown'; + + updateVpsStatusInDatabase($configId, $instanceId, $status); + } + } + + return $result; +} + +/** + * 启动CVM实例 + */ +function tencentPowerOn($configId, $instanceId, $region = null) { + $params = [ + 'InstanceIds.0' => $instanceId, + ]; + + $result = tencentApiRequest($configId, 'cvm', 'StartInstances', $params, $region); + + // 开机操作后立即获取状态并更新数据库 + if ($result && isset($result['status']) && $result['status'] === 200) { + sleep(2); + tencentGetCvmStatus($configId, $instanceId, $region, true); + } + + return $result; +} + +/** + * 启动轻量应用服务器 + */ +function tencentLighthousePowerOn($configId, $instanceId, $region = null) { + $params = [ + 'InstanceIds.0' => $instanceId, + ]; + + $result = tencentApiRequest($configId, 'lighthouse', 'StartInstances', $params, $region); + + // 开机操作后立即获取状态并更新数据库 + if ($result && isset($result['status']) && $result['status'] === 200) { + sleep(2); + tencentGetLighthouseStatus($configId, $instanceId, $region, true); + } + + return $result; +} + +/** + * 停止CVM实例 + */ +function tencentPowerOff($configId, $instanceId, $region = null) { + $params = [ + 'InstanceIds.0' => $instanceId, + ]; + + $result = tencentApiRequest($configId, 'cvm', 'StopInstances', $params, $region); + + // 关机操作后立即获取状态并更新数据库 + if ($result && isset($result['status']) && $result['status'] === 200) { + sleep(2); + tencentGetCvmStatus($configId, $instanceId, $region, true); + } + + return $result; +} + +/** + * 停止轻量应用服务器 + */ +function tencentLighthousePowerOff($configId, $instanceId, $region = null) { + $params = [ + 'InstanceIds.0' => $instanceId, + ]; + + $result = tencentApiRequest($configId, 'lighthouse', 'StopInstances', $params, $region); + + // 关机操作后立即获取状态并更新数据库 + if ($result && isset($result['status']) && $result['status'] === 200) { + sleep(2); + tencentGetLighthouseStatus($configId, $instanceId, $region, true); + } + + return $result; +} + +/** + * 重启CVM实例 + */ +function tencentReboot($configId, $instanceId, $region = null) { + $params = [ + 'InstanceIds.0' => $instanceId, + ]; + + $result = tencentApiRequest($configId, 'cvm', 'RebootInstances', $params, $region); + + // 重启操作后立即获取状态并更新数据库 + if ($result && isset($result['status']) && $result['status'] === 200) { + sleep(3); + tencentGetCvmStatus($configId, $instanceId, $region, true); + } + + return $result; +} + +/** + * 重启轻量应用服务器 + */ +function tencentLighthouseReboot($configId, $instanceId, $region = null) { + $params = [ + 'InstanceIds.0' => $instanceId, + ]; + + $result = tencentApiRequest($configId, 'lighthouse', 'RebootInstances', $params, $region); + + // 重启操作后立即获取状态并更新数据库 + if ($result && isset($result['status']) && $result['status'] === 200) { + sleep(3); + tencentGetLighthouseStatus($configId, $instanceId, $region, true); + } + + return $result; +} + +/** + * 将CVM/轻量服务器信息更新到数据库 + */ +function updateCvmToDatabase($configId, $instance, $serviceType) { + $listDb = getVpsListDB(); + + $vpsId = $instance['InstanceId']; + $region = $instance['Placement']['Zone'] ?? ''; + + // 提取CPU和内存信息 + $cpuCores = $instance['CPU'] ?? null; + // 注意:腾讯云API返回的Memory单位已经是GB,不需要转换 + $memorySize = isset($instance['Memory']) ? $instance['Memory'] . 'G' : null; + + // 提取系统盘信息 + $diskSize = null; + if (isset($instance['SystemDisk']) && isset($instance['SystemDisk']['DiskSize'])) { + $diskSize = $instance['SystemDisk']['DiskSize'] . 'G'; + } + + // 提取带宽信息 + $bandwidth = null; + // CVM和轻量服务器都使用InternetAccessible.InternetMaxBandwidthOut + if (isset($instance['InternetAccessible']) && isset($instance['InternetAccessible']['InternetMaxBandwidthOut'])) { + $bandwidth = $instance['InternetAccessible']['InternetMaxBandwidthOut'] . 'Mbps'; + } + + // 提取操作系统信息 + $osType = $instance['OsName'] ?? ($instance['Platform'] ?? null); + + // 提取IP地址 + $ipAddress = null; + if ($serviceType === 'cvm') { + if (isset($instance['PublicIpAddresses']) && !empty($instance['PublicIpAddresses'])) { + $ipAddress = $instance['PublicIpAddresses'][0]; + } + } else { + // 轻量服务器的公网IP + if (isset($instance['PublicAddresses']) && !empty($instance['PublicAddresses'])) { + $ipAddress = $instance['PublicAddresses'][0]; + } + } + + // 提取实例名称 + $productName = $instance['InstanceName'] ?? null; + + // 提取到期时间 + $nextduedate = null; + if (isset($instance['ExpiredTime'])) { + $nextduedate = strtotime($instance['ExpiredTime']); + } + + // 检查记录是否存在 + $existing = $listDb->queryOne( + 'SELECT id FROM vps_list WHERE config_id = ? AND vps_id = ?', + [$configId, $vpsId] + ); + + if ($existing) { + // 更新现有记录 + $listDb->execute( + 'UPDATE vps_list SET domain = ?, ip_address = ?, product_name = ?, cpu_cores = ?, memory_size = ?, disk_size = ?, bandwidth = ?, os_type = ?, nextduedate = ?, last_check = CURRENT_TIMESTAMP WHERE id = ?', + [ + $region, + $ipAddress, + $productName, + $cpuCores, + $memorySize, + $diskSize, + $bandwidth, + $osType, + $nextduedate, + $existing['id'] + ] + ); + Logger::info("[updateCvmToDatabase] {$serviceType} {$vpsId} 详细信息已更新", 'updateCvmToDatabase'); + } else { + // 插入新记录 + $listDb->execute( + 'INSERT INTO vps_list (config_id, vps_id, domain, ip_address, product_name, cpu_cores, memory_size, disk_size, bandwidth, os_type, status, nextduedate, last_check) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, CURRENT_TIMESTAMP)', + [ + $configId, + $vpsId, + $region, + $ipAddress, + $productName, + $cpuCores, + $memorySize, + $diskSize, + $bandwidth, + $osType, + $nextduedate + ] + ); + Logger::info("[updateCvmToDatabase] {$serviceType} {$vpsId} 新记录已插入", 'updateCvmToDatabase'); + } +} + +/** + * 更新VPS状态到数据库 + */ +function updateVpsStatusInDatabase($configId, $vpsId, $status) { + $listDb = getVpsListDB(); + + $vpsInfo = $listDb->queryOne( + 'SELECT id FROM vps_list WHERE config_id = ? AND vps_id = ?', + [$configId, $vpsId] + ); + + if ($vpsInfo) { + $listDb->execute( + 'UPDATE vps_list SET status = ?, last_check = CURRENT_TIMESTAMP WHERE id = ?', + [$status, $vpsInfo['id']] + ); + Logger::info("[updateVpsStatusInDatabase] VPS {$vpsId} 状态已更新为: {$status}", 'updateVpsStatusInDatabase'); + } else { + $listDb->execute( + 'INSERT INTO vps_list (config_id, vps_id, status, last_check) VALUES (?, ?, ?, CURRENT_TIMESTAMP)', + [$configId, $vpsId, $status] + ); + Logger::info("[updateVpsStatusInDatabase] VPS {$vpsId} 新记录已插入,状态: {$status}", 'updateVpsStatusInDatabase'); + } +} + +/** + * 刷新指定配置的CVM列表(遍历所有区域) + */ +function refreshTencentCvmListForConfig($configId) { + $db = getVpsDB(); + $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); + + if (!$config || $config['site_type'] !== 'tencent') { + return false; + } + + // 先获取区域列表 + $regions = getTencentRegions($configId, 'cvm'); + $totalInstances = 0; + + Logger::info("[refreshTencentCvmListForConfig] 开始遍历所有区域获取CVM列表", 'refreshTencentCvmListForConfig'); + + foreach ($regions as $region) { + try { + $result = tencentGetCvmList($configId, $region); + + if ($result && isset($result['status']) && $result['status'] === 200) { + $instances = $result['data']['InstanceSet'] ?? []; + + if (!empty($instances)) { + Logger::info("[refreshTencentCvmListForConfig] 区域 {$region} 找到 " . count($instances) . " 台CVM", 'refreshTencentCvmListForConfig'); + + foreach ($instances as $instance) { + updateCvmToDatabase($configId, $instance, 'cvm'); + + // 获取状态 + tencentGetCvmStatus($configId, $instance['InstanceId'], $region, true); + + $totalInstances++; + + // 避免频繁请求 + usleep(300000); // 300毫秒 + } + } + } else { + Logger::warning("[refreshTencentCvmListForConfig] 区域 {$region} 获取失败: " . ($result['msg'] ?? '未知错误'), 'refreshTencentCvmListForConfig'); + } + } catch (Exception $e) { + Logger::error("[refreshTencentCvmListForConfig] 区域 {$region} 异常: " . $e->getMessage(), 'refreshTencentCvmListForConfig'); + } + + // 区域之间稍作延迟 + usleep(500000); // 500毫秒 + } + + Logger::info("[refreshTencentCvmListForConfig] CVM列表刷新完成,共获取 {$totalInstances} 台实例", 'refreshTencentCvmListForConfig'); + + return true; +} + +/** + * 刷新指定配置的轻量应用服务器列表(遍历所有区域) + */ +function refreshTencentLighthouseListForConfig($configId) { + $db = getVpsDB(); + $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); + + if (!$config || $config['site_type'] !== 'tencent') { + return false; + } + + // 先获取区域列表 + $regions = getTencentRegions($configId, 'lighthouse'); + $totalInstances = 0; + + Logger::info("[refreshTencentLighthouseListForConfig] 开始遍历所有区域获取轻量应用服务器列表", 'refreshTencentLighthouseListForConfig'); + + foreach ($regions as $region) { + try { + $result = tencentGetLighthouseList($configId, $region); + + if ($result && isset($result['status']) && $result['status'] === 200) { + $instances = $result['data']['InstanceSet'] ?? []; + + if (!empty($instances)) { + Logger::info("[refreshTencentLighthouseListForConfig] 区域 {$region} 找到 " . count($instances) . " 台轻量服务器", 'refreshTencentLighthouseListForConfig'); + + foreach ($instances as $instance) { + updateCvmToDatabase($configId, $instance, 'lighthouse'); + + // 获取状态 + tencentGetLighthouseStatus($configId, $instance['InstanceId'], $region, true); + + $totalInstances++; + + // 避免频繁请求 + usleep(300000); // 300毫秒 + } + } + } else { + Logger::warning("[refreshTencentLighthouseListForConfig] 区域 {$region} 获取失败: " . ($result['msg'] ?? '未知错误'), 'refreshTencentLighthouseListForConfig'); + } + } catch (Exception $e) { + Logger::error("[refreshTencentLighthouseListForConfig] 区域 {$region} 异常: " . $e->getMessage(), 'refreshTencentLighthouseListForConfig'); + } + + // 区域之间稍作延迟 + usleep(500000); // 500毫秒 + } + + Logger::info("[refreshTencentLighthouseListForConfig] 轻量应用服务器列表刷新完成,共获取 {$totalInstances} 台实例", 'refreshTencentLighthouseListForConfig'); + + return true; +} + +/** + * 刷新所有腾讯云配置的实例列表(CVM + 轻量) + */ +function refreshAllTencentVpsLists() { + $db = getVpsDB(); + $configs = $db->query('SELECT * FROM configs WHERE site_type = ?', ['tencent']); + + $successCount = 0; + + foreach ($configs as $config) { + // 刷新CVM列表 + if (refreshTencentCvmListForConfig($config['id'])) { + $successCount++; + } + + // 刷新轻量应用服务器列表 + if (refreshTencentLighthouseListForConfig($config['id'])) { + $successCount++; + } + } + + return $successCount; +} + +/** + * 根据实例ID判断是CVM还是轻量服务器,并执行相应操作 + */ +function tencentGetInstanceType($instanceId) { + // CVM实例ID格式: ins-xxxxxxxx + // 轻量服务器实例ID格式: lhins-xxxxxxxx + if (strpos($instanceId, 'lhins-') === 0) { + return 'lighthouse'; + } + return 'cvm'; +} + +/** + * 智能开机(自动识别实例类型) + */ +function tencentSmartPowerOn($configId, $instanceId, $region = null) { + $type = tencentGetInstanceType($instanceId); + + if ($type === 'lighthouse') { + return tencentLighthousePowerOn($configId, $instanceId, $region); + } else { + return tencentPowerOn($configId, $instanceId, $region); + } +} + +/** + * 智能关机(自动识别实例类型) + */ +function tencentSmartPowerOff($configId, $instanceId, $region = null) { + $type = tencentGetInstanceType($instanceId); + + if ($type === 'lighthouse') { + return tencentLighthousePowerOff($configId, $instanceId, $region); + } else { + return tencentPowerOff($configId, $instanceId, $region); + } +} + +/** + * 智能重启(自动识别实例类型) + */ +function tencentSmartReboot($configId, $instanceId, $region = null) { + $type = tencentGetInstanceType($instanceId); + + if ($type === 'lighthouse') { + return tencentLighthouseReboot($configId, $instanceId, $region); + } else { + return tencentReboot($configId, $instanceId, $region); + } +} + +/** + * 智能获取状态(自动识别实例类型) + */ +function tencentSmartGetStatus($configId, $instanceId, $region = null, $updateDb = true) { + $type = tencentGetInstanceType($instanceId); + + if ($type === 'lighthouse') { + return tencentGetLighthouseStatus($configId, $instanceId, $region, $updateDb); + } else { + return tencentGetCvmStatus($configId, $instanceId, $region, $updateDb); + } +} + +/** + * 智能获取详情(自动识别实例类型) + */ +function tencentSmartGetDetails($configId, $instanceId, $region = null, $updateDb = true) { + $type = tencentGetInstanceType($instanceId); + + if ($type === 'lighthouse') { + return tencentGetLighthouseDetails($configId, $instanceId, $region, $updateDb); + } else { + return tencentGetCvmDetails($configId, $instanceId, $region, $updateDb); + } +} +?>