$token, 'timestamp' => time() ]; saveTokensFile($cached_tokens); } /** * 保存tokens数组到文件 * @param array $cached_tokens tokens数组 */ function saveTokensFile($cached_tokens) { $content = "\n"; file_put_contents(TOKEN_CACHE_FILE, $content); chmod(TOKEN_CACHE_FILE, 0600); } /** * 魔方平台登录获取JWT Token */ function mofangLogin($siteUrl, $account, $apiKey) { try { $url = rtrim($siteUrl, '/') . '/v1/login_api'; $data = [ 'account' => $account, 'password' => $apiKey ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); 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, 10); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { Logger::error("魔方登录请求失败,HTTP状态码: {$httpCode}", 'mofangLogin'); return null; } $result = json_decode($response, true); if (isset($result['status']) && $result['status'] === 200 && isset($result['jwt'])) { return $result['jwt']; } else { Logger::error("魔方登录失败: " . ($result['msg'] ?? '未知错误'), 'mofangLogin'); return null; } } catch (Exception $e) { Logger::error("魔方登录异常: " . $e->getMessage(), 'mofangLogin'); return null; } } /** * 获取有效的Token(先查缓存,没有则重新登录) */ function getValidToken($configId) { // 先尝试从缓存获取 $token = getCachedToken($configId); if ($token) { return $token; } // 缓存不存在或已过期,重新获取 $db = getVpsDB(); $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); if (!$config) { Logger::error("配置ID {$configId} 不存在", 'getValidToken'); return null; } $token = mofangLogin($config['site_url'], $config['account'], $config['api_key']); if ($token) { saveToken($configId, $token); } return $token; } /** * 发送魔方API请求 */ function mofangApiRequest($siteUrl, $endpoint, $method = 'GET', $data = [], $configId = null) { // 获取Token if ($configId) { $token = getValidToken($configId); } else { // 如果没有configId,尝试从第一个配置获取 $db = getVpsDB(); $configs = $db->query('SELECT * FROM configs LIMIT 1'); if (!$configs) { return null; } $config = $configs[0]; $token = getValidToken($config['id']); $siteUrl = $config['site_url']; } if (!$token) { return ['status' => 400, 'msg' => '获取Token失败']; } $url = rtrim($siteUrl, '/') . $endpoint; $headers = [ 'Authorization: JWT ' . $token, 'Content-Type: application/json' ]; $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_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_TIMEOUT, 10); if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); } elseif ($method === 'PUT') { curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); } $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { return ['status' => $httpCode, 'msg' => 'HTTP请求失败']; } $result = json_decode($response, true); // 如果Token失效(405),清除缓存并重试 if (isset($result['status']) && $result['status'] === 405 && $configId) { // 清除缓存 $cached_tokens = []; if (file_exists(TOKEN_CACHE_FILE)) { include TOKEN_CACHE_FILE; } unset($cached_tokens[$configId]); saveTokensFile($cached_tokens); // 重试一次 return mofangApiRequest($siteUrl, $endpoint, $method, $data, $configId); } return $result; } /** * 获取VPS列表 */ function mofangGetVpsList($configId, $page = 1, $limit = 100) { $db = getVpsDB(); $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); if (!$config) { return null; } $endpoint = "/v1/hosts?page={$page}&limit={$limit}"; return mofangApiRequest($config['site_url'], $endpoint, 'GET', [], $configId); } /** * 获取VPS状态 */ function mofangGetVpsStatus($configId, $vpsId, $updateDb = true) { $db = getVpsDB(); $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); if (!$config) { return null; } $endpoint = "/v1/hosts/{$vpsId}/module/status?type=host"; $result = mofangApiRequest($config['site_url'], $endpoint, 'GET', [], $configId); // 如果获取成功且需要更新数据库 if ($updateDb && $result && isset($result['status']) && $result['status'] === 200 && isset($result['data'])) { $statusData = $result['data']; $rawStatus = $statusData['status'] ?? 'unknown'; // 状态映射:将魔方平台的原始状态转换为标准状态 $statusMap = [ 'on' => 'on', // 开机 'off' => 'off', // 关机 'running' => 'on', // 运行中 'stopped' => 'off', // 已停止 'process' => 'process', // 处理中(开机/关机/重启过程中) 'pending' => 'process', // 等待中 'installing' => 'process', // 安装中 'rebooting' => 'process', // 重启中 'unknown' => 'unknown' // 未知 ]; $status = $statusMap[$rawStatus] ?? $rawStatus; $listDb = getVpsListDB(); // 先获取该VPS的IP地址,用于精确匹配 $vpsInfo = $listDb->queryOne( 'SELECT id, ip_address FROM vps_list WHERE config_id = ? AND vps_id = ?', [$configId, $vpsId] ); if ($vpsInfo) { // 记录存在,执行UPDATE $success = $listDb->execute( 'UPDATE vps_list SET status = ?, last_check = CURRENT_TIMESTAMP WHERE id = ?', [$status, $vpsInfo['id']] ); if ($success) { Logger::info("[mofangGetVpsStatus] VPS {$vpsId} (IP: {$vpsInfo['ip_address']}) 状态已更新为: {$status}", 'mofangGetVpsStatus'); } else { Logger::error("[mofangGetVpsStatus] VPS {$vpsId} 状态更新失败", 'mofangGetVpsStatus'); } } else { // 记录不存在,插入新记录 $success = $listDb->execute( 'INSERT INTO vps_list (config_id, vps_id, status, last_check) VALUES (?, ?, ?, CURRENT_TIMESTAMP)', [$configId, $vpsId, $status] ); if ($success) { Logger::info("[mofangGetVpsStatus] VPS {$vpsId} 新记录已插入,状态: {$status}", 'mofangGetVpsStatus'); } else { Logger::error("[mofangGetVpsStatus] VPS {$vpsId} 新记录插入失败", 'mofangGetVpsStatus'); } } } elseif ($updateDb && (!$result || !isset($result['status']) || $result['status'] !== 200)) { // API调用失败,不更新数据库,保留原有状态 Logger::warning("[mofangGetVpsStatus] VPS {$vpsId} API调用失败,保留原有状态", 'mofangGetVpsStatus'); } return $result; } /** * 获取VPS详细信息 */ function mofangGetVpsDetails($configId, $vpsId, $updateDb = true) { $db = getVpsDB(); $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); if (!$config) { return null; } $endpoint = "/v1/hosts/{$vpsId}"; $result = mofangApiRequest($config['site_url'], $endpoint, 'GET', [], $configId); // 如果获取成功且需要更新数据库 if ($updateDb && $result && isset($result['status']) && $result['status'] === 200 && isset($result['data']['host'])) { $host = $result['data']['host']; // 解析详细信息 $updates = []; // 存储要更新的字段 $values = []; // 提取开关机状态(如果有) if (isset($host['status'])) { $rawStatus = $host['status']; // 状态映射:将魔方平台的原始状态转换为标准状态 $statusMap = [ 'on' => 'on', // 开机 'off' => 'off', // 关机 'running' => 'on', // 运行中 'stopped' => 'off', // 已停止 'process' => 'process', // 处理中(开机/关机/重启过程中) 'pending' => 'process', // 等待中 'installing' => 'process', // 安装中 'rebooting' => 'process', // 重启中 'unknown' => 'unknown' // 未知 ]; $status = $statusMap[$rawStatus] ?? $rawStatus; $updates[] = 'status = ?'; $values[] = $status; } if (isset($host['config_option']) && is_array($host['config_option'])) { foreach ($host['config_option'] as $option) { switch ($option['key']) { case 'cpu': if (isset($option['value'])) { preg_match('/(\d+)/', $option['value'], $matches); if (!empty($matches)) { $updates[] = 'cpu_cores = ?'; $values[] = intval($matches[1]); } } break; case 'memory': if (isset($option['value'])) { $updates[] = 'memory_size = ?'; $values[] = $option['value']; } break; case 'system_disk_size': if (isset($option['value'])) { $updates[] = 'disk_size = ?'; $values[] = $option['value']; } break; case 'bw': if (isset($option['value'])) { $updates[] = 'bandwidth = ?'; $values[] = $option['value']; } break; case 'os': if (isset($option['value'])) { $updates[] = 'os_type = ?'; $values[] = $option['value']; } break; } } } // 只有当有字段需要更新时才执行UPDATE if (!empty($updates)) { // 添加last_check更新 $updates[] = 'last_check = CURRENT_TIMESTAMP'; // 构建动态UPDATE语句 $setClause = implode(', ', $updates); $values[] = $configId; $values[] = $vpsId; $listDb = getVpsListDB(); // 先检查记录是否存在 $existing = $listDb->queryOne( 'SELECT id FROM vps_list WHERE config_id = ? AND vps_id = ?', [$configId, $vpsId] ); if ($existing) { // 记录存在,执行UPDATE $listDb->execute( "UPDATE vps_list SET {$setClause} WHERE config_id = ? AND vps_id = ?", $values ); Logger::info("[mofangGetVpsDetails] VPS {$vpsId} 详细信息已更新" . (isset($status) ? ",状态: {$status}" : ""), 'mofangGetVpsDetails'); } else { // 记录不存在,插入新记录(只插入获取到的字段) $columns = []; $insertValues = []; $placeholders = []; // 解析SET子句中的字段 foreach ($updates as $update) { if (strpos($update, 'last_check') === false) { list($column, $value) = explode(' = ', $update); $columns[] = $column; $insertValues[] = array_shift($values); // 从$values中取出对应的值 $placeholders[] = '?'; } } // 添加config_id和vps_id $columns[] = 'config_id'; $columns[] = 'vps_id'; $insertValues[] = $configId; $insertValues[] = $vpsId; if (!empty($columns)) { $columnStr = implode(', ', $columns); $placeholderStr = implode(', ', $placeholders); $listDb->execute( "INSERT INTO vps_list ({$columnStr}) VALUES ({$placeholderStr})", $insertValues ); Logger::info("[mofangGetVpsDetails] VPS {$vpsId} 新记录已插入", 'mofangGetVpsDetails'); } } } } return $result; } /** * VPS开机 */ function mofangPowerOn($configId, $vpsId) { $db = getVpsDB(); $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); if (!$config) { return null; } $endpoint = "/v1/hosts/{$vpsId}/module/on"; $result = mofangApiRequest($config['site_url'], $endpoint, 'PUT', [], $configId); // 开机操作后立即获取状态并更新数据库 if ($result && isset($result['status']) && $result['status'] === 200) { sleep(2); // 等待2秒让状态生效 mofangGetVpsStatus($configId, $vpsId, true); } return $result; } /** * VPS关机 */ function mofangPowerOff($configId, $vpsId) { $db = getVpsDB(); $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); if (!$config) { return null; } $endpoint = "/v1/hosts/{$vpsId}/module/off"; $result = mofangApiRequest($config['site_url'], $endpoint, 'PUT', [], $configId); // 关机操作后立即获取状态并更新数据库 if ($result && isset($result['status']) && $result['status'] === 200) { sleep(2); // 等待2秒让状态生效 mofangGetVpsStatus($configId, $vpsId, true); } return $result; } /** * VPS硬重启 */ function mofangHardReboot($configId, $vpsId) { $db = getVpsDB(); $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); if (!$config) { return null; } $endpoint = "/v1/hosts/{$vpsId}/module/hard_reboot"; $result = mofangApiRequest($config['site_url'], $endpoint, 'PUT', [], $configId); // 硬重启操作后立即获取状态并更新数据库 if ($result && isset($result['status']) && $result['status'] === 200) { sleep(3); // 等待3秒让重启生效 mofangGetVpsStatus($configId, $vpsId, true); } return $result; } /** * 刷新指定配置的VPS列表并保存到数据库 */ function refreshVpsListForConfig($configId) { $db = getVpsDB(); $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); if (!$config) { return false; } // 获取VPS列表 $result = mofangGetVpsList($configId); if (!$result || !isset($result['status']) || $result['status'] !== 200) { Logger::error("获取VPS列表失败: " . ($result['msg'] ?? '未知错误'), 'refreshVpsListForConfig'); return false; } $hosts = $result['data']['host'] ?? []; if (empty($hosts)) { return true; // 没有VPS也算成功 } // 批量保存到数据库 $listDb = getVpsListDB(); foreach ($hosts as $host) { // 解析详细信息 $cpuCores = null; $memorySize = null; $diskSize = null; $bandwidth = null; $osType = null; if (isset($host['config_option']) && is_array($host['config_option'])) { foreach ($host['config_option'] as $option) { switch ($option['key']) { case 'cpu': if (isset($option['value'])) { // 提取数字,例如 "16核" -> 16 preg_match('/(\d+)/', $option['value'], $matches); if (!empty($matches)) { $cpuCores = intval($matches[1]); } } break; case 'memory': if (isset($option['value'])) { $memorySize = $option['value']; // 例如 "16G" } break; case 'system_disk_size': if (isset($option['value'])) { $diskSize = $option['value']; // 例如 "Lin50G,Win50G" } break; case 'bw': if (isset($option['value'])) { $bandwidth = $option['value']; // 例如 "70Mbps" } break; case 'os': if (isset($option['value'])) { $osType = $option['value']; // 例如 "Debian-12.0_x64" } break; } } } // 基于vps_id和ip_address去重 $existing = $listDb->queryOne( 'SELECT id, status FROM vps_list WHERE vps_id = ? AND ip_address = ?', [$host['id'], $host['dedicatedip'] ?? null] ); if ($existing) { // 记录已存在,只更新非status字段,保留原有status $listDb->execute( 'UPDATE vps_list SET domain = ?, product_name = ?, cpu_cores = ?, memory_size = ?, disk_size = ?, bandwidth = ?, os_type = ?, amount = ?, nextduedate = ?, section = ?, last_check = CURRENT_TIMESTAMP WHERE id = ?', [ $host['domain'] ?? null, $host['product_name'] ?? null, $cpuCores, $memorySize, $diskSize, $bandwidth, $osType, $host['amount'] ?? null, $host['nextduedate'] ?? null, $config['auto_monitor'] ? 1 : 0, $existing['id'] ] ); } else { // 新记录,插入所有字段(status设为null,稍后通过状态接口获取) $listDb->execute( 'INSERT INTO vps_list (config_id, vps_id, domain, ip_address, product_name, cpu_cores, memory_size, disk_size, bandwidth, os_type, amount, nextduedate, status, section, last_check) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, CURRENT_TIMESTAMP)', [ $configId, $host['id'], $host['domain'] ?? null, $host['dedicatedip'] ?? null, $host['product_name'] ?? null, $cpuCores, $memorySize, $diskSize, $bandwidth, $osType, $host['amount'] ?? null, $host['nextduedate'] ?? null, $config['auto_monitor'] ? 1 : 0 ] ); } } // 刷新列表后,批量获取每个VPS的状态 Logger::info("[refreshVpsListForConfig] 开始批量获取 {$configId} 配置下 " . count($hosts) . " 台VPS的状态", 'refreshVpsListForConfig'); foreach ($hosts as $host) { // 调用专门的状态接口获取开关机状态 mofangGetVpsStatus($configId, $host['id'], true); // 避免频繁请求,每次请求间隔0.5秒 usleep(500000); // 500毫秒 } Logger::info("[refreshVpsListForConfig] 配置 {$configId} 的VPS列表和状态刷新完成", 'refreshVpsListForConfig'); return true; } /** * 刷新所有配置的VPS列表 */ function refreshAllVpsLists() { $db = getVpsDB(); $configs = $db->query('SELECT * FROM configs'); $successCount = 0; foreach ($configs as $config) { if (refreshVpsListForConfig($config['id'])) { $successCount++; } } return $successCount; } /** * 获取单个VPS的状态并更新到数据库 */ function updateVpsStatusToDb($configId, $vpsId) { $db = getVpsDB(); $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); if (!$config) { Logger::error("配置ID {$configId} 不存在", 'updateVpsStatusToDb'); return null; } // 调用API获取VPS状态(不自动更新数据库,由本函数统一处理) $result = mofangGetVpsStatus($configId, $vpsId, false); if (!$result || !isset($result['status']) || $result['status'] !== 200) { Logger::error("获取VPS {$vpsId} 状态失败,保留原有状态: " . ($result['msg'] ?? '未知错误'), 'updateVpsStatusToDb'); return null; } $statusData = $result['data']; $rawStatus = $statusData['status'] ?? 'unknown'; $des = $statusData['des'] ?? '未知'; // 状态映射:将魔方平台的原始状态转换为标准状态 $statusMap = [ 'on' => 'on', // 开机 'off' => 'off', // 关机 'running' => 'on', // 运行中 'stopped' => 'off', // 已停止 'process' => 'process', // 处理中(开机/关机/重启过程中) 'pending' => 'process', // 等待中 'installing' => 'process', // 安装中 'rebooting' => 'process', // 重启中 'unknown' => 'unknown' // 未知 ]; $status = $statusMap[$rawStatus] ?? $rawStatus; // 检查记录是否存在 $listDb = getVpsListDB(); $vpsInfo = $listDb->queryOne( 'SELECT id, ip_address FROM vps_list WHERE config_id = ? AND vps_id = ?', [$configId, $vpsId] ); if ($vpsInfo) { // 记录存在,执行UPDATE $success = $listDb->execute( 'UPDATE vps_list SET status = ?, last_check = CURRENT_TIMESTAMP WHERE id = ?', [$status, $vpsInfo['id']] ); if ($success) { Logger::info("[updateVpsStatusToDb] VPS {$vpsId} (IP: {$vpsInfo['ip_address']}) 状态已更新为: {$status}", 'updateVpsStatusToDb'); } else { Logger::error("[updateVpsStatusToDb] VPS {$vpsId} 状态更新失败,保留原有状态", 'updateVpsStatusToDb'); return null; } } else { // 记录不存在,插入新记录 $success = $listDb->execute( 'INSERT INTO vps_list (config_id, vps_id, status, last_check) VALUES (?, ?, ?, CURRENT_TIMESTAMP)', [$configId, $vpsId, $status] ); if ($success) { Logger::info("[updateVpsStatusToDb] VPS {$vpsId} 新记录已插入,状态: {$status}", 'updateVpsStatusToDb'); } else { Logger::error("[updateVpsStatusToDb] VPS {$vpsId} 新记录插入失败", 'updateVpsStatusToDb'); return null; } } return [ 'vps_id' => $vpsId, 'status' => $status, 'des' => $des, 'updated' => true ]; } /** * 批量获取VPS状态并更新到数据库 */ function batchUpdateVpsStatus($configId, $vpsIds = []) { $db = getVpsDB(); $config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]); if (!$config) { return ['success' => 0, 'failed' => 0, 'error' => '配置不存在']; } // 如果未指定VPS IDs,获取该配置下的所有VPS if (empty($vpsIds)) { $listDb = getVpsListDB(); $vpsList = $listDb->query('SELECT vps_id FROM vps_list WHERE config_id = ?', [$configId]); $vpsIds = array_column($vpsList, 'vps_id'); } if (empty($vpsIds)) { return ['success' => 0, 'failed' => 0, 'error' => '没有VPS需要更新']; } $successCount = 0; $failedCount = 0; $results = []; $listDb = getVpsListDB(); $listDb->getConnection()->beginTransaction(); try { foreach ($vpsIds as $vpsId) { $result = updateVpsStatusToDb($configId, $vpsId); if ($result) { $successCount++; $results[] = $result; } else { $failedCount++; $results[] = [ 'vps_id' => $vpsId, 'status' => 'error', 'des' => '获取状态失败', 'updated' => false ]; } } $listDb->getConnection()->commit(); Logger::info("[batchUpdateVpsStatus] 批量更新完成: 成功{$successCount}, 失败{$failedCount}", 'batchUpdateVpsStatus'); } catch (Exception $e) { $listDb->getConnection()->rollBack(); Logger::error("[batchUpdateVpsStatus] 批量更新失败,已回滚: " . $e->getMessage(), 'batchUpdateVpsStatus'); return ['success' => 0, 'failed' => count($vpsIds), 'error' => '批量更新失败: ' . $e->getMessage()]; } return [ 'success' => $successCount, 'failed' => $failedCount, 'total' => count($vpsIds), 'results' => $results ]; } ?>