bug修复
This commit is contained in:
parent
914208aa70
commit
88f7fc7141
@ -1,21 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
require_once __DIR__ . '/logger.php';
|
||||||
* VPS Hub 数据库辅助类
|
|
||||||
* 提供SQLite数据库的统一访问接口
|
|
||||||
*/
|
|
||||||
|
|
||||||
class DBHelper {
|
class DBHelper {
|
||||||
private $db;
|
private $db;
|
||||||
private $dbPath;
|
private $dbPath;
|
||||||
|
|
||||||
/**
|
|
||||||
* 构造函数
|
|
||||||
* @param string $dbPath 数据库文件路径
|
|
||||||
*/
|
|
||||||
public function __construct($dbPath) {
|
public function __construct($dbPath) {
|
||||||
$this->dbPath = $dbPath;
|
$this->dbPath = $dbPath;
|
||||||
|
|
||||||
// 确保目录存在
|
|
||||||
$dir = dirname($dbPath);
|
$dir = dirname($dbPath);
|
||||||
if (!is_dir($dir)) {
|
if (!is_dir($dir)) {
|
||||||
mkdir($dir, 0755, true);
|
mkdir($dir, 0755, true);
|
||||||
@ -32,9 +24,6 @@ class DBHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行查询并返回所有结果
|
* 执行查询并返回所有结果
|
||||||
* @param string $sql SQL语句
|
|
||||||
* @param array $params 参数数组
|
|
||||||
* @return array 结果数组
|
|
||||||
*/
|
*/
|
||||||
public function query($sql, $params = []) {
|
public function query($sql, $params = []) {
|
||||||
try {
|
try {
|
||||||
@ -42,16 +31,13 @@ class DBHelper {
|
|||||||
$stmt->execute($params);
|
$stmt->execute($params);
|
||||||
return $stmt->fetchAll();
|
return $stmt->fetchAll();
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
error_log("数据库查询错误: " . $e->getMessage() . " | SQL: " . $sql);
|
Logger::error("数据库查询错误: " . $e->getMessage() . " | SQL: " . $sql, 'DBHelper::query');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行查询并返回单条结果
|
* 执行查询并返回单条结果
|
||||||
* @param string $sql SQL语句
|
|
||||||
* @param array $params 参数数组
|
|
||||||
* @return array|false 结果数组或false
|
|
||||||
*/
|
*/
|
||||||
public function queryOne($sql, $params = []) {
|
public function queryOne($sql, $params = []) {
|
||||||
try {
|
try {
|
||||||
@ -59,32 +45,26 @@ class DBHelper {
|
|||||||
$stmt->execute($params);
|
$stmt->execute($params);
|
||||||
return $stmt->fetch();
|
return $stmt->fetch();
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
error_log("数据库查询错误: " . $e->getMessage() . " | SQL: " . $sql);
|
Logger::error("数据库查询错误: " . $e->getMessage() . " | SQL: " . $sql, 'DBHelper::queryOne');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行插入、更新、删除操作
|
* 执行插入、更新、删除操作
|
||||||
* @param string $sql SQL语句
|
|
||||||
* @param array $params 参数数组
|
|
||||||
* @return bool 是否成功
|
|
||||||
*/
|
*/
|
||||||
public function execute($sql, $params = []) {
|
public function execute($sql, $params = []) {
|
||||||
try {
|
try {
|
||||||
$stmt = $this->db->prepare($sql);
|
$stmt = $this->db->prepare($sql);
|
||||||
return $stmt->execute($params);
|
return $stmt->execute($params);
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
error_log("数据库执行错误: " . $e->getMessage() . " | SQL: " . $sql);
|
Logger::error("数据库执行错误: " . $e->getMessage() . " | SQL: " . $sql, 'DBHelper::execute');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插入数据并返回最后插入的ID
|
* 插入数据并返回最后插入的ID
|
||||||
* @param string $sql SQL语句
|
|
||||||
* @param array $params 参数数组
|
|
||||||
* @return int|false 最后插入的ID或false
|
|
||||||
*/
|
*/
|
||||||
public function insert($sql, $params = []) {
|
public function insert($sql, $params = []) {
|
||||||
try {
|
try {
|
||||||
@ -92,36 +72,23 @@ class DBHelper {
|
|||||||
$stmt->execute($params);
|
$stmt->execute($params);
|
||||||
return $this->db->lastInsertId();
|
return $this->db->lastInsertId();
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
error_log("数据库插入错误: " . $e->getMessage() . " | SQL: " . $sql);
|
Logger::error("数据库插入错误: " . $e->getMessage() . " | SQL: " . $sql, 'DBHelper::insert');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始事务
|
|
||||||
*/
|
|
||||||
public function beginTransaction() {
|
public function beginTransaction() {
|
||||||
return $this->db->beginTransaction();
|
return $this->db->beginTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 提交事务
|
|
||||||
*/
|
|
||||||
public function commit() {
|
public function commit() {
|
||||||
return $this->db->commit();
|
return $this->db->commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 回滚事务
|
|
||||||
*/
|
|
||||||
public function rollBack() {
|
public function rollBack() {
|
||||||
return $this->db->rollBack();
|
return $this->db->rollBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取数据库连接对象(用于高级操作)
|
|
||||||
* @return PDO
|
|
||||||
*/
|
|
||||||
public function getConnection() {
|
public function getConnection() {
|
||||||
return $this->db;
|
return $this->db;
|
||||||
}
|
}
|
||||||
@ -133,7 +100,6 @@ function getVpsDB() {
|
|||||||
if ($db === null) {
|
if ($db === null) {
|
||||||
$dbPath = __DIR__ . '/db/vps.db';
|
$dbPath = __DIR__ . '/db/vps.db';
|
||||||
$db = new DBHelper($dbPath);
|
$db = new DBHelper($dbPath);
|
||||||
// 初始化表结构
|
|
||||||
initVpsTables($db);
|
initVpsTables($db);
|
||||||
}
|
}
|
||||||
return $db;
|
return $db;
|
||||||
@ -156,7 +122,6 @@ function getStatusDB() {
|
|||||||
if ($db === null) {
|
if ($db === null) {
|
||||||
$dbPath = __DIR__ . '/db/status.db';
|
$dbPath = __DIR__ . '/db/status.db';
|
||||||
$db = new DBHelper($dbPath);
|
$db = new DBHelper($dbPath);
|
||||||
// 初始化表结构
|
|
||||||
initStatusTables($db);
|
initStatusTables($db);
|
||||||
}
|
}
|
||||||
return $db;
|
return $db;
|
||||||
@ -176,7 +141,8 @@ function initVpsTables($db) {
|
|||||||
api_key TEXT NOT NULL,
|
api_key TEXT NOT NULL,
|
||||||
auto_monitor BOOLEAN DEFAULT 1,
|
auto_monitor BOOLEAN DEFAULT 1,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE(site_url, account)
|
||||||
)
|
)
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
@ -199,15 +165,17 @@ function initVpsListTables($db) {
|
|||||||
bandwidth TEXT,
|
bandwidth TEXT,
|
||||||
os_type TEXT,
|
os_type TEXT,
|
||||||
status TEXT,
|
status TEXT,
|
||||||
|
amount TEXT,
|
||||||
|
nextduedate INTEGER,
|
||||||
section BOOLEAN DEFAULT 0,
|
section BOOLEAN DEFAULT 0,
|
||||||
last_check TIMESTAMP,
|
last_check TIMESTAMP,
|
||||||
FOREIGN KEY (config_id) REFERENCES configs(id),
|
FOREIGN KEY (config_id) REFERENCES configs(id)
|
||||||
UNIQUE(config_id, vps_id)
|
|
||||||
)
|
)
|
||||||
");
|
");
|
||||||
|
|
||||||
$db->execute('CREATE INDEX IF NOT EXISTS idx_vps_config ON vps_list(config_id)');
|
$db->execute('CREATE INDEX IF NOT EXISTS idx_vps_config ON vps_list(config_id)');
|
||||||
$db->execute('CREATE INDEX IF NOT EXISTS idx_vps_vps_id ON vps_list(vps_id)');
|
$db->execute('CREATE INDEX IF NOT EXISTS idx_vps_vps_id ON vps_list(vps_id)');
|
||||||
|
$db->execute('CREATE INDEX IF NOT EXISTS idx_vps_unique ON vps_list(vps_id, ip_address)');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""VPS Hub 数据库管理模块"""
|
||||||
VPS Hub 数据库管理模块
|
|
||||||
负责初始化和管理三个SQLite数据库: vps.db, vpslist.db, status.db
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
@ -14,38 +11,24 @@ class DatabaseManager:
|
|||||||
"""数据库管理器"""
|
"""数据库管理器"""
|
||||||
|
|
||||||
def __init__(self, db_dir=None):
|
def __init__(self, db_dir=None):
|
||||||
"""初始化数据库管理器
|
|
||||||
|
|
||||||
Args:
|
|
||||||
db_dir: 数据库文件目录,默认为app/db/
|
|
||||||
"""
|
|
||||||
if db_dir is None:
|
if db_dir is None:
|
||||||
db_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'db')
|
db_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'db')
|
||||||
|
|
||||||
self.db_dir = db_dir
|
self.db_dir = db_dir
|
||||||
os.makedirs(db_dir, exist_ok=True)
|
os.makedirs(db_dir, exist_ok=True)
|
||||||
|
|
||||||
# 数据库文件路径
|
|
||||||
self.vps_db = os.path.join(db_dir, 'vps.db')
|
self.vps_db = os.path.join(db_dir, 'vps.db')
|
||||||
self.vpslist_db = os.path.join(db_dir, 'vpslist.db')
|
self.vpslist_db = os.path.join(db_dir, 'vpslist.db')
|
||||||
self.status_db = os.path.join(db_dir, 'status.db')
|
self.status_db = os.path.join(db_dir, 'status.db')
|
||||||
|
|
||||||
# 初始化所有数据库
|
|
||||||
self.init_vps_db()
|
self.init_vps_db()
|
||||||
self.init_vpslist_db()
|
self.init_vpslist_db()
|
||||||
self.init_status_db()
|
self.init_status_db()
|
||||||
|
|
||||||
def get_connection(self, db_path):
|
def get_connection(self, db_path):
|
||||||
"""获取数据库连接
|
"""获取数据库连接"""
|
||||||
|
|
||||||
Args:
|
|
||||||
db_path: 数据库文件路径
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
SQLite连接对象
|
|
||||||
"""
|
|
||||||
conn = sqlite3.connect(db_path)
|
conn = sqlite3.connect(db_path)
|
||||||
conn.row_factory = sqlite3.Row # 使结果可以通过列名访问
|
conn.row_factory = sqlite3.Row
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
def init_vps_db(self):
|
def init_vps_db(self):
|
||||||
@ -63,7 +46,8 @@ class DatabaseManager:
|
|||||||
api_key TEXT NOT NULL,
|
api_key TEXT NOT NULL,
|
||||||
auto_monitor BOOLEAN DEFAULT 1,
|
auto_monitor BOOLEAN DEFAULT 1,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE(site_url, account)
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
@ -89,16 +73,17 @@ class DatabaseManager:
|
|||||||
bandwidth TEXT,
|
bandwidth TEXT,
|
||||||
os_type TEXT,
|
os_type TEXT,
|
||||||
status TEXT,
|
status TEXT,
|
||||||
|
amount TEXT,
|
||||||
|
nextduedate INTEGER,
|
||||||
section BOOLEAN DEFAULT 0,
|
section BOOLEAN DEFAULT 0,
|
||||||
last_check TIMESTAMP,
|
last_check TIMESTAMP,
|
||||||
FOREIGN KEY (config_id) REFERENCES configs(id),
|
FOREIGN KEY (config_id) REFERENCES configs(id)
|
||||||
UNIQUE(config_id, vps_id)
|
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
# 创建索引以提高查询性能
|
|
||||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_vps_config ON vps_list(config_id)')
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_vps_config ON vps_list(config_id)')
|
||||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_vps_vps_id ON vps_list(vps_id)')
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_vps_vps_id ON vps_list(vps_id)')
|
||||||
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_vps_unique ON vps_list(vps_id, ip_address)')
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
@ -108,7 +93,6 @@ class DatabaseManager:
|
|||||||
conn = self.get_connection(self.status_db)
|
conn = self.get_connection(self.status_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# Ping状态记录表
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS ping_status (
|
CREATE TABLE IF NOT EXISTS ping_status (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@ -120,7 +104,6 @@ class DatabaseManager:
|
|||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
# VPS摘要统计表
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS vps_summary (
|
CREATE TABLE IF NOT EXISTS vps_summary (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@ -138,7 +121,6 @@ class DatabaseManager:
|
|||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
# 创建索引
|
|
||||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_ping_vps ON ping_status(vps_id)')
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_ping_vps ON ping_status(vps_id)')
|
||||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_ping_time ON ping_status(check_time)')
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_ping_time ON ping_status(check_time)')
|
||||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_summary_vps ON vps_summary(vps_id)')
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_summary_vps ON vps_summary(vps_id)')
|
||||||
@ -150,23 +132,19 @@ class DatabaseManager:
|
|||||||
# ==================== vps.db 操作 ====================
|
# ==================== vps.db 操作 ====================
|
||||||
|
|
||||||
def add_config(self, api_label, site_type, account, api_key, site_url=None, auto_monitor=True):
|
def add_config(self, api_label, site_type, account, api_key, site_url=None, auto_monitor=True):
|
||||||
"""添加VPS配置
|
"""添加VPS配置,基于site_url和account去重"""
|
||||||
|
|
||||||
Args:
|
|
||||||
api_label: API标识(必填,唯一)
|
|
||||||
site_type: 网站类型 (mofang/aliyun/tencent)
|
|
||||||
account: 账户
|
|
||||||
api_key: API密钥
|
|
||||||
site_url: 网站链接
|
|
||||||
auto_monitor: 是否开启自动监控
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
新配置的ID
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.vps_db)
|
conn = self.get_connection(self.vps_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
existing = cursor.execute(
|
||||||
|
'SELECT id FROM configs WHERE site_url = ? AND account = ?',
|
||||||
|
(site_url, account)
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
return existing['id']
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT INTO configs (api_label, site_type, site_url, account, api_key, auto_monitor)
|
INSERT INTO configs (api_label, site_type, site_url, account, api_key, auto_monitor)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
@ -182,11 +160,7 @@ class DatabaseManager:
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def get_all_configs(self):
|
def get_all_configs(self):
|
||||||
"""获取所有配置
|
"""获取所有配置"""
|
||||||
|
|
||||||
Returns:
|
|
||||||
配置列表
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.vps_db)
|
conn = self.get_connection(self.vps_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@ -197,14 +171,7 @@ class DatabaseManager:
|
|||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_config_by_id(self, config_id):
|
def get_config_by_id(self, config_id):
|
||||||
"""根据ID获取配置
|
"""根据ID获取配置"""
|
||||||
|
|
||||||
Args:
|
|
||||||
config_id: 配置ID
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
配置字典或None
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.vps_db)
|
conn = self.get_connection(self.vps_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@ -215,22 +182,13 @@ class DatabaseManager:
|
|||||||
return dict(row) if row else None
|
return dict(row) if row else None
|
||||||
|
|
||||||
def update_config(self, config_id, **kwargs):
|
def update_config(self, config_id, **kwargs):
|
||||||
"""更新配置
|
"""更新配置"""
|
||||||
|
|
||||||
Args:
|
|
||||||
config_id: 配置ID
|
|
||||||
**kwargs: 要更新的字段
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
是否成功
|
|
||||||
"""
|
|
||||||
if not kwargs:
|
if not kwargs:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
conn = self.get_connection(self.vps_db)
|
conn = self.get_connection(self.vps_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# 构建UPDATE语句
|
|
||||||
fields = ', '.join([f"{key} = ?" for key in kwargs.keys()])
|
fields = ', '.join([f"{key} = ?" for key in kwargs.keys()])
|
||||||
values = list(kwargs.values())
|
values = list(kwargs.values())
|
||||||
values.append(config_id)
|
values.append(config_id)
|
||||||
@ -244,14 +202,7 @@ class DatabaseManager:
|
|||||||
return affected > 0
|
return affected > 0
|
||||||
|
|
||||||
def delete_config(self, config_id):
|
def delete_config(self, config_id):
|
||||||
"""删除配置
|
"""删除配置"""
|
||||||
|
|
||||||
Args:
|
|
||||||
config_id: 配置ID
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
是否成功
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.vps_db)
|
conn = self.get_connection(self.vps_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@ -263,30 +214,88 @@ class DatabaseManager:
|
|||||||
|
|
||||||
return affected > 0
|
return affected > 0
|
||||||
|
|
||||||
|
def reset_config_ids(self):
|
||||||
|
"""重置configs表的ID序列,从1开始连续编号"""
|
||||||
|
conn = self.get_connection(self.vps_db)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor.execute('SELECT * FROM configs ORDER BY id')
|
||||||
|
configs = [dict(row) for row in cursor.fetchall()]
|
||||||
|
|
||||||
|
if not configs:
|
||||||
|
return True
|
||||||
|
|
||||||
|
cursor.execute('DROP TABLE IF EXISTS configs_backup')
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE configs_backup AS SELECT * FROM configs
|
||||||
|
''')
|
||||||
|
|
||||||
|
cursor.execute('DELETE FROM configs')
|
||||||
|
cursor.execute("DELETE FROM sqlite_sequence WHERE name='configs'")
|
||||||
|
|
||||||
|
for config in configs:
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT INTO configs (api_label, site_type, site_url, account, api_key, auto_monitor, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
''', (
|
||||||
|
config['api_label'],
|
||||||
|
config['site_type'],
|
||||||
|
config['site_url'],
|
||||||
|
config['account'],
|
||||||
|
config['api_key'],
|
||||||
|
config['auto_monitor'],
|
||||||
|
config['created_at'],
|
||||||
|
config['updated_at']
|
||||||
|
))
|
||||||
|
|
||||||
|
cursor.execute('DROP TABLE IF EXISTS configs_backup')
|
||||||
|
conn.commit()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
conn.rollback()
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
# ==================== vpslist.db 操作 ====================
|
# ==================== vpslist.db 操作 ====================
|
||||||
|
|
||||||
def add_vps(self, config_id, vps_id, domain=None, ip_address=None,
|
def get_next_available_id(self):
|
||||||
product_name=None, section=False):
|
"""获取下一个可用的ID(填补空缺)"""
|
||||||
"""添加VPS到列表
|
|
||||||
|
|
||||||
Args:
|
|
||||||
config_id: 配置ID
|
|
||||||
vps_id: VPS在平台的ID
|
|
||||||
domain: 域名
|
|
||||||
ip_address: IP地址
|
|
||||||
product_name: 产品名称
|
|
||||||
section: 是否标记为需要监控
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
新记录的ID
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.vpslist_db)
|
conn = self.get_connection(self.vpslist_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('SELECT id FROM vps_list ORDER BY id')
|
||||||
INSERT INTO vps_list (config_id, vps_id, domain, ip_address, product_name, section, last_check)
|
ids = [row['id'] for row in cursor.fetchall()]
|
||||||
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
conn.close()
|
||||||
''', (config_id, vps_id, domain, ip_address, product_name, section))
|
|
||||||
|
if not ids:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
for i in range(1, max(ids) + 1):
|
||||||
|
if i not in ids:
|
||||||
|
return i
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def add_vps(self, config_id, vps_id, domain=None, ip_address=None, product_name=None, section=False, custom_id=None):
|
||||||
|
"""添加VPS到列表,支持指定ID以填补空缺"""
|
||||||
|
conn = self.get_connection(self.vpslist_db)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
if custom_id is None:
|
||||||
|
custom_id = self.get_next_available_id()
|
||||||
|
|
||||||
|
if custom_id:
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT INTO vps_list (id, config_id, vps_id, domain, ip_address, product_name, section, last_check)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
|
''', (custom_id, config_id, vps_id, domain, ip_address, product_name, section))
|
||||||
|
else:
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT INTO vps_list (config_id, vps_id, domain, ip_address, product_name, section, last_check)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
|
''', (config_id, vps_id, domain, ip_address, product_name, section))
|
||||||
|
|
||||||
record_id = cursor.lastrowid
|
record_id = cursor.lastrowid
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@ -295,42 +304,68 @@ class DatabaseManager:
|
|||||||
return record_id
|
return record_id
|
||||||
|
|
||||||
def batch_add_vps(self, vps_list):
|
def batch_add_vps(self, vps_list):
|
||||||
"""批量添加VPS
|
"""批量添加VPS,基于vps_id和ip_address去重"""
|
||||||
|
|
||||||
Args:
|
|
||||||
vps_list: VPS信息列表,每个元素是字典
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.vpslist_db)
|
conn = self.get_connection(self.vpslist_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
for vps in vps_list:
|
for vps in vps_list:
|
||||||
cursor.execute('''
|
existing = cursor.execute(
|
||||||
INSERT OR REPLACE INTO vps_list
|
'SELECT id FROM vps_list WHERE vps_id = ? AND ip_address = ?',
|
||||||
(config_id, vps_id, domain, ip_address, product_name, cpu_cores, memory_size, disk_size, bandwidth, os_type, section, last_check)
|
(vps['vps_id'], vps.get('ip_address'))
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
).fetchone()
|
||||||
''', (
|
|
||||||
vps['config_id'],
|
if existing:
|
||||||
vps['vps_id'],
|
continue
|
||||||
vps.get('domain'),
|
|
||||||
vps.get('ip_address'),
|
custom_id = self.get_next_available_id()
|
||||||
vps.get('product_name'),
|
|
||||||
vps.get('cpu_cores'),
|
if custom_id:
|
||||||
vps.get('memory_size'),
|
cursor.execute('''
|
||||||
vps.get('disk_size'),
|
INSERT INTO vps_list
|
||||||
vps.get('bandwidth'),
|
(id, config_id, vps_id, domain, ip_address, product_name, cpu_cores, memory_size, disk_size, bandwidth, os_type, amount, nextduedate, section, last_check)
|
||||||
vps.get('os_type'),
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
vps.get('section', False)
|
''', (
|
||||||
))
|
custom_id,
|
||||||
|
vps['config_id'],
|
||||||
|
vps['vps_id'],
|
||||||
|
vps.get('domain'),
|
||||||
|
vps.get('ip_address'),
|
||||||
|
vps.get('product_name'),
|
||||||
|
vps.get('cpu_cores'),
|
||||||
|
vps.get('memory_size'),
|
||||||
|
vps.get('disk_size'),
|
||||||
|
vps.get('bandwidth'),
|
||||||
|
vps.get('os_type'),
|
||||||
|
vps.get('amount'),
|
||||||
|
vps.get('nextduedate'),
|
||||||
|
vps.get('section', False)
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
cursor.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, section, last_check)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
|
''', (
|
||||||
|
vps['config_id'],
|
||||||
|
vps['vps_id'],
|
||||||
|
vps.get('domain'),
|
||||||
|
vps.get('ip_address'),
|
||||||
|
vps.get('product_name'),
|
||||||
|
vps.get('cpu_cores'),
|
||||||
|
vps.get('memory_size'),
|
||||||
|
vps.get('disk_size'),
|
||||||
|
vps.get('bandwidth'),
|
||||||
|
vps.get('os_type'),
|
||||||
|
vps.get('amount'),
|
||||||
|
vps.get('nextduedate'),
|
||||||
|
vps.get('section', False)
|
||||||
|
))
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def get_all_vps(self):
|
def get_all_vps(self):
|
||||||
"""获取所有VPS
|
"""获取所有VPS"""
|
||||||
|
|
||||||
Returns:
|
|
||||||
VPS列表
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.vpslist_db)
|
conn = self.get_connection(self.vpslist_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@ -341,14 +376,7 @@ class DatabaseManager:
|
|||||||
return vps_list
|
return vps_list
|
||||||
|
|
||||||
def get_vps_by_config(self, config_id):
|
def get_vps_by_config(self, config_id):
|
||||||
"""根据配置ID获取VPS列表
|
"""根据配置ID获取VPS列表"""
|
||||||
|
|
||||||
Args:
|
|
||||||
config_id: 配置ID
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
VPS列表
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.vpslist_db)
|
conn = self.get_connection(self.vpslist_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@ -359,15 +387,7 @@ class DatabaseManager:
|
|||||||
return vps_list
|
return vps_list
|
||||||
|
|
||||||
def update_vps_details(self, vps_id, **kwargs):
|
def update_vps_details(self, vps_id, **kwargs):
|
||||||
"""更新VPS详细信息
|
"""更新VPS详细信息"""
|
||||||
|
|
||||||
Args:
|
|
||||||
vps_id: VPS ID
|
|
||||||
**kwargs: 要更新的字段
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
是否成功
|
|
||||||
"""
|
|
||||||
if not kwargs:
|
if not kwargs:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -387,23 +407,11 @@ class DatabaseManager:
|
|||||||
return affected > 0
|
return affected > 0
|
||||||
|
|
||||||
def update_vps_status(self, vps_id, status):
|
def update_vps_status(self, vps_id, status):
|
||||||
"""更新VPS状态
|
"""更新VPS状态"""
|
||||||
|
|
||||||
Args:
|
|
||||||
vps_id: VPS ID
|
|
||||||
status: 状态 (on/off/unknown)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
是否成功
|
|
||||||
"""
|
|
||||||
return self.update_vps_details(vps_id, status=status)
|
return self.update_vps_details(vps_id, status=status)
|
||||||
|
|
||||||
def get_monitored_vps(self):
|
def get_monitored_vps(self):
|
||||||
"""获取所有标记为需要监控的VPS
|
"""获取所有标记为需要监控的VPS"""
|
||||||
|
|
||||||
Returns:
|
|
||||||
VPS列表
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.vpslist_db)
|
conn = self.get_connection(self.vpslist_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@ -413,17 +421,61 @@ class DatabaseManager:
|
|||||||
conn.close()
|
conn.close()
|
||||||
return vps_list
|
return vps_list
|
||||||
|
|
||||||
|
def reset_vps_list_ids(self):
|
||||||
|
"""重置vps_list表的ID序列,从1开始连续编号"""
|
||||||
|
conn = self.get_connection(self.vpslist_db)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor.execute('SELECT * FROM vps_list ORDER BY id')
|
||||||
|
vps_items = [dict(row) for row in cursor.fetchall()]
|
||||||
|
|
||||||
|
if not vps_items:
|
||||||
|
return True
|
||||||
|
|
||||||
|
cursor.execute('DROP TABLE IF EXISTS vps_list_backup')
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE vps_list_backup AS SELECT * FROM vps_list
|
||||||
|
''')
|
||||||
|
|
||||||
|
cursor.execute('DELETE FROM vps_list')
|
||||||
|
cursor.execute("DELETE FROM sqlite_sequence WHERE name='vps_list'")
|
||||||
|
|
||||||
|
for vps in vps_items:
|
||||||
|
cursor.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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
''', (
|
||||||
|
vps['config_id'],
|
||||||
|
vps['vps_id'],
|
||||||
|
vps.get('domain'),
|
||||||
|
vps.get('ip_address'),
|
||||||
|
vps.get('product_name'),
|
||||||
|
vps.get('cpu_cores'),
|
||||||
|
vps.get('memory_size'),
|
||||||
|
vps.get('disk_size'),
|
||||||
|
vps.get('bandwidth'),
|
||||||
|
vps.get('os_type'),
|
||||||
|
vps.get('amount'),
|
||||||
|
vps.get('nextduedate'),
|
||||||
|
vps.get('status'),
|
||||||
|
vps.get('section', 0),
|
||||||
|
vps.get('last_check')
|
||||||
|
))
|
||||||
|
|
||||||
|
cursor.execute('DROP TABLE IF EXISTS vps_list_backup')
|
||||||
|
conn.commit()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
conn.rollback()
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
# ==================== status.db 操作 ====================
|
# ==================== status.db 操作 ====================
|
||||||
|
|
||||||
def save_ping_status(self, vps_id, target, status, latency_ms=None):
|
def save_ping_status(self, vps_id, target, status, latency_ms=None):
|
||||||
"""保存Ping状态记录
|
"""保存Ping状态记录"""
|
||||||
|
|
||||||
Args:
|
|
||||||
vps_id: VPS ID
|
|
||||||
target: 目标(IP或域名)
|
|
||||||
status: 状态 (normal/abnormal)
|
|
||||||
latency_ms: 延迟(ms)
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.status_db)
|
conn = self.get_connection(self.status_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@ -436,15 +488,7 @@ class DatabaseManager:
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def get_ping_records(self, vps_id, date=None):
|
def get_ping_records(self, vps_id, date=None):
|
||||||
"""获取Ping记录
|
"""获取Ping记录"""
|
||||||
|
|
||||||
Args:
|
|
||||||
vps_id: VPS ID
|
|
||||||
date: 日期 (YYYY-MM-DD),为空则获取所有记录
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Ping记录列表
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.status_db)
|
conn = self.get_connection(self.status_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@ -467,11 +511,7 @@ class DatabaseManager:
|
|||||||
return records
|
return records
|
||||||
|
|
||||||
def cleanup_old_ping_records(self, days=30):
|
def cleanup_old_ping_records(self, days=30):
|
||||||
"""清理旧的Ping记录
|
"""清理旧的Ping记录"""
|
||||||
|
|
||||||
Args:
|
|
||||||
days: 保留天数,默认30天
|
|
||||||
"""
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
conn = self.get_connection(self.status_db)
|
conn = self.get_connection(self.status_db)
|
||||||
@ -489,20 +529,7 @@ class DatabaseManager:
|
|||||||
def save_vps_summary(self, vps_id, date, avg_latency, max_latency, min_latency,
|
def save_vps_summary(self, vps_id, date, avg_latency, max_latency, min_latency,
|
||||||
count_under_100, count_100_to_300, count_300_to_500,
|
count_under_100, count_100_to_300, count_300_to_500,
|
||||||
count_abnormal, availability):
|
count_abnormal, availability):
|
||||||
"""保存VPS摘要统计
|
"""保存VPS摘要统计"""
|
||||||
|
|
||||||
Args:
|
|
||||||
vps_id: VPS ID
|
|
||||||
date: 日期 (YYYY-MM-DD)
|
|
||||||
avg_latency: 平均延迟
|
|
||||||
max_latency: 最大延迟
|
|
||||||
min_latency: 最小延迟
|
|
||||||
count_under_100: <100ms次数
|
|
||||||
count_100_to_300: 100-300ms次数
|
|
||||||
count_300_to_500: 300-500ms次数
|
|
||||||
count_abnormal: 异常次数
|
|
||||||
availability: 可用性评分
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.status_db)
|
conn = self.get_connection(self.status_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@ -519,15 +546,7 @@ class DatabaseManager:
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def get_vps_summary(self, vps_id, date=None):
|
def get_vps_summary(self, vps_id, date=None):
|
||||||
"""获取VPS摘要统计
|
"""获取VPS摘要统计"""
|
||||||
|
|
||||||
Args:
|
|
||||||
vps_id: VPS ID
|
|
||||||
date: 日期,为空则获取最新一条
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
摘要统计字典或None
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.status_db)
|
conn = self.get_connection(self.status_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@ -542,21 +561,13 @@ class DatabaseManager:
|
|||||||
return dict(row) if row else None
|
return dict(row) if row else None
|
||||||
|
|
||||||
def get_all_summaries(self, date=None):
|
def get_all_summaries(self, date=None):
|
||||||
"""获取所有VPS的摘要统计
|
"""获取所有VPS的摘要统计"""
|
||||||
|
|
||||||
Args:
|
|
||||||
date: 日期,为空则获取最新
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
摘要统计列表
|
|
||||||
"""
|
|
||||||
conn = self.get_connection(self.status_db)
|
conn = self.get_connection(self.status_db)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
if date:
|
if date:
|
||||||
cursor.execute('SELECT * FROM vps_summary WHERE date = ? ORDER BY vps_id', (date,))
|
cursor.execute('SELECT * FROM vps_summary WHERE date = ? ORDER BY vps_id', (date,))
|
||||||
else:
|
else:
|
||||||
# 获取每个VPS的最新摘要
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
SELECT vs.* FROM vps_summary vs
|
SELECT vs.* FROM vps_summary vs
|
||||||
INNER JOIN (
|
INNER JOIN (
|
||||||
|
|||||||
102
app/logger.php
Normal file
102
app/logger.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* PHP日志管理类
|
||||||
|
* 统一处理所有PHP文件的日志记录
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
private static $logFile = __DIR__ . '/logs/php.log';
|
||||||
|
private static $initialized = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化日志系统
|
||||||
|
*/
|
||||||
|
public static function init() {
|
||||||
|
if (self::$initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保日志目录存在
|
||||||
|
$logDir = dirname(self::$logFile);
|
||||||
|
if (!is_dir($logDir)) {
|
||||||
|
mkdir($logDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建日志文件(如果不存在)
|
||||||
|
if (!file_exists(self::$logFile)) {
|
||||||
|
touch(self::$logFile);
|
||||||
|
chmod(self::$logFile, 0666);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置PHP错误日志路径
|
||||||
|
ini_set('error_log', self::$logFile);
|
||||||
|
ini_set('log_errors', 1);
|
||||||
|
ini_set('display_errors', 0); // 生产环境不显示错误
|
||||||
|
|
||||||
|
self::$initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录日志
|
||||||
|
* @param string $message 日志消息
|
||||||
|
* @param string $level 日志级别 (INFO, WARNING, ERROR, DEBUG)
|
||||||
|
* @param string $source 来源文件/函数
|
||||||
|
*/
|
||||||
|
public static function log($message, $level = 'INFO', $source = '') {
|
||||||
|
self::init();
|
||||||
|
|
||||||
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
|
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||||
|
$caller = $source ?: (isset($backtrace[1]) ? basename($backtrace[1]['file']) . ':' . $backtrace[1]['line'] : 'unknown');
|
||||||
|
|
||||||
|
$logEntry = "[{$timestamp}] [{$level}] [{$caller}] {$message}" . PHP_EOL;
|
||||||
|
|
||||||
|
file_put_contents(self::$logFile, $logEntry, FILE_APPEND | LOCK_EX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录信息日志
|
||||||
|
*/
|
||||||
|
public static function info($message, $source = '') {
|
||||||
|
self::log($message, 'INFO', $source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录警告日志
|
||||||
|
*/
|
||||||
|
public static function warning($message, $source = '') {
|
||||||
|
self::log($message, 'WARNING', $source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录错误日志
|
||||||
|
*/
|
||||||
|
public static function error($message, $source = '') {
|
||||||
|
self::log($message, 'ERROR', $source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录调试日志
|
||||||
|
*/
|
||||||
|
public static function debug($message, $source = '') {
|
||||||
|
self::log($message, 'DEBUG', $source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取日志文件路径
|
||||||
|
*/
|
||||||
|
public static function getLogFile() {
|
||||||
|
return self::$logFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空日志文件
|
||||||
|
*/
|
||||||
|
public static function clear() {
|
||||||
|
file_put_contents(self::$logFile, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动初始化
|
||||||
|
Logger::init();
|
||||||
|
?>
|
||||||
0
app/logs/php.log
Normal file
0
app/logs/php.log
Normal file
@ -1,9 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""VPS Hub 多平台监控程序"""
|
||||||
VPS Hub 多平台监控程序
|
|
||||||
功能:支持多平台(魔方/阿里云/腾讯云)的VPS监控、自动开机和可用性统计
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@ -16,7 +13,6 @@ import schedule
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# 导入数据库管理器
|
|
||||||
from db_manager import DatabaseManager
|
from db_manager import DatabaseManager
|
||||||
|
|
||||||
|
|
||||||
@ -29,41 +25,12 @@ class PlatformAdapter:
|
|||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.jwt_token = None
|
self.jwt_token = None
|
||||||
|
|
||||||
def login(self):
|
|
||||||
"""登录获取Token - 需要子类实现"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def get_vps_list(self):
|
|
||||||
"""获取VPS列表 - 需要子类实现"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def get_vps_status(self, vps_id):
|
|
||||||
"""获取VPS状态 - 需要子类实现"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def get_vps_details(self, vps_id):
|
|
||||||
"""获取VPS详细信息 - 需要子类实现"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def power_on(self, vps_id):
|
|
||||||
"""开机 - 需要子类实现"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def power_off(self, vps_id):
|
|
||||||
"""关机 - 需要子类实现"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def hard_reboot(self, vps_id):
|
|
||||||
"""硬重启 - 需要子类实现"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class MofangAdapter(PlatformAdapter):
|
class MofangAdapter(PlatformAdapter):
|
||||||
"""魔方平台适配器(核云IDC等使用此适配器)"""
|
"""魔方平台适配器"""
|
||||||
|
|
||||||
def __init__(self, site_url, account, api_key):
|
def __init__(self, site_url, account, api_key):
|
||||||
super().__init__(site_url, account, api_key)
|
super().__init__(site_url, account, api_key)
|
||||||
# 魔方平台的API路径统一为 /v1
|
|
||||||
if not self.site_url:
|
if not self.site_url:
|
||||||
raise ValueError("魔方平台必须提供网站链接(API地址)")
|
raise ValueError("魔方平台必须提供网站链接(API地址)")
|
||||||
|
|
||||||
@ -79,7 +46,6 @@ class MofangAdapter(PlatformAdapter):
|
|||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
try:
|
try:
|
||||||
# 魔方平台API统一在 /v1 路径下
|
|
||||||
url = f"{self.site_url}/v1/login_api"
|
url = f"{self.site_url}/v1/login_api"
|
||||||
data = {
|
data = {
|
||||||
'account': self.account,
|
'account': self.account,
|
||||||
@ -729,7 +695,9 @@ class MonitorService:
|
|||||||
'disk_size': disk_size,
|
'disk_size': disk_size,
|
||||||
'bandwidth': bandwidth,
|
'bandwidth': bandwidth,
|
||||||
'os_type': os_type,
|
'os_type': os_type,
|
||||||
'section': config['auto_monitor'] # 根据配置的auto_monitor设置
|
'amount': host.get('amount'),
|
||||||
|
'nextduedate': host.get('nextduedate'),
|
||||||
|
'section': config['auto_monitor']
|
||||||
})
|
})
|
||||||
|
|
||||||
# 批量添加到数据库
|
# 批量添加到数据库
|
||||||
|
|||||||
98
index.php
98
index.php
@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/app/db_helper.php';
|
require_once __DIR__ . '/app/db_helper.php';
|
||||||
|
require_once __DIR__ . '/app/logger.php';
|
||||||
require_once __DIR__ . '/mofangidc.php';
|
require_once __DIR__ . '/mofangidc.php';
|
||||||
|
|
||||||
// 存储API_PASS
|
// 存储API_PASS
|
||||||
@ -110,50 +111,36 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$db = getVpsDB();
|
$db = getVpsDB();
|
||||||
$configs = $db->query('SELECT * FROM configs ORDER BY id');
|
$configs = $db->query('SELECT * FROM configs ORDER BY id');
|
||||||
|
|
||||||
// 构建配置ID到配置的映射
|
// 构建配置ID到api_label的映射
|
||||||
$configMap = [];
|
$configMap = [];
|
||||||
foreach ($configs as $config) {
|
foreach ($configs as $config) {
|
||||||
$configMap[$config['id']] = $config;
|
$configMap[$config['id']] = $config['api_label'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取所有VPS列表(只从vpslist.db查询)
|
// 获取所有VPS列表(从vpslist.db查询)
|
||||||
$listDb = getVpsListDB();
|
$listDb = getVpsListDB();
|
||||||
$vpsList = $listDb->query('SELECT * FROM vps_list ORDER BY config_id, vps_id');
|
$vpsList = $listDb->query('SELECT * FROM vps_list ORDER BY config_id, vps_id');
|
||||||
|
|
||||||
// 为每个VPS添加site_type和site_url信息
|
// 为每个VPS添加api_label
|
||||||
foreach ($vpsList as &$vps) {
|
foreach ($vpsList as &$vps) {
|
||||||
$configId = $vps['config_id'];
|
$configId = $vps['config_id'];
|
||||||
if (isset($configMap[$configId])) {
|
$vps['api_label'] = $configMap[$configId] ?? 'Unknown';
|
||||||
$vps['site_type'] = $configMap[$configId]['site_type'];
|
|
||||||
// 修正site_url:移除末尾的/v1或/v1/
|
|
||||||
$siteUrl = $configMap[$configId]['site_url'];
|
|
||||||
$siteUrl = rtrim($siteUrl, '/'); // 移除末尾的 /
|
|
||||||
if (substr($siteUrl, -3) === '/v1') {
|
|
||||||
$siteUrl = substr($siteUrl, 0, -3); // 移除 /v1
|
|
||||||
}
|
|
||||||
$vps['site_url'] = $siteUrl;
|
|
||||||
} else {
|
|
||||||
$vps['site_type'] = 'unknown';
|
|
||||||
$vps['site_url'] = '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
unset($vps); // 解除引用
|
unset($vps);
|
||||||
|
|
||||||
// 调试信息(临时)
|
Logger::info("Total VPS count: " . count($vpsList), 'index.php');
|
||||||
error_log("Configs count: " . count($configs));
|
|
||||||
error_log("VPS List count: " . count($vpsList));
|
|
||||||
if (!empty($vpsList)) {
|
if (!empty($vpsList)) {
|
||||||
error_log("First VPS: " . json_encode($vpsList[0]));
|
Logger::debug("First VPS: " . json_encode($vpsList[0]), 'index.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按配置分组
|
// 按api_label分组
|
||||||
$vpsByConfig = [];
|
$vpsByApiLabel = [];
|
||||||
foreach ($vpsList as $vps) {
|
foreach ($vpsList as $vps) {
|
||||||
$configId = $vps['config_id'];
|
$apiLabel = $vps['api_label'];
|
||||||
if (!isset($vpsByConfig[$configId])) {
|
if (!isset($vpsByApiLabel[$apiLabel])) {
|
||||||
$vpsByConfig[$configId] = [];
|
$vpsByApiLabel[$apiLabel] = [];
|
||||||
}
|
}
|
||||||
$vpsByConfig[$configId][] = $vps;
|
$vpsByApiLabel[$apiLabel][] = $vps;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统计信息
|
// 统计信息
|
||||||
@ -296,6 +283,15 @@ foreach ($vpsList as $vps) {
|
|||||||
.power-off {
|
.power-off {
|
||||||
background-color: #dc3545;
|
background-color: #dc3545;
|
||||||
}
|
}
|
||||||
|
.power-process {
|
||||||
|
background-color: #ffc107;
|
||||||
|
box-shadow: 0 0 8px #ffc107;
|
||||||
|
animation: pulse 1.5s infinite;
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.5; }
|
||||||
|
}
|
||||||
.power-unknown {
|
.power-unknown {
|
||||||
background-color: #6c757d;
|
background-color: #6c757d;
|
||||||
}
|
}
|
||||||
@ -395,43 +391,37 @@ foreach ($vpsList as $vps) {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<!-- 按配置分组显示VPS -->
|
<!-- 按api_label分组显示VPS -->
|
||||||
<?php foreach ($configs as $config):
|
<?php foreach ($vpsByApiLabel as $apiLabel => $labelVps): ?>
|
||||||
$configVps = $vpsByConfig[$config['id']] ?? [];
|
|
||||||
|
|
||||||
$typeMap = [
|
|
||||||
'mofang' => '魔方平台',
|
|
||||||
'aliyun' => '阿里云',
|
|
||||||
'tencent' => '腾讯云'
|
|
||||||
];
|
|
||||||
$typeName = $typeMap[$config['site_type']] ?? $config['site_type'];
|
|
||||||
?>
|
|
||||||
<div class="config-section">
|
<div class="config-section">
|
||||||
<div class="config-title">
|
<div class="config-title">
|
||||||
<?php echo htmlspecialchars($config['api_label']); ?>
|
<?php echo htmlspecialchars($apiLabel); ?>
|
||||||
(<?php echo htmlspecialchars($typeName); ?>)
|
- <?php echo count($labelVps); ?> 台VPS
|
||||||
- <?php echo count($configVps); ?> 台VPS
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (empty($configVps)): ?>
|
<?php if (empty($labelVps)): ?>
|
||||||
<div class="empty-state" style="background: white; padding: 40px;">
|
<div class="empty-state" style="background: white; padding: 40px;">
|
||||||
<p>此配置下暂无VPS,请点击"手动刷新VPS列表"获取</p>
|
<p>此配置下暂无VPS,请点击“手动刷新VPS列表”获取</p>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="vps-grid">
|
<div class="vps-grid">
|
||||||
<?php foreach ($configVps as $vps):
|
<?php foreach ($labelVps as $vps):
|
||||||
$statusClass = 'power-unknown';
|
$statusClass = 'power-unknown';
|
||||||
$statusText = '未知';
|
$statusText = '未知';
|
||||||
$statusColor = '#999';
|
$statusColor = '#999';
|
||||||
|
|
||||||
if ($vps['status'] === 'on') {
|
if ($vps['status'] === 'on' || $vps['status'] === 'running') {
|
||||||
$statusClass = 'power-on';
|
$statusClass = 'power-on';
|
||||||
$statusText = '运行中';
|
$statusText = '运行中';
|
||||||
$statusColor = '#28a745';
|
$statusColor = '#28a745';
|
||||||
} elseif ($vps['status'] === 'off') {
|
} elseif ($vps['status'] === 'off' || $vps['status'] === 'stopped') {
|
||||||
$statusClass = 'power-off';
|
$statusClass = 'power-off';
|
||||||
$statusText = '已关机';
|
$statusText = '已关机';
|
||||||
$statusColor = '#dc3545';
|
$statusColor = '#dc3545';
|
||||||
|
} elseif ($vps['status'] === 'process' || $vps['status'] === 'pending' || $vps['status'] === 'rebooting') {
|
||||||
|
$statusClass = 'power-process';
|
||||||
|
$statusText = '处理中';
|
||||||
|
$statusColor = '#ffc107';
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<div class="vps-card">
|
<div class="vps-card">
|
||||||
@ -482,6 +472,20 @@ foreach ($vpsList as $vps) {
|
|||||||
<span class="info-value"><?php echo htmlspecialchars($vps['disk_size']) . ' / ' . htmlspecialchars($vps['bandwidth']) . ' / ' . htmlspecialchars($vps['os_type']); ?></span>
|
<span class="info-value"><?php echo htmlspecialchars($vps['disk_size']) . ' / ' . htmlspecialchars($vps['bandwidth']) . ' / ' . htmlspecialchars($vps['os_type']); ?></span>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($vps['amount']): ?>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">价格</span>
|
||||||
|
<span class="info-value"><?php echo htmlspecialchars($vps['amount']); ?></span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($vps['nextduedate']): ?>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">到期时间</span>
|
||||||
|
<span class="info-value"><?php echo date('Y-m-d', $vps['nextduedate']); ?></span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="power-status">
|
<div class="power-status">
|
||||||
|
|||||||
335
mofangidc.php
335
mofangidc.php
@ -1,19 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
* 魔方平台API接口封装
|
|
||||||
* 所有智简魔方的操作API以及相关函数存储在此文件中
|
|
||||||
*/
|
|
||||||
|
|
||||||
require_once __DIR__ . '/app/db_helper.php';
|
require_once __DIR__ . '/app/db_helper.php';
|
||||||
|
require_once __DIR__ . '/app/logger.php';
|
||||||
|
|
||||||
// Token缓存文件路径
|
|
||||||
define('TOKEN_CACHE_FILE', __DIR__ . '/app/token.php');
|
define('TOKEN_CACHE_FILE', __DIR__ . '/app/token.php');
|
||||||
define('TOKEN_EXPIRE_TIME', 7200); // Token过期时间(秒),2小时
|
define('TOKEN_EXPIRE_TIME', 7200);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取缓存的Token
|
* 获取缓存的Token
|
||||||
* @param int $configId 配置ID
|
|
||||||
* @return string|null Token字符串或null
|
|
||||||
*/
|
*/
|
||||||
function getCachedToken($configId) {
|
function getCachedToken($configId) {
|
||||||
if (!file_exists(TOKEN_CACHE_FILE)) {
|
if (!file_exists(TOKEN_CACHE_FILE)) {
|
||||||
@ -43,8 +36,6 @@ function getCachedToken($configId) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存Token到缓存文件
|
* 保存Token到缓存文件
|
||||||
* @param int $configId 配置ID
|
|
||||||
* @param string $token Token字符串
|
|
||||||
*/
|
*/
|
||||||
function saveToken($configId, $token) {
|
function saveToken($configId, $token) {
|
||||||
$cached_tokens = [];
|
$cached_tokens = [];
|
||||||
@ -77,10 +68,6 @@ function saveTokensFile($cached_tokens) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 魔方平台登录获取JWT Token
|
* 魔方平台登录获取JWT Token
|
||||||
* @param string $siteUrl API基础URL
|
|
||||||
* @param string $account 账户
|
|
||||||
* @param string $apiKey API密钥
|
|
||||||
* @return string|null JWT Token或null
|
|
||||||
*/
|
*/
|
||||||
function mofangLogin($siteUrl, $account, $apiKey) {
|
function mofangLogin($siteUrl, $account, $apiKey) {
|
||||||
try {
|
try {
|
||||||
@ -104,7 +91,7 @@ function mofangLogin($siteUrl, $account, $apiKey) {
|
|||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
if ($httpCode !== 200) {
|
if ($httpCode !== 200) {
|
||||||
error_log("魔方登录请求失败,HTTP状态码: {$httpCode}");
|
Logger::error("魔方登录请求失败,HTTP状态码: {$httpCode}", 'mofangLogin');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,20 +100,18 @@ function mofangLogin($siteUrl, $account, $apiKey) {
|
|||||||
if (isset($result['status']) && $result['status'] === 200 && isset($result['jwt'])) {
|
if (isset($result['status']) && $result['status'] === 200 && isset($result['jwt'])) {
|
||||||
return $result['jwt'];
|
return $result['jwt'];
|
||||||
} else {
|
} else {
|
||||||
error_log("魔方登录失败: " . ($result['msg'] ?? '未知错误'));
|
Logger::error("魔方登录失败: " . ($result['msg'] ?? '未知错误'), 'mofangLogin');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("魔方登录异常: " . $e->getMessage());
|
Logger::error("魔方登录异常: " . $e->getMessage(), 'mofangLogin');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取有效的Token(先查缓存,没有则重新登录)
|
* 获取有效的Token(先查缓存,没有则重新登录)
|
||||||
* @param int $configId 配置ID
|
|
||||||
* @return string|null Token或null
|
|
||||||
*/
|
*/
|
||||||
function getValidToken($configId) {
|
function getValidToken($configId) {
|
||||||
// 先尝试从缓存获取
|
// 先尝试从缓存获取
|
||||||
@ -140,7 +125,7 @@ function getValidToken($configId) {
|
|||||||
$config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]);
|
$config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]);
|
||||||
|
|
||||||
if (!$config) {
|
if (!$config) {
|
||||||
error_log("配置ID {$configId} 不存在");
|
Logger::error("配置ID {$configId} 不存在", 'getValidToken');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,12 +140,6 @@ function getValidToken($configId) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送魔方API请求
|
* 发送魔方API请求
|
||||||
* @param string $siteUrl API基础URL
|
|
||||||
* @param string $endpoint API端点
|
|
||||||
* @param string $method HTTP方法 (GET/POST/PUT)
|
|
||||||
* @param array $data 请求数据
|
|
||||||
* @param int $configId 配置ID
|
|
||||||
* @return array|null 响应数据或null
|
|
||||||
*/
|
*/
|
||||||
function mofangApiRequest($siteUrl, $endpoint, $method = 'GET', $data = [], $configId = null) {
|
function mofangApiRequest($siteUrl, $endpoint, $method = 'GET', $data = [], $configId = null) {
|
||||||
// 获取Token
|
// 获取Token
|
||||||
@ -233,10 +212,6 @@ function mofangApiRequest($siteUrl, $endpoint, $method = 'GET', $data = [], $con
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取VPS列表
|
* 获取VPS列表
|
||||||
* @param int $configId 配置ID
|
|
||||||
* @param int $page 页码
|
|
||||||
* @param int $limit 每页数量
|
|
||||||
* @return array|null VPS列表数据或null
|
|
||||||
*/
|
*/
|
||||||
function mofangGetVpsList($configId, $page = 1, $limit = 100) {
|
function mofangGetVpsList($configId, $page = 1, $limit = 100) {
|
||||||
$db = getVpsDB();
|
$db = getVpsDB();
|
||||||
@ -252,10 +227,6 @@ function mofangGetVpsList($configId, $page = 1, $limit = 100) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取VPS状态
|
* 获取VPS状态
|
||||||
* @param int $configId 配置ID
|
|
||||||
* @param int $vpsId VPS ID
|
|
||||||
* @param bool $updateDb 是否更新数据库,默认true
|
|
||||||
* @return array|null 状态数据或null
|
|
||||||
*/
|
*/
|
||||||
function mofangGetVpsStatus($configId, $vpsId, $updateDb = true) {
|
function mofangGetVpsStatus($configId, $vpsId, $updateDb = true) {
|
||||||
$db = getVpsDB();
|
$db = getVpsDB();
|
||||||
@ -271,32 +242,57 @@ function mofangGetVpsStatus($configId, $vpsId, $updateDb = true) {
|
|||||||
// 如果获取成功且需要更新数据库
|
// 如果获取成功且需要更新数据库
|
||||||
if ($updateDb && $result && isset($result['status']) && $result['status'] === 200 && isset($result['data'])) {
|
if ($updateDb && $result && isset($result['status']) && $result['status'] === 200 && isset($result['data'])) {
|
||||||
$statusData = $result['data'];
|
$statusData = $result['data'];
|
||||||
$status = $statusData['status'] ?? 'unknown';
|
$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;
|
||||||
|
|
||||||
// 只更新status字段,不影响其他字段
|
|
||||||
$listDb = getVpsListDB();
|
$listDb = getVpsListDB();
|
||||||
|
|
||||||
// 先检查记录是否存在
|
// 先获取该VPS的IP地址,用于精确匹配
|
||||||
$existing = $listDb->queryOne(
|
$vpsInfo = $listDb->queryOne(
|
||||||
'SELECT id FROM vps_list WHERE config_id = ? AND vps_id = ?',
|
'SELECT id, ip_address FROM vps_list WHERE config_id = ? AND vps_id = ?',
|
||||||
[$configId, $vpsId]
|
[$configId, $vpsId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($existing) {
|
if ($vpsInfo) {
|
||||||
// 记录存在,执行UPDATE
|
// 记录存在,执行UPDATE
|
||||||
$listDb->execute(
|
$success = $listDb->execute(
|
||||||
'UPDATE vps_list SET status = ?, last_check = CURRENT_TIMESTAMP WHERE config_id = ? AND vps_id = ?',
|
'UPDATE vps_list SET status = ?, last_check = CURRENT_TIMESTAMP WHERE id = ?',
|
||||||
[$status, $configId, $vpsId]
|
[$status, $vpsInfo['id']]
|
||||||
);
|
);
|
||||||
error_log("[mofangGetVpsStatus] VPS {$vpsId} 状态已更新为: {$status}");
|
if ($success) {
|
||||||
|
Logger::info("[mofangGetVpsStatus] VPS {$vpsId} (IP: {$vpsInfo['ip_address']}) 状态已更新为: {$status}", 'mofangGetVpsStatus');
|
||||||
|
} else {
|
||||||
|
Logger::error("[mofangGetVpsStatus] VPS {$vpsId} 状态更新失败", 'mofangGetVpsStatus');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 记录不存在,插入新记录
|
// 记录不存在,插入新记录
|
||||||
$listDb->execute(
|
$success = $listDb->execute(
|
||||||
'INSERT INTO vps_list (config_id, vps_id, status, last_check) VALUES (?, ?, ?, CURRENT_TIMESTAMP)',
|
'INSERT INTO vps_list (config_id, vps_id, status, last_check) VALUES (?, ?, ?, CURRENT_TIMESTAMP)',
|
||||||
[$configId, $vpsId, $status]
|
[$configId, $vpsId, $status]
|
||||||
);
|
);
|
||||||
error_log("[mofangGetVpsStatus] VPS {$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;
|
return $result;
|
||||||
@ -304,10 +300,6 @@ function mofangGetVpsStatus($configId, $vpsId, $updateDb = true) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取VPS详细信息
|
* 获取VPS详细信息
|
||||||
* @param int $configId 配置ID
|
|
||||||
* @param int $vpsId VPS ID
|
|
||||||
* @param bool $updateDb 是否更新数据库,默认true
|
|
||||||
* @return array|null 详细信息或null
|
|
||||||
*/
|
*/
|
||||||
function mofangGetVpsDetails($configId, $vpsId, $updateDb = true) {
|
function mofangGetVpsDetails($configId, $vpsId, $updateDb = true) {
|
||||||
$db = getVpsDB();
|
$db = getVpsDB();
|
||||||
@ -328,6 +320,28 @@ function mofangGetVpsDetails($configId, $vpsId, $updateDb = true) {
|
|||||||
$updates = []; // 存储要更新的字段
|
$updates = []; // 存储要更新的字段
|
||||||
$values = [];
|
$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'])) {
|
if (isset($host['config_option']) && is_array($host['config_option'])) {
|
||||||
foreach ($host['config_option'] as $option) {
|
foreach ($host['config_option'] as $option) {
|
||||||
switch ($option['key']) {
|
switch ($option['key']) {
|
||||||
@ -392,7 +406,7 @@ function mofangGetVpsDetails($configId, $vpsId, $updateDb = true) {
|
|||||||
"UPDATE vps_list SET {$setClause} WHERE config_id = ? AND vps_id = ?",
|
"UPDATE vps_list SET {$setClause} WHERE config_id = ? AND vps_id = ?",
|
||||||
$values
|
$values
|
||||||
);
|
);
|
||||||
error_log("[mofangGetVpsDetails] VPS {$vpsId} 详细信息已更新");
|
Logger::info("[mofangGetVpsDetails] VPS {$vpsId} 详细信息已更新" . (isset($status) ? ",状态: {$status}" : ""), 'mofangGetVpsDetails');
|
||||||
} else {
|
} else {
|
||||||
// 记录不存在,插入新记录(只插入获取到的字段)
|
// 记录不存在,插入新记录(只插入获取到的字段)
|
||||||
$columns = [];
|
$columns = [];
|
||||||
@ -422,7 +436,7 @@ function mofangGetVpsDetails($configId, $vpsId, $updateDb = true) {
|
|||||||
"INSERT INTO vps_list ({$columnStr}) VALUES ({$placeholderStr})",
|
"INSERT INTO vps_list ({$columnStr}) VALUES ({$placeholderStr})",
|
||||||
$insertValues
|
$insertValues
|
||||||
);
|
);
|
||||||
error_log("[mofangGetVpsDetails] VPS {$vpsId} 新记录已插入");
|
Logger::info("[mofangGetVpsDetails] VPS {$vpsId} 新记录已插入", 'mofangGetVpsDetails');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -433,9 +447,6 @@ function mofangGetVpsDetails($configId, $vpsId, $updateDb = true) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* VPS开机
|
* VPS开机
|
||||||
* @param int $configId 配置ID
|
|
||||||
* @param int $vpsId VPS ID
|
|
||||||
* @return array|null 响应数据或null
|
|
||||||
*/
|
*/
|
||||||
function mofangPowerOn($configId, $vpsId) {
|
function mofangPowerOn($configId, $vpsId) {
|
||||||
$db = getVpsDB();
|
$db = getVpsDB();
|
||||||
@ -446,14 +457,19 @@ function mofangPowerOn($configId, $vpsId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$endpoint = "/v1/hosts/{$vpsId}/module/on";
|
$endpoint = "/v1/hosts/{$vpsId}/module/on";
|
||||||
return mofangApiRequest($config['site_url'], $endpoint, 'PUT', [], $configId);
|
$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关机
|
* VPS关机
|
||||||
* @param int $configId 配置ID
|
|
||||||
* @param int $vpsId VPS ID
|
|
||||||
* @return array|null 响应数据或null
|
|
||||||
*/
|
*/
|
||||||
function mofangPowerOff($configId, $vpsId) {
|
function mofangPowerOff($configId, $vpsId) {
|
||||||
$db = getVpsDB();
|
$db = getVpsDB();
|
||||||
@ -464,14 +480,19 @@ function mofangPowerOff($configId, $vpsId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$endpoint = "/v1/hosts/{$vpsId}/module/off";
|
$endpoint = "/v1/hosts/{$vpsId}/module/off";
|
||||||
return mofangApiRequest($config['site_url'], $endpoint, 'PUT', [], $configId);
|
$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硬重启
|
* VPS硬重启
|
||||||
* @param int $configId 配置ID
|
|
||||||
* @param int $vpsId VPS ID
|
|
||||||
* @return array|null 响应数据或null
|
|
||||||
*/
|
*/
|
||||||
function mofangHardReboot($configId, $vpsId) {
|
function mofangHardReboot($configId, $vpsId) {
|
||||||
$db = getVpsDB();
|
$db = getVpsDB();
|
||||||
@ -482,13 +503,19 @@ function mofangHardReboot($configId, $vpsId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$endpoint = "/v1/hosts/{$vpsId}/module/hard_reboot";
|
$endpoint = "/v1/hosts/{$vpsId}/module/hard_reboot";
|
||||||
return mofangApiRequest($config['site_url'], $endpoint, 'PUT', [], $configId);
|
$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列表并保存到数据库
|
* 刷新指定配置的VPS列表并保存到数据库
|
||||||
* @param int $configId 配置ID
|
|
||||||
* @return bool 是否成功
|
|
||||||
*/
|
*/
|
||||||
function refreshVpsListForConfig($configId) {
|
function refreshVpsListForConfig($configId) {
|
||||||
$db = getVpsDB();
|
$db = getVpsDB();
|
||||||
@ -502,7 +529,7 @@ function refreshVpsListForConfig($configId) {
|
|||||||
$result = mofangGetVpsList($configId);
|
$result = mofangGetVpsList($configId);
|
||||||
|
|
||||||
if (!$result || !isset($result['status']) || $result['status'] !== 200) {
|
if (!$result || !isset($result['status']) || $result['status'] !== 200) {
|
||||||
error_log("获取VPS列表失败: " . ($result['msg'] ?? '未知错误'));
|
Logger::error("获取VPS列表失败: " . ($result['msg'] ?? '未知错误'), 'refreshVpsListForConfig');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,40 +586,71 @@ function refreshVpsListForConfig($configId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用INSERT OR REPLACE防止重复数据
|
// 基于vps_id和ip_address去重
|
||||||
// 注意:需要保留原有的status字段,避免被覆盖为NULL
|
|
||||||
$existing = $listDb->queryOne(
|
$existing = $listDb->queryOne(
|
||||||
'SELECT status FROM vps_list WHERE config_id = ? AND vps_id = ?',
|
'SELECT id, status FROM vps_list WHERE vps_id = ? AND ip_address = ?',
|
||||||
[$configId, $host['id']]
|
[$host['id'], $host['dedicatedip'] ?? null]
|
||||||
);
|
);
|
||||||
|
|
||||||
$currentStatus = $existing ? $existing['status'] : null;
|
if ($existing) {
|
||||||
|
// 记录已存在,只更新非status字段,保留原有status
|
||||||
$listDb->execute(
|
$listDb->execute(
|
||||||
'INSERT OR REPLACE INTO vps_list (config_id, vps_id, domain, ip_address, product_name, cpu_cores, memory_size, disk_size, bandwidth, os_type, status, section, last_check) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)',
|
'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 = ?',
|
||||||
[
|
[
|
||||||
$configId,
|
$host['domain'] ?? null,
|
||||||
$host['id'],
|
$host['product_name'] ?? null,
|
||||||
$host['domain'] ?? null,
|
$cpuCores,
|
||||||
$host['dedicatedip'] ?? null,
|
$memorySize,
|
||||||
$host['product_name'] ?? null,
|
$diskSize,
|
||||||
$cpuCores,
|
$bandwidth,
|
||||||
$memorySize,
|
$osType,
|
||||||
$diskSize,
|
$host['amount'] ?? null,
|
||||||
$bandwidth,
|
$host['nextduedate'] ?? null,
|
||||||
$osType,
|
$config['auto_monitor'] ? 1 : 0,
|
||||||
$currentStatus, // 保留原有状态
|
$existing['id']
|
||||||
$config['auto_monitor'] ? 1 : 0
|
]
|
||||||
]
|
);
|
||||||
);
|
} 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新所有配置的VPS列表
|
* 刷新所有配置的VPS列表
|
||||||
* @return int 成功刷新的配置数量
|
|
||||||
*/
|
*/
|
||||||
function refreshAllVpsLists() {
|
function refreshAllVpsLists() {
|
||||||
$db = getVpsDB();
|
$db = getVpsDB();
|
||||||
@ -611,16 +669,13 @@ function refreshAllVpsLists() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取单个VPS的状态并更新到数据库
|
* 获取单个VPS的状态并更新到数据库
|
||||||
* @param int $configId 配置ID
|
|
||||||
* @param int $vpsId VPS ID
|
|
||||||
* @return array|null 状态信息或null
|
|
||||||
*/
|
*/
|
||||||
function updateVpsStatusToDb($configId, $vpsId) {
|
function updateVpsStatusToDb($configId, $vpsId) {
|
||||||
$db = getVpsDB();
|
$db = getVpsDB();
|
||||||
$config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]);
|
$config = $db->queryOne('SELECT * FROM configs WHERE id = ?', [$configId]);
|
||||||
|
|
||||||
if (!$config) {
|
if (!$config) {
|
||||||
error_log("配置ID {$configId} 不存在");
|
Logger::error("配置ID {$configId} 不存在", 'updateVpsStatusToDb');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -628,35 +683,60 @@ function updateVpsStatusToDb($configId, $vpsId) {
|
|||||||
$result = mofangGetVpsStatus($configId, $vpsId, false);
|
$result = mofangGetVpsStatus($configId, $vpsId, false);
|
||||||
|
|
||||||
if (!$result || !isset($result['status']) || $result['status'] !== 200) {
|
if (!$result || !isset($result['status']) || $result['status'] !== 200) {
|
||||||
error_log("获取VPS {$vpsId} 状态失败: " . ($result['msg'] ?? '未知错误'));
|
Logger::error("获取VPS {$vpsId} 状态失败,保留原有状态: " . ($result['msg'] ?? '未知错误'), 'updateVpsStatusToDb');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$statusData = $result['data'];
|
$statusData = $result['data'];
|
||||||
$status = $statusData['status'] ?? 'unknown';
|
$rawStatus = $statusData['status'] ?? 'unknown';
|
||||||
$des = $statusData['des'] ?? '未知';
|
$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();
|
$listDb = getVpsListDB();
|
||||||
$existing = $listDb->queryOne(
|
$vpsInfo = $listDb->queryOne(
|
||||||
'SELECT id FROM vps_list WHERE config_id = ? AND vps_id = ?',
|
'SELECT id, ip_address FROM vps_list WHERE config_id = ? AND vps_id = ?',
|
||||||
[$configId, $vpsId]
|
[$configId, $vpsId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($existing) {
|
if ($vpsInfo) {
|
||||||
// 记录存在,执行UPDATE
|
// 记录存在,执行UPDATE
|
||||||
$listDb->execute(
|
$success = $listDb->execute(
|
||||||
'UPDATE vps_list SET status = ?, last_check = CURRENT_TIMESTAMP WHERE config_id = ? AND vps_id = ?',
|
'UPDATE vps_list SET status = ?, last_check = CURRENT_TIMESTAMP WHERE id = ?',
|
||||||
[$status, $configId, $vpsId]
|
[$status, $vpsInfo['id']]
|
||||||
);
|
);
|
||||||
error_log("[updateVpsStatusToDb] VPS {$vpsId} 状态已更新为: {$status}");
|
if ($success) {
|
||||||
|
Logger::info("[updateVpsStatusToDb] VPS {$vpsId} (IP: {$vpsInfo['ip_address']}) 状态已更新为: {$status}", 'updateVpsStatusToDb');
|
||||||
|
} else {
|
||||||
|
Logger::error("[updateVpsStatusToDb] VPS {$vpsId} 状态更新失败,保留原有状态", 'updateVpsStatusToDb');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 记录不存在,插入新记录
|
// 记录不存在,插入新记录
|
||||||
$listDb->execute(
|
$success = $listDb->execute(
|
||||||
'INSERT INTO vps_list (config_id, vps_id, status, last_check) VALUES (?, ?, ?, CURRENT_TIMESTAMP)',
|
'INSERT INTO vps_list (config_id, vps_id, status, last_check) VALUES (?, ?, ?, CURRENT_TIMESTAMP)',
|
||||||
[$configId, $vpsId, $status]
|
[$configId, $vpsId, $status]
|
||||||
);
|
);
|
||||||
error_log("[updateVpsStatusToDb] VPS {$vpsId} 新记录已插入,状态: {$status}");
|
if ($success) {
|
||||||
|
Logger::info("[updateVpsStatusToDb] VPS {$vpsId} 新记录已插入,状态: {$status}", 'updateVpsStatusToDb');
|
||||||
|
} else {
|
||||||
|
Logger::error("[updateVpsStatusToDb] VPS {$vpsId} 新记录插入失败", 'updateVpsStatusToDb');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -669,9 +749,6 @@ function updateVpsStatusToDb($configId, $vpsId) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量获取VPS状态并更新到数据库
|
* 批量获取VPS状态并更新到数据库
|
||||||
* @param int $configId 配置ID
|
|
||||||
* @param array $vpsIds VPS ID数组,为空则更新该配置下所有VPS
|
|
||||||
* @return array 更新结果统计
|
|
||||||
*/
|
*/
|
||||||
function batchUpdateVpsStatus($configId, $vpsIds = []) {
|
function batchUpdateVpsStatus($configId, $vpsIds = []) {
|
||||||
$db = getVpsDB();
|
$db = getVpsDB();
|
||||||
@ -696,21 +773,33 @@ function batchUpdateVpsStatus($configId, $vpsIds = []) {
|
|||||||
$failedCount = 0;
|
$failedCount = 0;
|
||||||
$results = [];
|
$results = [];
|
||||||
|
|
||||||
foreach ($vpsIds as $vpsId) {
|
$listDb = getVpsListDB();
|
||||||
$result = updateVpsStatusToDb($configId, $vpsId);
|
$listDb->getConnection()->beginTransaction();
|
||||||
|
|
||||||
if ($result) {
|
try {
|
||||||
$successCount++;
|
foreach ($vpsIds as $vpsId) {
|
||||||
$results[] = $result;
|
$result = updateVpsStatusToDb($configId, $vpsId);
|
||||||
} else {
|
|
||||||
$failedCount++;
|
if ($result) {
|
||||||
$results[] = [
|
$successCount++;
|
||||||
'vps_id' => $vpsId,
|
$results[] = $result;
|
||||||
'status' => 'error',
|
} else {
|
||||||
'des' => '获取状态失败',
|
$failedCount++;
|
||||||
'updated' => false
|
$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 [
|
return [
|
||||||
|
|||||||
351
view_log.php
Normal file
351
view_log.php
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/app/logger.php';
|
||||||
|
require_once __DIR__ . '/app/pass.php';
|
||||||
|
|
||||||
|
// 检查是否已登录(简单验证)
|
||||||
|
session_start();
|
||||||
|
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
|
||||||
|
// 如果没有登录,显示简单的密码验证
|
||||||
|
if (isset($_POST['password'])) {
|
||||||
|
// 使用pass.php中定义的API_PASS作为密码
|
||||||
|
if ($_POST['password'] === API_PASS) {
|
||||||
|
$_SESSION['admin_logged_in'] = true;
|
||||||
|
} else {
|
||||||
|
$error = '密码错误';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_SESSION['admin_logged_in'])) {
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>VPSHUB - 日志查看</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; margin: 50px; background: #f5f5f5; }
|
||||||
|
.login-box { max-width: 400px; margin: 100px auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||||
|
h2 { margin-top: 0; color: #333; }
|
||||||
|
input[type="password"] { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
|
||||||
|
button { width: 100%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
|
||||||
|
button:hover { background: #0056b3; }
|
||||||
|
.error { color: red; margin-bottom: 10px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-box">
|
||||||
|
<h2>🔐 日志查看权限验证</h2>
|
||||||
|
<?php if (isset($error)): ?>
|
||||||
|
<div class="error"><?php echo htmlspecialchars($error); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<form method="POST">
|
||||||
|
<input type="password" name="password" placeholder="请输入管理员密码" required>
|
||||||
|
<button type="submit">登录</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理操作
|
||||||
|
$action = $_GET['action'] ?? '';
|
||||||
|
if ($action === 'clear' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
Logger::clear();
|
||||||
|
header('Location: view_log.php?cleared=1');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取日志文件
|
||||||
|
$logFile = Logger::getLogFile();
|
||||||
|
$logContent = '';
|
||||||
|
$logLines = [];
|
||||||
|
|
||||||
|
if (file_exists($logFile)) {
|
||||||
|
$logContent = file_get_contents($logFile);
|
||||||
|
$logLines = array_filter(explode("\n", trim($logContent)));
|
||||||
|
// 反转数组,最新的在前面
|
||||||
|
$logLines = array_reverse($logLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤级别
|
||||||
|
$filterLevel = $_GET['level'] ?? 'ALL';
|
||||||
|
$filteredLines = $logLines;
|
||||||
|
|
||||||
|
if ($filterLevel !== 'ALL') {
|
||||||
|
$filteredLines = array_filter($logLines, function($line) use ($filterLevel) {
|
||||||
|
return strpos($line, "[{$filterLevel}]") !== false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索关键词
|
||||||
|
$search = $_GET['search'] ?? '';
|
||||||
|
if ($search) {
|
||||||
|
$filteredLines = array_filter($filteredLines, function($line) use ($search) {
|
||||||
|
return stripos($line, $search) !== false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限制显示行数
|
||||||
|
$limit = intval($_GET['limit'] ?? 100);
|
||||||
|
$filteredLines = array_slice($filteredLines, 0, $limit);
|
||||||
|
|
||||||
|
$cleared = isset($_GET['cleared']);
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>VPSHUB - 日志查看器</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f0f2f5;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 20px 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
padding: 20px 30px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.controls select,
|
||||||
|
.controls input,
|
||||||
|
.controls button {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.controls input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
.controls button {
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.controls button:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
}
|
||||||
|
.controls button.danger {
|
||||||
|
background: #dc3545;
|
||||||
|
}
|
||||||
|
.controls button.danger:hover {
|
||||||
|
background: #c82333;
|
||||||
|
}
|
||||||
|
.stats {
|
||||||
|
padding: 15px 30px;
|
||||||
|
background: #fff3cd;
|
||||||
|
border-bottom: 1px solid #ffc107;
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.log-container {
|
||||||
|
padding: 20px 30px;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.log-line {
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.log-line.INFO {
|
||||||
|
background: #d1ecf1;
|
||||||
|
border-left: 3px solid #17a2b8;
|
||||||
|
}
|
||||||
|
.log-line.WARNING {
|
||||||
|
background: #fff3cd;
|
||||||
|
border-left: 3px solid #ffc107;
|
||||||
|
}
|
||||||
|
.log-line.ERROR {
|
||||||
|
background: #f8d7da;
|
||||||
|
border-left: 3px solid #dc3545;
|
||||||
|
}
|
||||||
|
.log-line.DEBUG {
|
||||||
|
background: #e2e3e5;
|
||||||
|
border-left: 3px solid #6c757d;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #6c757d;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.level {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
.level.INFO { background: #17a2b8; color: white; }
|
||||||
|
.level.WARNING { background: #ffc107; color: black; }
|
||||||
|
.level.ERROR { background: #dc3545; color: white; }
|
||||||
|
.level.DEBUG { background: #6c757d; color: white; }
|
||||||
|
.source {
|
||||||
|
color: #495057;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
.empty-state svg {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
.alert {
|
||||||
|
padding: 12px 20px;
|
||||||
|
margin: 20px 30px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #d4edda;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>📋 VPSHUB 日志查看器</h1>
|
||||||
|
<div>
|
||||||
|
<span style="font-size: 14px; opacity: 0.9;">
|
||||||
|
<?php echo date('Y-m-d H:i:s'); ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($cleared): ?>
|
||||||
|
<div class="alert">✅ 日志已清空</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<select name="level" onchange="window.location.href='view_log.php?level=' + this.value + '&search=<?php echo urlencode($search); ?>&limit=<?php echo $limit; ?>'">
|
||||||
|
<option value="ALL" <?php echo $filterLevel === 'ALL' ? 'selected' : ''; ?>>全部级别</option>
|
||||||
|
<option value="INFO" <?php echo $filterLevel === 'INFO' ? 'selected' : ''; ?>>INFO</option>
|
||||||
|
<option value="WARNING" <?php echo $filterLevel === 'WARNING' ? 'selected' : ''; ?>>WARNING</option>
|
||||||
|
<option value="ERROR" <?php echo $filterLevel === 'ERROR' ? 'selected' : ''; ?>>ERROR</option>
|
||||||
|
<option value="DEBUG" <?php echo $filterLevel === 'DEBUG' ? 'selected' : ''; ?>>DEBUG</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<form method="GET" style="flex: 1; display: flex; gap: 10px;">
|
||||||
|
<input type="hidden" name="level" value="<?php echo htmlspecialchars($filterLevel); ?>">
|
||||||
|
<input type="text" name="search" placeholder="搜索日志..." value="<?php echo htmlspecialchars($search); ?>">
|
||||||
|
<button type="submit">🔍 搜索</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<select onchange="window.location.href='view_log.php?level=<?php echo urlencode($filterLevel); ?>&search=<?php echo urlencode($search); ?>&limit=' + this.value">
|
||||||
|
<option value="50" <?php echo $limit === 50 ? 'selected' : ''; ?>>50行</option>
|
||||||
|
<option value="100" <?php echo $limit === 100 ? 'selected' : ''; ?>>100行</option>
|
||||||
|
<option value="200" <?php echo $limit === 200 ? 'selected' : ''; ?>>200行</option>
|
||||||
|
<option value="500" <?php echo $limit === 500 ? 'selected' : ''; ?>>500行</option>
|
||||||
|
<option value="1000" <?php echo $limit === 1000 ? 'selected' : ''; ?>>1000行</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<form method="POST" action="view_log.php?action=clear" onsubmit="return confirm('确定要清空所有日志吗?此操作不可恢复!');">
|
||||||
|
<button type="submit" class="danger">🗑️ 清空日志</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<button onclick="location.reload()">🔄 刷新</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<strong>总行数:</strong> <?php echo count($logLines); ?>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<strong>显示:</strong> <?php echo count($filteredLines); ?>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<strong>文件大小:</strong> <?php echo number_format(filesize($logFile) / 1024, 2); ?> KB
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="log-container">
|
||||||
|
<?php if (empty($filteredLines)): ?>
|
||||||
|
<div class="empty-state">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/>
|
||||||
|
</svg>
|
||||||
|
<h3>暂无日志记录</h3>
|
||||||
|
<p>当系统运行时,日志将显示在这里</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($filteredLines as $line): ?>
|
||||||
|
<?php
|
||||||
|
// 解析日志行
|
||||||
|
preg_match('/\[(.*?)\] \[(.*?)\] \[(.*?)\] (.*)/', $line, $matches);
|
||||||
|
if ($matches) {
|
||||||
|
$timestamp = $matches[1];
|
||||||
|
$level = $matches[2];
|
||||||
|
$source = $matches[3];
|
||||||
|
$message = $matches[4];
|
||||||
|
} else {
|
||||||
|
$timestamp = '';
|
||||||
|
$level = 'INFO';
|
||||||
|
$source = '';
|
||||||
|
$message = $line;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="log-line <?php echo htmlspecialchars($level); ?>">
|
||||||
|
<?php if ($timestamp): ?>
|
||||||
|
<span class="timestamp">[<?php echo htmlspecialchars($timestamp); ?>]</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<span class="level <?php echo htmlspecialchars($level); ?>"><?php echo htmlspecialchars($level); ?></span>
|
||||||
|
<?php if ($source): ?>
|
||||||
|
<span class="source">[<?php echo htmlspecialchars($source); ?>]</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<span class="message"><?php echo htmlspecialchars($message); ?></span>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue
Block a user