$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) { // 如果未提供region,从数据库获取 if (!$region) { $listDb = getVpsListDB(); $vpsInfo = $listDb->queryOne( 'SELECT domain FROM vps_list WHERE config_id = ? AND vps_id = ?', [$configId, $instanceId] ); if ($vpsInfo && !empty($vpsInfo['domain'])) { $region = $vpsInfo['domain']; } else { Logger::error("[tencentPowerOn] 无法从数据库获取 region", 'tencentPowerOn'); return ['status' => 400, 'error' => '无法获取实例区域信息']; } } $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) { // 如果未提供region,从数据库获取 if (!$region) { $listDb = getVpsListDB(); $vpsInfo = $listDb->queryOne( 'SELECT domain FROM vps_list WHERE config_id = ? AND vps_id = ?', [$configId, $instanceId] ); if ($vpsInfo && !empty($vpsInfo['domain'])) { $region = $vpsInfo['domain']; } else { Logger::error("[tencentLighthousePowerOn] 无法从数据库获取 region", 'tencentLighthousePowerOn'); return ['status' => 400, 'error' => '无法获取实例区域信息']; } } $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) { // 如果未提供region,从数据库获取 if (!$region) { $listDb = getVpsListDB(); $vpsInfo = $listDb->queryOne( 'SELECT domain FROM vps_list WHERE config_id = ? AND vps_id = ?', [$configId, $instanceId] ); if ($vpsInfo && !empty($vpsInfo['domain'])) { $region = $vpsInfo['domain']; } else { Logger::error("[tencentPowerOff] 无法从数据库获取 region", 'tencentPowerOff'); return ['status' => 400, 'error' => '无法获取实例区域信息']; } } $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) { // 如果未提供region,从数据库获取 if (!$region) { $listDb = getVpsListDB(); $vpsInfo = $listDb->queryOne( 'SELECT domain FROM vps_list WHERE config_id = ? AND vps_id = ?', [$configId, $instanceId] ); if ($vpsInfo && !empty($vpsInfo['domain'])) { $region = $vpsInfo['domain']; } else { Logger::error("[tencentLighthousePowerOff] 无法从数据库获取 region", 'tencentLighthousePowerOff'); return ['status' => 400, 'error' => '无法获取实例区域信息']; } } $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) { // 如果未提供region,从数据库获取 if (!$region) { $listDb = getVpsListDB(); $vpsInfo = $listDb->queryOne( 'SELECT domain FROM vps_list WHERE config_id = ? AND vps_id = ?', [$configId, $instanceId] ); if ($vpsInfo && !empty($vpsInfo['domain'])) { $region = $vpsInfo['domain']; } else { Logger::error("[tencentReboot] 无法从数据库获取 region", 'tencentReboot'); return ['status' => 400, 'error' => '无法获取实例区域信息']; } } $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) { // 如果未提供region,从数据库获取 if (!$region) { $listDb = getVpsListDB(); $vpsInfo = $listDb->queryOne( 'SELECT domain FROM vps_list WHERE config_id = ? AND vps_id = ?', [$configId, $instanceId] ); if ($vpsInfo && !empty($vpsInfo['domain'])) { $region = $vpsInfo['domain']; } else { Logger::error("[tencentLighthouseReboot] 无法从数据库获取 region", 'tencentLighthouseReboot'); return ['status' => 400, 'error' => '无法获取实例区域信息']; } } $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); } } ?>