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.0typecho1.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/

特别说明

  1. 该功能基于handsome主题开发,其他主题需自行修改适配
  2. 代码支持Linux系统,在CentOS Stream 9上测试通过
  3. 支持各种网络环境下的IP地址解析(电信、联通、移动)
  4. 能够准确识别访客设备、浏览器信息

完整代码

完整代码已上传至我的GitHub,欢迎访问:
GitHub仓库

如有问题,欢迎在评论区留言讨论!

如果觉得我的文章对你有用,请随意赞赏
END
本文作者:
文章标题:Typecho handsome主题 添加服务器状态栏教程最新版,附源代码
本文地址:https://blog.ybyq.wang/archives/41/
版权说明:若无注明,本文皆Xuan's blog原创,转载请保留文章出处。