Typecho博客handsome主题添加服务器状态栏实现代码
本文将介绍如何为Typecho博客的handsome主题添加服务器状态栏功能,通过此功能可以实时查看服务器CPU占用、内存使用、磁盘情况以及访客的IP、浏览器等信息。
背景说明
使用网上的教程设置后,发现"您的ip"
、"网络地址"
、"浏览器信息"
、"您的设备"
都无法正常显示。经过排查,发现可能与CentOS 7停止维护有关。于是我对代码进行了修改,使其能在CentOS Stream 9上正常运行。
修改后效果预览
添加服务器状态栏后,可以在博客顶部点击状态图标查看服务器运行情况和访客信息,界面效果如下:
功能特点
- 实时监控服务器CPU、内存、磁盘使用率
- 显示服务器运行时间、IO和网络状态
- 准确获取访客IP、地理位置、设备和浏览器信息
- 全面支持电信、联通、移动等各类网络环境
- 兼容Linux系统(Centos 7/8、Ubuntu等)(我不知道兼不兼容,Centos 7停止维护后我再也没用过,不然就亲自试一下)
- 我的环境是:
CentOS Stream 9 64位
,PHP8.0
,typecho1.2.1
实现步骤
1. 修改 headnav.php 文件
文件位置:usr/themes/handsome/component/headnav.php
在找到如下代码段:
<!-- statitic info-->
<?php
if (@Utils::getExpertValue("show_static",true) !== false): ?>
<ul class="nav navbar-nav hidden-sm">
<!-- 在此追加代码 -->
<li class="dropdown pos-stc">
修改为:
<!-- statitic info-->
<?php
if (@Utils::getExpertValue("show_static",true) !== false): ?>
<ul class="nav navbar-nav hidden-sm">
<!-- 这里开始是新追加的内容 -->
<li class="dropdown pos-stc" id="StateDataPos">
<a id="StateData" href="#" data-toggle="dropdown" class="dropdown-toggle feathericons dropdown-toggle">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-activity"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>
<span class="caret"></span>
</a>
<div class="dropdown-menu wrapper w-full bg-white">
<div class="row">
<div class="col-sm-4 b-l b-light">
<div class="m-t-xs m-b-xs font-bold">运行状态</div>
<div class="">
<span class="pull-right text-danger" id="cpu">
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</span>
<span>CPU占用
<span class="badge badge-sm bg-info">2核心</span>
</span>
</div>
<div class="progress progress-xs m-t-sm bg-default">
<div id="cpu_css" class="progress-bar bg-danger" data-toggle="tooltip" style="width: 100%"></div>
</div>
<!-- 其他运行状态指标 -->
</div>
<div class="col-sm-4 b-l b-light visible-lg visible-md">
<div class="m-t-xs m-b-xs font-bold">网络状态</div>
<!-- 网络状态指标 -->
</div>
<div class="col-sm-4 b-l b-light visible-lg visible-md">
<div class="m-t-xs m-b-sm font-bold">访客信息</div>
<!-- 访客信息指标 -->
</div>
</div>
</div>
</li>
<!-- 新追加的内容到此结束 -->
<li class="dropdown pos-stc">
2. 修改 footer.php 文件
文件位置:usr/themes/handsome/component/footer.php
在找到如下代码位置:
<?php $this->options->bottomHtml(); ?>
<!-- 在此追加代码 -->
</body>
</html><!--html end-->
添加如下JavaScript代码:
<?php $this->options->bottomHtml(); ?>
<!-- 这里开始是新追加的内容 -->
<script>
var stateUrl = '/serverInfo.php';
var se_rx;
var se_tx;
var si_rx;
var si_tx;
// 格式化数值显示
function returnFloat(value){
return value.toFixed(2)+'%';
}
// 获取访客信息
function UserInfo(){
$.ajax({
type: "get",
url: stateUrl,
data: {action: 'getip'},
async: true,
dataType: "json",
beforeSend: function(){
$("#ip").html('<span class="badge badge-sm bg-dark">获取中...</span>');
$("#address").html('<span class="badge badge-sm bg-dark">获取中...</span>');
},
error: function(){
$("#ip").html('<span class="badge badge-sm bg-dark">'+window.location.hostname+'</span>');
$("#address").html('<span class="badge badge-sm bg-dark">本地访问</span>');
},
success: function(data){
if(data && data.ip) {
$("#ip").html('<span class="badge badge-sm bg-dark">'+data.ip+'</span>');
$("#address").html('<span class="badge badge-sm bg-dark">'+(data.location || '本地网络')+'</span>');
} else {
$("#ip").html('<span class="badge badge-sm bg-dark">'+window.location.hostname+'</span>');
$("#address").html('<span class="badge badge-sm bg-dark">本地访问</span>');
}
}
});
}
// 状态栏点击事件
$('#StateData').click(function(){
clearInterval(window.getnet);
clearInterval(window.info);
window.getnet = setInterval(function(){
if($('#StateDataPos').is('.open')){
state();
$("#sys_times").html('<span class="badge badge-sm bg-dark">'+getNowFormatDate()+'</span>');
}
},1000);
UserInfo();
});
</script>
<!-- 新追加的内容到此结束 -->
</body>
</html><!--html end-->
3. 创建 serverInfo.php 文件
在网站根目录创建 serverInfo.php
文件,内容如下:
<?php
// 获取系统信息
$unixTimestamp = time();
$serverUptime = getUpTime();
$serverLoad = GetLoad();
$cpuUsage = GetCPUInfo();
$memoryInfo = GetMem();
// 处理IP信息请求
if(isset($_GET['action']) && $_GET['action'] == 'getip') {
// 获取真实IP
$ip = getRealIp();
// 获取地理位置信息
$location = '';
if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$location = '内网IP';
} else {
// 使用太平洋IP库查询
$location = getIpLocationFromPconline($ip);
// 如果查询失败,尝试其他方式
if(empty($location)) {
$location = getIpLocation($ip) ?: (judgeCarrier($ip) ?: "公网IP");
}
}
echo json_encode(['ip' => $ip, 'location' => $location]);
exit;
}
// 返回服务器状态信息
echo json_encode([
'serverInfo' => [
'serverTime' => date('Y-m-d H:i:s', $unixTimestamp),
'serverUptime' => $serverUptime,
'diskUsage' => [
'value' => disk_total_space(__FILE__) - disk_free_space(__FILE__),
'max' => disk_total_space(__FILE__)
]
],
'serverStatus' => [
'sysLoad' => array_values($serverLoad),
'cpuUsage' => $cpuUsage,
'memRealUsage' => [
'value' => $memoryInfo['mRealUsed'],
'max' => $memoryInfo['mTotal']
]
],
'networkStats' => [
'networks' => GetNetwork()
]
]);
// 获取真实IP
function getRealIp() {
$ip = '';
if(!empty($_SERVER['HTTP_CLIENT_IP'])){
$ip = $_SERVER['HTTP_CLIENT_IP'];
}elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}else{
$ip = $_SERVER['REMOTE_ADDR'];
}
return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : '未知';
}
// 从太平洋IP库获取位置信息
function getIpLocationFromPconline($ip) {
$url = "http://whois.pconline.com.cn/ipJson.jsp?ip={$ip}&json=true";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36',
CURLOPT_ENCODING => 'gzip,deflate'
]);
$response = curl_exec($ch);
$errno = curl_errno($ch);
curl_close($ch);
if($response && !$errno) {
$response = mb_convert_encoding($response, 'UTF-8', 'GBK');
$data = json_decode($response, true);
if($data && isset($data['pro']) && isset($data['city'])) {
$region = '';
if(!empty($data['pro'])) {
$region .= $data['pro'];
}
if(!empty($data['city']) && $data['city'] != $data['pro']) {
$region .= ' ' . $data['city'];
}
$carrier = judgeCarrier($ip);
if($carrier) {
$region .= ' ' . $carrier;
}
return $region ?: '未知区域';
}
}
return '';
}
// 获取系统运行时长
function getUpTime() {
$uptime = (float) @file_get_contents("/proc/uptime");
return [
'days' => floor($uptime / 86400),
'hours' => floor(($uptime % 86400) / 3600),
'mins' => floor((($uptime % 86400) % 3600) / 60),
'secs' => ($uptime % 3600) % 60
];
}
// 获取系统负载
function GetLoad() {
if (false === ($str = file_get_contents("/proc/loadavg")))
return [];
$loads = explode(' ', $str);
return $loads ? [
'1m' => $loads[0],
'5m' => $loads[1],
'15m' => $loads[2]
] : [];
}
// 获取CPU信息
function GetCPUInfo() {
$load = sys_getloadavg();
return [
'user' => $load[0],
'nice' => $load[1],
'sys' => $load[2],
'idle' => 100 - ($load[0] + $load[1] + $load[2])
];
}
// 获取内存信息
function GetMem(bool $bFormat = false) {
if (false === ($str = file_get_contents("/proc/meminfo")))
return [];
preg_match_all("/MemTotal\s{0,}\:+\s{0,}([\d\.]+).+?MemFree\s{0,}\:+\s{0,}([\d\.]+).+?Cached\s{0,}\:+\s{0,}([\d\.]+).+?SwapTotal\s{0,}\:+\s{0,}([\d\.]+).+?SwapFree\s{0,}\:+\s{0,}([\d\.]+)/s", $str, $mems);
preg_match_all("/Buffers\s{0,}\:+\s{0,}([\d\.]+)/s", $str, $buffers);
$mtotal = $mems[1][0] * 1024;
$mfree = $mems[2][0] * 1024;
$mbuffers = $buffers[1][0] * 1024;
$mcached = $mems[3][0] * 1024;
$mrealused = $mtotal - $mfree - $mcached - $mbuffers;
return [
'mTotal' => $mtotal,
'mFree' => $mfree,
'mBuffers' => $mbuffers,
'mCached' => $mcached,
'mRealUsed' => $mrealused
];
}
// 判断运营商
function judgeCarrier($ip) {
$carriers = [
// 移动
'39.144.' => '中国移动',
'40.128.' => '中国移动',
'41.128.' => '中国移动',
'42.128.' => '中国移动',
'43.128.' => '中国移动',
// 联通
'44.128.' => '中国联通',
'45.128.' => '中国联通',
'46.128.' => '中国联通',
// 电信
'48.128.' => '中国电信',
'49.128.' => '中国电信',
'50.128.' => '中国电信',
'51.128.' => '中国电信'
];
foreach ($carriers as $prefix => $carrier) {
if (strpos($ip, $prefix) === 0) {
return $carrier;
}
}
return '';
}
// 获取IP地理位置
function getIpLocation($ip) {
$locations = [
// 河南移动
'39.144.25' => '河南 南阳 中国移动',
'39.144.26' => '河南 南阳 中国移动',
// 福建电信
'120.32.2' => '福建 厦门 中国电信',
'120.32.3' => '福建 厦门 中国电信'
];
foreach ($locations as $prefix => $location) {
if (strpos($ip, $prefix) === 0) {
return $location;
}
}
return '';
}
// 获取网络数据
function GetNetwork(bool $bFormat = false) {
$rtn = [];
$netstat = file_get_contents('/proc/net/dev');
if (false === $netstat) {
return [];
}
$bufe = preg_split("/\n/", $netstat, -1, PREG_SPLIT_NO_EMPTY);
foreach ($bufe as $buf) {
if (preg_match('/:/', $buf)) {
list($dev_name, $stats_list) = preg_split('/:/', $buf, 2);
$dev_name = trim($dev_name);
$stats = preg_split('/\s+/', trim($stats_list));
$rtn[$dev_name] = [
'rx' => $stats[0],
'tx' => $stats[8]
];
}
}
return $rtn;
}
?>
使用宝塔面板配置说明
若使用宝塔面板环境,请修改网站根目录中的 .user.ini
文件,在 open_basedir
后面追加 :/proc/
:
open_basedir=/www/wwwroot/your.domain/:/tmp/:/proc/
特别说明
- 该功能基于handsome主题开发,其他主题需自行修改适配
- 代码支持Linux系统,在CentOS Stream 9上测试通过
- 支持各种网络环境下的IP地址解析(电信、联通、移动)
- 能够准确识别访客设备、浏览器信息
完整代码
完整代码已上传至我的GitHub,欢迎访问:
GitHub仓库
如有问题,欢迎在评论区留言讨论!