health_check.php
· 6.2 KiB · PHP
Raw
<?php
// 禁用错误报告,避免在健康检查中暴露敏感信息
error_reporting(0);
ini_set('display_errors', 0);
header('Content-Type: application/json');
$status = [
'overall' => 'healthy',
'checks' => []
];
// --- 数据库连接检查 (修改为适应Typecho现代化配置) ---
function checkDatabaseConnection() {
// 假设 Typecho 的配置文件是 config.inc.php
$configFilePath = __DIR__ . '/config.inc.php';
if (!file_exists($configFilePath)) {
return ['status' => 'critical', 'message' => 'Typecho configuration file (config.inc.php) not found.'];
}
// 尝试包含 config.inc.php 文件
// 这将初始化 Typecho 的数据库连接对象
try {
require_once $configFilePath;
// 检查 Typecho 的 Db 类是否已初始化并可用
if (!class_exists('\Typecho\Db') || !\Typecho\Db::get()) {
return ['status' => 'critical', 'message' => 'Typecho database object not initialized or failed to get instance.'];
}
// 获取数据库实例
$db = \Typecho\Db::get();
// 尝试执行一个简单的查询来验证连接和权限
// Typecho 的 Db 对象没有直接的 `query` 方法用于简单SQL,
// 可以通过 `fetchRow` 或 `fetchObject` 来执行 select 语句
// 或者直接通过 Typecho_Db_Adapter::test() 方法 (如果存在且公开的话)
// 这里我们尝试查询一个 Typecho 常用表,例如 'typecho_options'
// 注意: Typecho 的 Db::query 方法返回的是结果集对象,不是直接布尔值
// 我们需要确保查询没有抛出异常,并能获取到结果。
// 修正:直接从 $db 对象获取前缀
$optionsTableName = $db->getPrefix() . 'options';
// 尝试查询一条数据,确认连接活跃且表可访问
$db->fetchRow($db->select()->from($optionsTableName)->limit(1));
return ['status' => 'healthy', 'message' => 'Database connection successful and query executed.'];
} catch (\Typecho\Db\Exception $e) {
return ['status' => 'critical', 'message' => 'Database connection failed: ' . $e->getMessage()];
} catch (Exception $e) {
return ['status' => 'critical', 'message' => 'An unexpected error occurred during database check: ' . $e->getMessage()];
}
}
$status['checks']['database'] = checkDatabaseConnection();
if ($status['checks']['database']['status'] === 'critical') {
$status['overall'] = 'unhealthy';
}
// --- 文件系统权限检查 ---
function checkFilePermissions() {
$paths = [
'usr/uploads' => 'write', // 用户上传目录
'config.inc.php' => 'read', // 配置文件
'var/Widget/Contents/Post/Edit.php' => 'read' // 核心文件示例
];
$results = [];
foreach ($paths as $path => $permissionType) {
$fullPath = __DIR__ . DIRECTORY_SEPARATOR . $path;
if (!file_exists($fullPath)) {
$results[$path] = ['status' => 'warning', 'message' => "Path not found: {$path}"];
continue;
}
$canAccess = false;
if ($permissionType === 'read') {
$canAccess = is_readable($fullPath);
} elseif ($permissionType === 'write') {
// 对于目录,检查是否可写入
if (is_dir($fullPath)) {
$testFile = $fullPath . DIRECTORY_SEPARATOR . uniqid('health_check_') . '.tmp';
if (file_put_contents($testFile, 'test') !== false) {
unlink($testFile); // 删除测试文件
$canAccess = true;
}
} else {
$canAccess = is_writable($fullPath);
}
}
if ($canAccess) {
$results[$path] = ['status' => 'healthy', 'message' => "{$permissionType} access OK."];
} else {
$results[$path] = ['status' => 'critical', 'message' => "{$permissionType} access FAILED."];
}
}
return $results;
}
$status['checks']['file_permissions'] = checkFilePermissions();
foreach ($status['checks']['file_permissions'] as $pathStatus) {
if ($pathStatus['status'] === 'critical') {
$status['overall'] = 'unhealthy';
break;
}
}
// --- 核心文件可访问性检查 (HTTP请求) ---
// 注意:这个检查需要服务器环境支持CURL或file_get_contents对外部URL的访问
// 并且检查的是Web服务器是否能正常提供这些文件,而不是PHP脚本本身是否能访问
function checkCoreFileAccess() {
$baseUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://{$_SERVER['HTTP_HOST']}";
// 假设这些文件是可以通过HTTP访问的Typecho核心文件
// 这里需要根据你的Typecho安装路径和实际可访问的文件来调整
$coreFiles = [
'/index.php', // 首页
'/admin/index.php' // 后台登录页
];
$results = [];
foreach ($coreFiles as $file) {
$url = $baseUrl . $file;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5); // 5秒超时
curl_setopt($ch, CURLOPT_NOBODY, true); // 只获取头部信息,不下载内容
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 200 && $httpCode < 400) {
$results[$file] = ['status' => 'healthy', 'message' => "Accessible (HTTP {$httpCode})."];
} else {
$results[$file] = ['status' => 'critical', 'message' => "Not accessible (HTTP {$httpCode})."];
}
}
return $results;
}
// 仅在服务器支持CURL的情况下执行此检查
if (function_exists('curl_init')) {
$status['checks']['core_file_access'] = checkCoreFileAccess();
foreach ($status['checks']['core_file_access'] as $fileStatus) {
if ($fileStatus['status'] === 'critical') {
$status['overall'] = 'unhealthy';
break;
}
}
} else {
$status['checks']['core_file_access'] = ['status' => 'warning', 'message' => 'cURL extension is not enabled, skipping core file HTTP access check.'];
}
// 输出 JSON 结果
echo json_encode($status, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
?>
| 1 | <?php |
| 2 | |
| 3 | // 禁用错误报告,避免在健康检查中暴露敏感信息 |
| 4 | error_reporting(0); |
| 5 | ini_set('display_errors', 0); |
| 6 | |
| 7 | header('Content-Type: application/json'); |
| 8 | |
| 9 | $status = [ |
| 10 | 'overall' => 'healthy', |
| 11 | 'checks' => [] |
| 12 | ]; |
| 13 | |
| 14 | // --- 数据库连接检查 (修改为适应Typecho现代化配置) --- |
| 15 | function checkDatabaseConnection() { |
| 16 | // 假设 Typecho 的配置文件是 config.inc.php |
| 17 | $configFilePath = __DIR__ . '/config.inc.php'; |
| 18 | if (!file_exists($configFilePath)) { |
| 19 | return ['status' => 'critical', 'message' => 'Typecho configuration file (config.inc.php) not found.']; |
| 20 | } |
| 21 | |
| 22 | // 尝试包含 config.inc.php 文件 |
| 23 | // 这将初始化 Typecho 的数据库连接对象 |
| 24 | try { |
| 25 | require_once $configFilePath; |
| 26 | |
| 27 | // 检查 Typecho 的 Db 类是否已初始化并可用 |
| 28 | if (!class_exists('\Typecho\Db') || !\Typecho\Db::get()) { |
| 29 | return ['status' => 'critical', 'message' => 'Typecho database object not initialized or failed to get instance.']; |
| 30 | } |
| 31 | |
| 32 | // 获取数据库实例 |
| 33 | $db = \Typecho\Db::get(); |
| 34 | |
| 35 | // 尝试执行一个简单的查询来验证连接和权限 |
| 36 | // Typecho 的 Db 对象没有直接的 `query` 方法用于简单SQL, |
| 37 | // 可以通过 `fetchRow` 或 `fetchObject` 来执行 select 语句 |
| 38 | // 或者直接通过 Typecho_Db_Adapter::test() 方法 (如果存在且公开的话) |
| 39 | // 这里我们尝试查询一个 Typecho 常用表,例如 'typecho_options' |
| 40 | |
| 41 | // 注意: Typecho 的 Db::query 方法返回的是结果集对象,不是直接布尔值 |
| 42 | // 我们需要确保查询没有抛出异常,并能获取到结果。 |
| 43 | |
| 44 | // 修正:直接从 $db 对象获取前缀 |
| 45 | $optionsTableName = $db->getPrefix() . 'options'; |
| 46 | |
| 47 | // 尝试查询一条数据,确认连接活跃且表可访问 |
| 48 | $db->fetchRow($db->select()->from($optionsTableName)->limit(1)); |
| 49 | |
| 50 | return ['status' => 'healthy', 'message' => 'Database connection successful and query executed.']; |
| 51 | |
| 52 | } catch (\Typecho\Db\Exception $e) { |
| 53 | return ['status' => 'critical', 'message' => 'Database connection failed: ' . $e->getMessage()]; |
| 54 | } catch (Exception $e) { |
| 55 | return ['status' => 'critical', 'message' => 'An unexpected error occurred during database check: ' . $e->getMessage()]; |
| 56 | } |
| 57 | } |
| 58 | $status['checks']['database'] = checkDatabaseConnection(); |
| 59 | if ($status['checks']['database']['status'] === 'critical') { |
| 60 | $status['overall'] = 'unhealthy'; |
| 61 | } |
| 62 | |
| 63 | // --- 文件系统权限检查 --- |
| 64 | function checkFilePermissions() { |
| 65 | $paths = [ |
| 66 | 'usr/uploads' => 'write', // 用户上传目录 |
| 67 | 'config.inc.php' => 'read', // 配置文件 |
| 68 | 'var/Widget/Contents/Post/Edit.php' => 'read' // 核心文件示例 |
| 69 | ]; |
| 70 | |
| 71 | $results = []; |
| 72 | foreach ($paths as $path => $permissionType) { |
| 73 | $fullPath = __DIR__ . DIRECTORY_SEPARATOR . $path; |
| 74 | if (!file_exists($fullPath)) { |
| 75 | $results[$path] = ['status' => 'warning', 'message' => "Path not found: {$path}"]; |
| 76 | continue; |
| 77 | } |
| 78 | |
| 79 | $canAccess = false; |
| 80 | if ($permissionType === 'read') { |
| 81 | $canAccess = is_readable($fullPath); |
| 82 | } elseif ($permissionType === 'write') { |
| 83 | // 对于目录,检查是否可写入 |
| 84 | if (is_dir($fullPath)) { |
| 85 | $testFile = $fullPath . DIRECTORY_SEPARATOR . uniqid('health_check_') . '.tmp'; |
| 86 | if (file_put_contents($testFile, 'test') !== false) { |
| 87 | unlink($testFile); // 删除测试文件 |
| 88 | $canAccess = true; |
| 89 | } |
| 90 | } else { |
| 91 | $canAccess = is_writable($fullPath); |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | if ($canAccess) { |
| 96 | $results[$path] = ['status' => 'healthy', 'message' => "{$permissionType} access OK."]; |
| 97 | } else { |
| 98 | $results[$path] = ['status' => 'critical', 'message' => "{$permissionType} access FAILED."]; |
| 99 | } |
| 100 | } |
| 101 | return $results; |
| 102 | } |
| 103 | $status['checks']['file_permissions'] = checkFilePermissions(); |
| 104 | foreach ($status['checks']['file_permissions'] as $pathStatus) { |
| 105 | if ($pathStatus['status'] === 'critical') { |
| 106 | $status['overall'] = 'unhealthy'; |
| 107 | break; |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | // --- 核心文件可访问性检查 (HTTP请求) --- |
| 112 | // 注意:这个检查需要服务器环境支持CURL或file_get_contents对外部URL的访问 |
| 113 | // 并且检查的是Web服务器是否能正常提供这些文件,而不是PHP脚本本身是否能访问 |
| 114 | function checkCoreFileAccess() { |
| 115 | $baseUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://{$_SERVER['HTTP_HOST']}"; |
| 116 | // 假设这些文件是可以通过HTTP访问的Typecho核心文件 |
| 117 | // 这里需要根据你的Typecho安装路径和实际可访问的文件来调整 |
| 118 | $coreFiles = [ |
| 119 | '/index.php', // 首页 |
| 120 | '/admin/index.php' // 后台登录页 |
| 121 | ]; |
| 122 | |
| 123 | $results = []; |
| 124 | foreach ($coreFiles as $file) { |
| 125 | $url = $baseUrl . $file; |
| 126 | $ch = curl_init($url); |
| 127 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
| 128 | curl_setopt($ch, CURLOPT_TIMEOUT, 5); // 5秒超时 |
| 129 | curl_setopt($ch, CURLOPT_NOBODY, true); // 只获取头部信息,不下载内容 |
| 130 | curl_exec($ch); |
| 131 | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); |
| 132 | curl_close($ch); |
| 133 | |
| 134 | if ($httpCode >= 200 && $httpCode < 400) { |
| 135 | $results[$file] = ['status' => 'healthy', 'message' => "Accessible (HTTP {$httpCode})."]; |
| 136 | } else { |
| 137 | $results[$file] = ['status' => 'critical', 'message' => "Not accessible (HTTP {$httpCode})."]; |
| 138 | } |
| 139 | } |
| 140 | return $results; |
| 141 | } |
| 142 | // 仅在服务器支持CURL的情况下执行此检查 |
| 143 | if (function_exists('curl_init')) { |
| 144 | $status['checks']['core_file_access'] = checkCoreFileAccess(); |
| 145 | foreach ($status['checks']['core_file_access'] as $fileStatus) { |
| 146 | if ($fileStatus['status'] === 'critical') { |
| 147 | $status['overall'] = 'unhealthy'; |
| 148 | break; |
| 149 | } |
| 150 | } |
| 151 | } else { |
| 152 | $status['checks']['core_file_access'] = ['status' => 'warning', 'message' => 'cURL extension is not enabled, skipping core file HTTP access check.']; |
| 153 | } |
| 154 | |
| 155 | // 输出 JSON 结果 |
| 156 | echo json_encode($status, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); |
| 157 | |
| 158 | ?> |