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 | ?> |