diff --git a/aliyun.php b/aliyun.php new file mode 100644 index 0000000..7b64f7e --- /dev/null +++ b/aliyun.php @@ -0,0 +1,933 @@ + $value) { + if ($canonicalizedQueryString !== '') { + $canonicalizedQueryString .= '&'; + } + $canonicalizedQueryString .= aliyunEncode($key) . '=' . aliyunEncode($value); + } + + // 构造待签名字符串 + $stringToSign = $method . '&%2F&' . aliyunEncode($canonicalizedQueryString); + + // 计算HMAC-SHA1签名 + $signature = base64_encode(hash_hmac('sha1', $stringToSign, $secretKey . '&', true)); + + return $signature; +} + +/** + * 发送阿里云API请求 + * @param string $configId 配置ID + * @param string $action API动作 + * @param array $params 请求参数 + * @param string $region 区域 + * @param string $serviceType 服务类型: ecs 或 swas + * @return array|null + */ +function aliyunApiRequest($configId, $action, $params = [], $region = 'cn-hangzhou', $serviceType = 'ecs') { + $db = getVpsDB(); + $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); + + if (!$config) { + Logger::error("配置ID {$configId} 不存在", 'aliyunApiRequest'); + return null; + } + + $accessKeyId = $config['account']; + $accessKeySecret = $config['api_key']; + + Logger::debug("[aliyunApiRequest] Config ID: {$configId}, AccessKeyId: " . substr($accessKeyId, 0, 8) . "***", 'aliyunApiRequest'); + + // 确定API域名 + if (!$region || empty($region)) { + Logger::error("Region参数为空,无法构建API域名", 'aliyunApiRequest'); + return ['status' => 400, 'error' => 'Region参数为空']; + } + + if ($serviceType === 'swas') { + $host = 'swas.' . $region . '.aliyuncs.com'; + } else { + $host = 'ecs.' . $region . '.aliyuncs.com'; + } + + // 公共参数 + $commonParams = [ + 'Action' => $action, + 'Version' => ($serviceType === 'swas') ? '2020-06-01' : '2014-05-26', + 'Format' => 'JSON', + 'AccessKeyId' => $accessKeyId, + 'SignatureMethod' => 'HMAC-SHA1', + 'SignatureVersion' => '1.0', + 'SignatureNonce' => uniqid(), + 'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'), + 'RegionId' => $region, + ]; + + // 合并参数 + $allParams = array_merge($commonParams, $params); + + // 生成签名 + $signature = generateAliyunSignature( + 'GET', + $allParams, + $accessKeySecret + ); + + $allParams['Signature'] = $signature; + + // 构建URL + $url = 'https://' . $host . '/?' . http_build_query($allParams); + + Logger::debug("[aliyunApiRequest] URL: {$url}", 'aliyunApiRequest'); + Logger::debug("[aliyunApiRequest] Action: {$action}, Region: {$region}, ServiceType: {$serviceType}", 'aliyunApiRequest'); + + // 发送请求 + $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}", 'aliyunApiRequest'); + return ['status' => 0, 'error' => $curlError]; + } + + if ($httpCode !== 200) { + Logger::error("HTTP请求失败: {$httpCode}, 响应: {$response}", 'aliyunApiRequest'); + + // 尝试解析错误响应 + $errorResult = json_decode($response, true); + if ($errorResult && isset($errorResult['Message'])) { + return ['status' => $httpCode, 'error' => $errorResult['Message'], 'code' => $errorResult['Code'] ?? 'Unknown']; + } + + return ['status' => $httpCode, 'error' => 'HTTP请求失败 (' . $httpCode . ')']; + } + + $result = json_decode($response, true); + + if (!$result) { + Logger::error("JSON解析失败", 'aliyunApiRequest'); + return ['status' => 500, 'error' => '响应解析失败']; + } + + // 检查响应 + if (isset($result['Code'])) { + Logger::error("API错误: {$result['Code']} - {$result['Message']}", 'aliyunApiRequest'); + return ['status' => 400, 'error' => $result['Message'], 'code' => $result['Code']]; + } + + return ['status' => 200, 'data' => $result, 'region' => $region, 'service_type' => $serviceType]; +} + +/** + * 获取所有可用区域列表(ECS) + */ +function getAliyunRegions($configId) { + return [ + 'cn-qingdao', // 华北1(青岛) + 'cn-beijing', // 华北2(北京) + 'cn-zhangjiakou', // 华北3(张家口) + 'cn-huhehaote', // 华北5(呼和浩特) + 'cn-wulanchabu', // 华北6(乌兰察布) + 'cn-hangzhou', // 华东1(杭州) + 'cn-shanghai', // 华东2(上海) + 'cn-nanjing', // 华东5(南京) + 'cn-fuzhou', // 华东6(福州) + 'cn-shenzhen', // 华南1(深圳) + 'cn-heyuan', // 华南2(河源) + 'cn-guangzhou', // 华南3(广州) + 'cn-chengdu', // 西南1(成都) + 'cn-hongkong', // 中国香港 + 'ap-southeast-1', // 新加坡 + 'ap-southeast-5', // 印度尼西亚(雅加达) + 'ap-northeast-1', // 日本(东京) + 'ap-northeast-2', // 韩国(首尔) + 'ap-southeast-3', // 马来西亚(吉隆坡) + 'ap-southeast-6', // 菲律宾(马尼拉) + 'ap-southeast-7', // 泰国(曼谷) + 'us-west-1', // 美国(硅谷) + 'us-east-1', // 美国(弗吉尼亚) + 'eu-central-1', // 德国(法兰克福) + 'eu-west-1', // 英国(伦敦) + 'me-east-1', // 阿联酋(迪拜) + 'me-central-1', // 沙特(利雅得) + ]; +} + +/** + * 获取轻量应用服务器可用区域列表 + */ +function getAliyunSwasRegions($configId) { + return [ + 'cn-hangzhou', // 华东1(杭州) + 'cn-shanghai', // 华东2(上海) + 'cn-qingdao', // 华北1(青岛) + 'cn-beijing', // 华北2(北京) + 'cn-zhangjiakou', // 华北3(张家口) + 'cn-shenzhen', // 华南1(深圳) + 'cn-heyuan', // 华南2(河源) + 'cn-chengdu', // 西南1(成都) + 'cn-hongkong', // 中国香港 + 'ap-southeast-1', // 新加坡 + 'ap-northeast-1', // 日本(东京) + 'us-west-1', // 美国(硅谷) + 'eu-central-1', // 德国(法兰克福) + ]; +} + +/** + * 获取ECS实例列表(单个区域) + */ +function aliyunGetEcsList($configId, $region, $maxResults = 100, $nextToken = null) { + $params = [ + 'MaxResults' => $maxResults, + ]; + + if ($nextToken) { + $params['NextToken'] = $nextToken; + } + + return aliyunApiRequest($configId, 'DescribeInstances', $params, $region, 'ecs'); +} + +/** + * 获取轻量应用服务器实例列表(单个区域) + */ +function aliyunGetSwasList($configId, $region, $pageNumber = 1, $pageSize = 100) { + $params = [ + 'PageNumber' => $pageNumber, + 'PageSize' => $pageSize, + ]; + + return aliyunApiRequest($configId, 'ListInstances', $params, $region, 'swas'); +} + +/** + * 获取ECS实例详细信息 + */ +function aliyunGetEcsDetails($configId, $instanceId, $region = null, $updateDb = true) { + $params = [ + 'InstanceIds' => json_encode([$instanceId]), + ]; + + $result = aliyunApiRequest($configId, 'DescribeInstances', $params, $region, 'ecs'); + + // 如果获取成功且需要更新数据库 + if ($updateDb && $result && isset($result['status']) && $result['status'] === 200) { + $instances = $result['data']['Instances']['Instance'] ?? []; + + if (!empty($instances)) { + updateAliyunEcsToDatabase($configId, $instances[0]); + } + } + + return $result; +} + +/** + * 获取轻量应用服务器实例详细信息 + */ +function aliyunGetSwasDetails($configId, $instanceId, $region = null, $updateDb = true) { + $params = [ + 'InstanceIds' => json_encode([$instanceId]), + ]; + + $result = aliyunApiRequest($configId, 'ListInstances', $params, $region, 'swas'); + + // 如果获取成功且需要更新数据库 + if ($updateDb && $result && isset($result['status']) && $result['status'] === 200) { + $instances = $result['data']['Instances'] ?? []; + + if (!empty($instances)) { + updateAliyunSwasToDatabase($configId, $instances[0]); + } + } + + return $result; +} + +/** + * 获取ECS实例状态 + */ +function aliyunGetEcsStatus($configId, $instanceId, $region = null, $updateDb = true) { + $params = [ + 'InstanceId.1' => $instanceId, + ]; + + $result = aliyunApiRequest($configId, 'DescribeInstanceStatus', $params, $region, 'ecs'); + + // 如果获取成功且需要更新数据库 + if ($updateDb && $result && isset($result['status']) && $result['status'] === 200) { + $instanceStatusSet = $result['data']['InstanceStatuses']['InstanceStatus'] ?? []; + + if (!empty($instanceStatusSet)) { + $statusInfo = $instanceStatusSet[0]; + $rawStatus = $statusInfo['Status'] ?? 'UNKNOWN'; + + // 状态映射 + $statusMap = [ + 'Pending' => 'process', + 'Running' => 'on', + 'Starting' => 'process', + 'Stopping' => 'process', + 'Stopped' => 'off', + 'Rebooting' => 'process', + 'Unknown' => 'unknown' + ]; + + $status = $statusMap[$rawStatus] ?? 'unknown'; + + updateVpsStatusInDatabaseAliyun($configId, $instanceId, $status); + } + } + + return $result; +} + +/** + * 获取轻量应用服务器实例状态 + */ +function aliyunGetSwasStatus($configId, $instanceId, $region = null, $updateDb = true) { + $params = [ + 'InstanceIds' => json_encode([$instanceId]), + ]; + + $result = aliyunApiRequest($configId, 'ListInstanceStatus', $params, $region, 'swas'); + + // 如果获取成功且需要更新数据库 + if ($updateDb && $result && isset($result['status']) && $result['status'] === 200) { + $instanceStatusSet = $result['data']['InstanceStatuses'] ?? []; + + if (!empty($instanceStatusSet)) { + $statusInfo = $instanceStatusSet[0]; + $rawStatus = $statusInfo['Status'] ?? 'UNKNOWN'; + + // 状态映射 + $statusMap = [ + 'Pending' => 'process', + 'Starting' => 'process', + 'Running' => 'on', + 'Stopping' => 'process', + 'Stopped' => 'off', + 'Resetting' => 'process', + 'Upgrading' => 'process', + 'Disabled' => 'off', + 'Unknown' => 'unknown' + ]; + + $status = $statusMap[$rawStatus] ?? 'unknown'; + + updateVpsStatusInDatabaseAliyun($configId, $instanceId, $status); + } + } + + return $result; +} + +/** + * 启动ECS实例 + */ +function aliyunPowerOn($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']; + Logger::info("[aliyunPowerOn] 从数据库获取到 region: {$region}", 'aliyunPowerOn'); + } else { + Logger::error("[aliyunPowerOn] 无法从数据库获取 region,config_id: {$configId}, instance_id: {$instanceId}", 'aliyunPowerOn'); + return ['status' => 400, 'error' => '无法获取实例区域信息']; + } + } + + $params = [ + 'InstanceId' => $instanceId, + ]; + + $result = aliyunApiRequest($configId, 'StartInstance', $params, $region, 'ecs'); + + // 开机操作后立即获取状态并更新数据库 + if ($result && isset($result['status']) && $result['status'] === 200) { + sleep(2); + aliyunGetEcsStatus($configId, $instanceId, $region, true); + } + + return $result; +} + +/** + * 启动轻量应用服务器 + */ +function aliyunSwasPowerOn($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']; + Logger::info("[aliyunSwasPowerOn] 从数据库获取到 region: {$region}", 'aliyunSwasPowerOn'); + } else { + Logger::error("[aliyunSwasPowerOn] 无法从数据库获取 region,config_id: {$configId}, instance_id: {$instanceId}", 'aliyunSwasPowerOn'); + return ['status' => 400, 'error' => '无法获取实例区域信息']; + } + } + + $params = [ + 'InstanceId' => $instanceId, + ]; + + $result = aliyunApiRequest($configId, 'StartInstance', $params, $region, 'swas'); + + // 开机操作后立即获取状态并更新数据库 + if ($result && isset($result['status']) && $result['status'] === 200) { + sleep(2); + aliyunGetSwasStatus($configId, $instanceId, $region, true); + } + + return $result; +} + +/** + * 停止ECS实例 + */ +function aliyunPowerOff($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("[aliyunPowerOff] 无法从数据库获取 region", 'aliyunPowerOff'); + return ['status' => 400, 'error' => '无法获取实例区域信息']; + } + } + + $params = [ + 'InstanceId' => $instanceId, + ]; + + $result = aliyunApiRequest($configId, 'StopInstance', $params, $region, 'ecs'); + + // 关机操作后立即获取状态并更新数据库 + if ($result && isset($result['status']) && $result['status'] === 200) { + sleep(2); + aliyunGetEcsStatus($configId, $instanceId, $region, true); + } + + return $result; +} + +/** + * 停止轻量应用服务器 + */ +function aliyunSwasPowerOff($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("[aliyunSwasPowerOff] 无法从数据库获取 region", 'aliyunSwasPowerOff'); + return ['status' => 400, 'error' => '无法获取实例区域信息']; + } + } + + $params = [ + 'InstanceId' => $instanceId, + ]; + + $result = aliyunApiRequest($configId, 'StopInstance', $params, $region, 'swas'); + + // 关机操作后立即获取状态并更新数据库 + if ($result && isset($result['status']) && $result['status'] === 200) { + sleep(2); + aliyunGetSwasStatus($configId, $instanceId, $region, true); + } + + return $result; +} + +/** + * 重启ECS实例 + */ +function aliyunReboot($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("[aliyunReboot] 无法从数据库获取 region", 'aliyunReboot'); + return ['status' => 400, 'error' => '无法获取实例区域信息']; + } + } + + $params = [ + 'InstanceId' => $instanceId, + ]; + + $result = aliyunApiRequest($configId, 'RebootInstance', $params, $region, 'ecs'); + + // 重启操作后立即获取状态并更新数据库 + if ($result && isset($result['status']) && $result['status'] === 200) { + sleep(3); + aliyunGetEcsStatus($configId, $instanceId, $region, true); + } + + return $result; +} + +/** + * 重启轻量应用服务器 + */ +function aliyunSwasReboot($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("[aliyunSwasReboot] 无法从数据库获取 region", 'aliyunSwasReboot'); + return ['status' => 400, 'error' => '无法获取实例区域信息']; + } + } + + $params = [ + 'InstanceId' => $instanceId, + ]; + + $result = aliyunApiRequest($configId, 'RebootInstance', $params, $region, 'swas'); + + // 重启操作后立即获取状态并更新数据库 + if ($result && isset($result['status']) && $result['status'] === 200) { + sleep(3); + aliyunGetSwasStatus($configId, $instanceId, $region, true); + } + + return $result; +} + +/** + * 将ECS信息更新到数据库 + */ +function updateAliyunEcsToDatabase($configId, $instance) { + $listDb = getVpsListDB(); + + $vpsId = $instance['InstanceId']; + $region = $instance['RegionId'] ?? ''; + $zoneId = $instance['ZoneId'] ?? ''; + + // 提取CPU和内存信息 + $cpuCores = $instance['Cpu'] ?? null; + // 注意:阿里云API返回的Memory单位是MB,需要转换为GB + $memorySize = isset($instance['Memory']) ? round($instance['Memory'] / 1024, 2) . 'G' : null; + + // 提取系统盘信息 + $diskSize = null; + if (isset($instance['SystemDisk']) && isset($instance['SystemDisk']['Size'])) { + $diskSize = $instance['SystemDisk']['Size'] . 'G'; + } + + // 提取带宽信息 + $bandwidth = null; + if (isset($instance['InternetMaxBandwidthOut'])) { + $bandwidth = $instance['InternetMaxBandwidthOut'] . 'Mbps'; + } + + // 提取操作系统信息 + $osType = $instance['OSName'] ?? ($instance['ImageId'] ?? null); + + // 提取IP地址 + $ipAddress = null; + if (isset($instance['PublicIpAddress']) && !empty($instance['PublicIpAddress']['IpAddress'])) { + $ipAddress = $instance['PublicIpAddress']['IpAddress'][0]; + } elseif (isset($instance['EipAddress']) && isset($instance['EipAddress']['IpAddress'])) { + $ipAddress = $instance['EipAddress']['IpAddress']; + } + + // 提取实例名称 + $productName = $instance['InstanceName'] ?? null; + + // 提取到期时间 + $nextduedate = null; + if (isset($instance['ExpiredTime'])) { + $expiredTime = $instance['ExpiredTime']; + if ($expiredTime !== '' && $expiredTime !== null) { + $nextduedate = strtotime($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 = ?', + [ + $zoneId, + $ipAddress, + $productName, + $cpuCores, + $memorySize, + $diskSize, + $bandwidth, + $osType, + $nextduedate, + $existing['id'] + ] + ); + Logger::info("[updateAliyunEcsToDatabase] ECS {$vpsId} 详细信息已更新", 'updateAliyunEcsToDatabase'); + } 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, + $zoneId, + $ipAddress, + $productName, + $cpuCores, + $memorySize, + $diskSize, + $bandwidth, + $osType, + $nextduedate + ] + ); + Logger::info("[updateAliyunEcsToDatabase] ECS {$vpsId} 新记录已插入", 'updateAliyunEcsToDatabase'); + } +} + +/** + * 将轻量应用服务器信息更新到数据库 + */ +function updateAliyunSwasToDatabase($configId, $instance) { + $listDb = getVpsListDB(); + + $vpsId = $instance['InstanceId']; + $region = $instance['RegionId'] ?? ''; + + // 提取CPU和内存信息(从ResourceSpec中获取) + $cpuCores = isset($instance['ResourceSpec']) ? $instance['ResourceSpec']['Cpu'] : null; + $memorySize = isset($instance['ResourceSpec']) ? $instance['ResourceSpec']['Memory'] . 'G' : null; + + // 提取磁盘信息 + $diskSize = isset($instance['ResourceSpec']) ? $instance['ResourceSpec']['DiskSize'] . 'G' : null; + + // 提取带宽信息 + $bandwidth = isset($instance['ResourceSpec']) ? $instance['ResourceSpec']['Bandwidth'] . 'Mbps' : null; + + // 提取操作系统信息 + $osType = null; + if (isset($instance['Image'])) { + $osType = $instance['Image']['OsType'] ?? null; + } + + // 提取IP地址 + $ipAddress = $instance['PublicIpAddress'] ?? null; + + // 提取实例名称 + $productName = $instance['InstanceName'] ?? null; + + // 提取到期时间 + $nextduedate = null; + if (isset($instance['ExpiredTime'])) { + $expiredTime = $instance['ExpiredTime']; + if ($expiredTime !== '' && $expiredTime !== null) { + $nextduedate = strtotime($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("[updateAliyunSwasToDatabase] SWAS {$vpsId} 详细信息已更新", 'updateAliyunSwasToDatabase'); + } 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("[updateAliyunSwasToDatabase] SWAS {$vpsId} 新记录已插入", 'updateAliyunSwasToDatabase'); + } +} + +/** + * 更新VPS状态到数据库 + */ +function updateVpsStatusInDatabaseAliyun($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("[updateVpsStatusInDatabaseAliyun] VPS {$vpsId} 状态已更新为: {$status}", 'updateVpsStatusInDatabaseAliyun'); + } else { + $listDb->execute( + 'INSERT INTO vps_list (config_id, vps_id, status, last_check) VALUES (?, ?, ?, CURRENT_TIMESTAMP)', + [$configId, $vpsId, $status] + ); + Logger::info("[updateVpsStatusInDatabaseAliyun] VPS {$vpsId} 新记录已插入,状态: {$status}", 'updateVpsStatusInDatabaseAliyun'); + } +} + +/** + * 刷新指定配置的ECS列表(遍历所有区域) + */ +function refreshAliyunEcsListForConfig($configId) { + $db = getVpsDB(); + $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); + + if (!$config || $config['site_type'] !== 'aliyun') { + return false; + } + + // 先获取区域列表 + $regions = getAliyunRegions($configId); + $totalInstances = 0; + + Logger::info("[refreshAliyunEcsListForConfig] 开始遍历所有区域获取ECS列表", 'refreshAliyunEcsListForConfig'); + + foreach ($regions as $region) { + try { + $nextToken = null; + do { + $result = aliyunGetEcsList($configId, $region, 100, $nextToken); + + if ($result && isset($result['status']) && $result['status'] === 200) { + $instances = $result['data']['Instances']['Instance'] ?? []; + + if (!empty($instances)) { + Logger::info("[refreshAliyunEcsListForConfig] 区域 {$region} 找到 " . count($instances) . " 台ECS", 'refreshAliyunEcsListForConfig'); + + foreach ($instances as $instance) { + updateAliyunEcsToDatabase($configId, $instance); + + // 获取状态 + aliyunGetEcsStatus($configId, $instance['InstanceId'], $region, true); + + $totalInstances++; + + // 避免频繁请求 + usleep(300000); // 300毫秒 + } + } + + // 检查是否有下一页 + $nextToken = $result['data']['NextToken'] ?? null; + } else { + Logger::warning("[refreshAliyunEcsListForConfig] 区域 {$region} 获取失败: " . ($result['msg'] ?? '未知错误'), 'refreshAliyunEcsListForConfig'); + break; + } + } while ($nextToken); + } catch (Exception $e) { + Logger::error("[refreshAliyunEcsListForConfig] 区域 {$region} 异常: " . $e->getMessage(), 'refreshAliyunEcsListForConfig'); + } + + // 区域之间稍作延迟 + usleep(500000); // 500毫秒 + } + + Logger::info("[refreshAliyunEcsListForConfig] ECS列表刷新完成,共获取 {$totalInstances} 台实例", 'refreshAliyunEcsListForConfig'); + + return true; +} + +/** + * 刷新指定配置的轻量应用服务器列表(遍历所有区域) + */ +function refreshAliyunSwasListForConfig($configId) { + $db = getVpsDB(); + $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); + + if (!$config || $config['site_type'] !== 'aliyun_swas') { + return false; + } + + // 先获取区域列表 + $regions = getAliyunSwasRegions($configId); + $totalInstances = 0; + + Logger::info("[refreshAliyunSwasListForConfig] 开始遍历所有区域获取轻量应用服务器列表", 'refreshAliyunSwasListForConfig'); + + foreach ($regions as $region) { + try { + $pageNumber = 1; + do { + $result = aliyunGetSwasList($configId, $region, $pageNumber, 100); + + if ($result && isset($result['status']) && $result['status'] === 200) { + $instances = $result['data']['Instances'] ?? []; + $totalCount = $result['data']['TotalCount'] ?? 0; + $pageSize = $result['data']['PageSize'] ?? 10; + + if (!empty($instances)) { + Logger::info("[refreshAliyunSwasListForConfig] 区域 {$region} 找到 " . count($instances) . " 台轻量服务器", 'refreshAliyunSwasListForConfig'); + + foreach ($instances as $instance) { + updateAliyunSwasToDatabase($configId, $instance); + + // 获取状态 + aliyunGetSwasStatus($configId, $instance['InstanceId'], $region, true); + + $totalInstances++; + + // 避免频繁请求 + usleep(300000); // 300毫秒 + } + } + + // 检查是否有下一页 + $totalPages = ceil($totalCount / $pageSize); + $pageNumber++; + + if ($pageNumber > $totalPages) { + break; + } + } else { + Logger::warning("[refreshAliyunSwasListForConfig] 区域 {$region} 获取失败: " . ($result['msg'] ?? '未知错误'), 'refreshAliyunSwasListForConfig'); + break; + } + } while (true); + } catch (Exception $e) { + Logger::error("[refreshAliyunSwasListForConfig] 区域 {$region} 异常: " . $e->getMessage(), 'refreshAliyunSwasListForConfig'); + } + + // 区域之间稍作延迟 + usleep(500000); // 500毫秒 + } + + Logger::info("[refreshAliyunSwasListForConfig] 轻量应用服务器列表刷新完成,共获取 {$totalInstances} 台实例", 'refreshAliyunSwasListForConfig'); + + return true; +} + +/** + * 刷新所有阿里云配置的实例列表(ECS + SWAS) + */ +function refreshAllAliyunVpsLists() { + $db = getVpsDB(); + $configs = $db->query('SELECT * FROM configs WHERE site_type IN (?, ?)', ['aliyun', 'aliyun_swas']); + + $successCount = 0; + + foreach ($configs as $config) { + if ($config['site_type'] === 'aliyun') { + // 刷新ECS列表 + if (refreshAliyunEcsListForConfig($config['id'])) { + $successCount++; + } + } elseif ($config['site_type'] === 'aliyun_swas') { + // 刷新轻量应用服务器列表 + if (refreshAliyunSwasListForConfig($config['id'])) { + $successCount++; + } + } + } + + return $successCount; +} +?> diff --git a/config_add.php b/config_add.php index bf7087f..8a93777 100644 --- a/config_add.php +++ b/config_add.php @@ -138,13 +138,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $dbSiteType = $siteType; if ($siteType === 'tencent_cvm' || $siteType === 'tencent_lh') { $dbSiteType = 'tencent'; + } elseif ($siteType === 'aliyun_swas') { + $dbSiteType = 'aliyun_swas'; } - // 验证必填字段(腾讯云需要siteUrl) - if ($siteType === 'tencent_cvm' || $siteType === 'tencent_lh') { - // 腾讯云:siteUrl必填(API域名) + // 验证必填字段(腾讯云和阿里云需要siteUrl) + if ($siteType === 'tencent_cvm' || $siteType === 'tencent_lh' || $siteType === 'aliyun' || $siteType === 'aliyun_swas') { + // 腾讯云/阿里云:siteUrl必填(API域名) if (empty($apiLabel) || empty($siteType) || empty($siteUrl) || empty($account) || empty($apiKey)) { - $message = '
'; + $message = ''; } else { $db = getVpsDB(); @@ -160,8 +162,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { ); if ($configId) { - $typeName = ($siteType === 'tencent_cvm') ? 'CVM云服务器' : '轻量应用服务器'; - $message = ''; + if ($siteType === 'aliyun') { + $message = ''; + } elseif ($siteType === 'aliyun_swas') { + $message = ''; + } else { + $typeName = ($siteType === 'tencent_cvm') ? 'CVM云服务器' : '轻量应用服务器'; + $message = ''; + } // 如果是首次添加配置,提示运行install.sh $configCount = $db->queryOne('SELECT COUNT(*) as count FROM configs')['count']; @@ -209,6 +217,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { refreshTencentLighthouseListForConfig($configId); } $message .= '| 序号 | API标识 | 网站类型 | 网站链接 | @@ -448,13 +466,13 @@ $configs = $db->query('SELECT * FROM configs ORDER BY id');
|---|---|---|---|
|
'魔方',
- 'aliyun' => '阿里云',
+ 'aliyun' => '阿里云ECS',
+ 'aliyun_swas' => '阿里云轻量',
'tencent' => '腾讯云'
];
echo $typeMap[$config['site_type']] ?? $config['site_type'];
@@ -504,12 +522,21 @@ $configs = $db->query('SELECT * FROM configs ORDER BY id');
accountLabel.innerHTML = '账户 * (邮箱或手机号)';
keyLabel.textContent = 'API密钥 *';
} else if (siteType === 'aliyun') {
- // 阿里云
+ // 阿里云ECS
urlGroup.style.display = 'block';
- urlInput.placeholder = '例如: https://ecs.aliyuncs.com';
+ urlInput.value = 'https://ecs.aliyuncs.com';
+ urlInput.placeholder = 'https://ecs.aliyuncs.com';
urlInput.required = true;
- accountLabel.innerHTML = '账户 * (AccessKey ID)';
- keyLabel.textContent = 'API密钥 *';
+ accountLabel.innerHTML = 'AccessKey ID * (阿里云访问密钥ID)';
+ keyLabel.textContent = 'AccessKey Secret *';
+ } else if (siteType === 'aliyun_swas') {
+ // 阿里云轻量应用服务器
+ urlGroup.style.display = 'block';
+ urlInput.value = 'https://swas.aliyuncs.com';
+ urlInput.placeholder = 'https://swas.aliyuncs.com';
+ urlInput.required = true;
+ accountLabel.innerHTML = 'AccessKey ID * (阿里云访问密钥ID)';
+ keyLabel.textContent = 'AccessKey Secret *';
} else if (siteType === 'tencent_cvm') {
// CVM云服务器
urlGroup.style.display = 'block';
diff --git a/diagnose_tencent.php b/diagnose_tencent.php
deleted file mode 100644
index 595d1ff..0000000
--- a/diagnose_tencent.php
+++ /dev/null
@@ -1,108 +0,0 @@
-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 7c9ab0f..13aed5f 100644
--- a/index.php
+++ b/index.php
@@ -3,6 +3,7 @@ require_once __DIR__ . '/app/db_helper.php';
require_once __DIR__ . '/app/logger.php';
require_once __DIR__ . '/mofangidc.php';
require_once __DIR__ . '/tencentcloud.php';
+require_once __DIR__ . '/aliyun.php';
// 存储API_PASS
$tokenFile = __DIR__ . '/app/pass.php';
@@ -29,12 +30,14 @@ if (!defined('API_PASS') || $pass !== API_PASS) {
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
- $hostId = intval($_POST['host_id'] ?? 0);
+ $hostId = $_POST['host_id'] ?? '';
$configId = intval($_POST['config_id'] ?? 0);
- if ($action && $hostId > 0 && $configId > 0) {
+ if ($action && !empty($hostId) && $configId > 0) {
$result = null;
+ Logger::info("[操作请求] action={$action}, host_id={$hostId}, config_id={$configId}", 'index.php');
+
// 获取配置信息以确定平台类型
$db = getVpsDB();
$config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]);
@@ -57,6 +60,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} elseif ($action === 'off') {
$result = tencentSmartPowerOff($configId, $hostId);
}
+ } elseif ($config['site_type'] === 'aliyun') {
+ // 阿里云ECS操作
+ if ($action === 'reboot') {
+ $result = aliyunReboot($configId, $hostId);
+ } elseif ($action === 'on') {
+ $result = aliyunPowerOn($configId, $hostId);
+ } elseif ($action === 'off') {
+ $result = aliyunPowerOff($configId, $hostId);
+ }
+ } elseif ($config['site_type'] === 'aliyun_swas') {
+ // 阿里云轻量应用服务器操作
+ if ($action === 'reboot') {
+ $result = aliyunSwasReboot($configId, $hostId);
+ } elseif ($action === 'on') {
+ $result = aliyunSwasPowerOn($configId, $hostId);
+ } elseif ($action === 'off') {
+ $result = aliyunSwasPowerOff($configId, $hostId);
+ }
}
}
@@ -78,6 +99,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$tencentCount = refreshAllTencentVpsLists();
$count += $tencentCount;
+ // 刷新阿里云的ECS列表
+ $aliyunCount = refreshAllAliyunVpsLists();
+ $count += $aliyunCount;
+
$message = '';
// 获取每个VPS的详细信息并更新数据库
@@ -127,6 +152,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} elseif ($config['site_type'] === 'tencent') {
// 腾讯云实例已在refreshAllTencentVpsLists中更新了详细信息,这里不需要额外处理
Logger::info("[refresh_vps_list] 腾讯云配置 {$config['id']} 已刷新", 'index.php');
+ } elseif ($config['site_type'] === 'aliyun') {
+ // 阿里云ECS实例已在refreshAllAliyunVpsLists中更新了详细信息,这里不需要额外处理
+ Logger::info("[refresh_vps_list] 阿里云配置 {$config['id']} 已刷新", 'index.php');
+ } elseif ($config['site_type'] === 'aliyun_swas') {
+ // 阿里云轻量应用服务器实例已在refreshAllAliyunVpsLists中更新了详细信息,这里不需要额外处理
+ Logger::info("[refresh_vps_list] 阿里云轻量配置 {$config['id']} 已刷新", 'index.php');
}
}
@@ -262,6 +293,7 @@ foreach ($vpsList as $vps) {
font-size: 18px;
font-weight: bold;
color: #333;
+ cursor: help;
}
.status-badge {
padding: 4px 12px;
@@ -450,10 +482,19 @@ foreach ($vpsList as $vps) {
$statusText = '处理中';
$statusColor = '#ffc107';
}
+
+ // 处理长ID显示
+ $vpsId = $vps['vps_id'];
+ $displayId = $vpsId;
+ $showTooltip = false;
+ if (strlen($vpsId) > 12) {
+ $displayId = substr($vpsId, 0, 10) . '...';
+ $showTooltip = true;
+ }
?>
- #
+ title="">#
@@ -540,6 +581,12 @@ foreach ($vpsList as $vps) {
+
+
diff --git a/tencentcloud.php b/tencentcloud.php
index 272a32f..df56d65 100644
--- a/tencentcloud.php
+++ b/tencentcloud.php
@@ -300,6 +300,21 @@ function tencentGetLighthouseStatus($configId, $instanceId, $region = null, $upd
* 启动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,
];
@@ -319,6 +334,21 @@ function tencentPowerOn($configId, $instanceId, $region = null) {
* 启动轻量应用服务器
*/
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,
];
@@ -338,6 +368,21 @@ function tencentLighthousePowerOn($configId, $instanceId, $region = null) {
* 停止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,
];
@@ -357,6 +402,21 @@ function tencentPowerOff($configId, $instanceId, $region = null) {
* 停止轻量应用服务器
*/
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,
];
@@ -376,6 +436,21 @@ function tencentLighthousePowerOff($configId, $instanceId, $region = null) {
* 重启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,
];
@@ -395,6 +470,21 @@ function tencentReboot($configId, $instanceId, $region = null) {
* 重启轻量应用服务器
*/
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,
];
|