AI摘要

本文提供了一个针对Debian系统的handsome主题服务器状态栏的实现教程。教程包括安装必要的系统工具、修改headnav.php和footer.php文件以及创建serverInfo.php文件的步骤。同时,还提供了宝塔面板配置说明、PHP安全设置调整和Apache配置(可选)。该功能可以实时监控服务器CPU、内存、磁盘使用率,显示服务器运行时间、IO和网络状态,准确获取访客IP、地理位置、设备和浏览器信息,并兼容多种网络环境。

背景说明

使用handsome主题添加服务器状态栏教程最新版后, 发现在Debian系统上,"您的ip""网络地址""浏览器信息""您的设备"以及除CPU以外的系统信息都无法正常显示。经过排查,发现Debian系统的信息获取方式与CentOS有所不同,需要使用备选方案。
出错

功能特点

  • 实时监控服务器CPU、内存、磁盘使用率
  • 显示服务器运行时间、IO和网络状态
  • 准确获取访客IP、地理位置、设备和浏览器信息
  • 全面支持电信、联通、移动等各类网络环境
  • 兼容Linux系统(CentOS Stream 9、Debian 10.2等)
  • 环境是Debian 10.2 64位PHP8.0typecho1.2.1

实现步骤

1. 安装必要的系统工具

首先,确保系统安装了必要的工具:

apt-get update
apt-get install -y procps net-tools sysstat

2. 修改 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">

添加代码:

展开查看添加代码


    <!-- 这里开始是新追加的内容 -->
    <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>
    <!-- 新追加的内容到此结束 -->

3. 修改 footer.php 文件

文件位置:usr/themes/handsome/component/footer.php

在找到如下代码位置:

<?php $this->options->bottomHtml(); ?>

   <!-- 在此追加代码 -->

</body>
</html><!--html end-->

添加如下JavaScript代码:

展开查看添加代码


<!-- 这里开始是新追加的内容 -->
<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>
<!-- 新追加的内容到此结束 -->

4. 创建 serverInfo.php 文件 (Debian适配版)

在网站根目录创建 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() {  
        // 尝试直接读取/proc/uptime
        $uptime = @file_get_contents("/proc/uptime");
        if ($uptime !== false) {
            $uptime = (float)explode(' ', $uptime)[0];
        } else {
            // 备选方案:使用uptime命令
            if (function_exists('shell_exec')) {
                $uptimeStr = shell_exec('uptime -s');
                if ($uptimeStr) {
                    $startTime = strtotime(trim($uptimeStr));
                    if ($startTime) {
                        $uptime = time() - $startTime;
                    } else {
                        $uptime = 0;
                    }
                } else {
                    $uptime = 0;
                }
            } else {
                $uptime = 0;
            }
        }

        return [
            'days' => floor($uptime / 86400),
            'hours' => floor(($uptime % 86400) / 3600),
            'mins' => floor((($uptime % 86400) % 3600) / 60),
            'secs' => ($uptime % 60)
        ];  
    }

    // 获取系统负载
    function GetLoad() {
        // 尝试读取/proc/loadavg
        if (($str = @file_get_contents("/proc/loadavg")) !== false) {
            $loads = explode(' ', $str);
            return $loads ? [
                '1m' => $loads[0],
                '5m' => $loads[1],
                '15m' => $loads[2]
            ] : [];
        } 

        // 使用系统函数作为备选方案
        if (function_exists('sys_getloadavg')) {
            $loads = sys_getloadavg();
            return [
                '1m' => $loads[0],
                '5m' => $loads[1],
                '15m' => $loads[2]
            ];
        }

        // 使用命令行作为最后备选方案
        if (function_exists('shell_exec')) {
            $uptime = shell_exec('uptime');
            if ($uptime) {
                if (preg_match('/load average[s]?: ([0-9.]+)[,\s]+([0-9.]+)[,\s]+([0-9.]+)/', $uptime, $matches)) {
                    return [
                        '1m' => $matches[1],
                        '5m' => $matches[2],
                        '15m' => $matches[3]
                    ];
                }
            }
        }

        return [
            '1m' => 0,
            '5m' => 0,
            '15m' => 0
        ];
    }

    // 获取CPU信息
    function GetCPUInfo() {  
        // 原方法在Debian上可能不准确
        if (function_exists('shell_exec')) {
            $cpu = shell_exec("top -bn1 | grep 'Cpu(s)' | awk '{print $2 + $4}'");
            if ($cpu !== null) {
                $usage = floatval(trim($cpu));
                return [
                    'user' => $usage,
                    'nice' => 0,
                    'sys' => 0,
                    'idle' => 100 - $usage
                ];
            }
        }

        // 使用sys_getloadavg作为备选方法
        $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 [];

        // 更改匹配模式,使其更兼容Debian系统
        preg_match_all("/MemTotal:\s+(\d+).+?MemFree:\s+(\d+).+?Cached:\s+(\d+).+?SwapTotal:\s+(\d+).+?SwapFree:\s+(\d+)/s", $str, $mems);
        preg_match_all("/Buffers:\s+(\d+)/s", $str, $buffers);

        if (empty($mems[1][0]) || empty($mems[2][0])) {
            // 备选方案:使用命令行工具获取内存信息
            if (function_exists('shell_exec')) {
                $meminfo = shell_exec('free -b');
                if ($meminfo) {
                    $lines = explode("\n", $meminfo);
                    if (isset($lines[1])) {
                        $memvals = preg_split('/\s+/', trim($lines[1]));
                        if (count($memvals) >= 7) {
                            $mtotal = $memvals[1];
                            $mfree = $memvals[3];
                            $mbuffers = $memvals[5];
                            $mcached = $memvals[6];
                            $mrealused = $mtotal - $mfree - $mbuffers - $mcached;

                            return [
                                'mTotal' => $mtotal,
                                'mFree' => $mfree,
                                'mBuffers' => $mbuffers,
                                'mCached' => $mcached,
                                'mRealUsed' => $mrealused
                            ];
                        }
                    }
                }
            }

            // 如果依然失败,返回默认值
            return [
                'mTotal' => 0,
                'mFree' => 0,
                'mBuffers' => 0,
                'mCached' => 0,
                'mRealUsed' => 0
            ];
        }

        $mtotal = $mems[1][0] * 1024;
        $mfree = $mems[2][0] * 1024;
        $mbuffers = isset($buffers[1][0]) ? $buffers[1][0] * 1024 : 0;
        $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 = [];

        // 尝试读取/proc/net/dev
        $netstat = @file_get_contents('/proc/net/dev');
        if ($netstat !== false) {
            $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' => isset($stats[0]) ? $stats[0] : 0,
                        'tx' => isset($stats[8]) ? $stats[8] : 0
                    ];
                }
            }
        } else {
            // 使用ifconfig命令作为备选方案
            if (function_exists('shell_exec')) {
                $ifconfig = shell_exec('ifconfig 2>/dev/null || ip -s link');
                if ($ifconfig) {
                    preg_match_all('/^([a-zA-Z0-9]+):.+?RX.+?bytes.+?(\d+).+?TX.+?bytes.+?(\d+)/sm', $ifconfig, $matches);
                    if (isset($matches[1]) && !empty($matches[1])) {
                        foreach ($matches[1] as $key => $dev_name) {
                            if (isset($matches[2][$key]) && isset($matches[3][$key])) {
                                $rtn[$dev_name] = [
                                    'rx' => $matches[2][$key],
                                    'tx' => $matches[3][$key]
                                ];
                            }
                        }
                    }
                }
            }
        }

        return $rtn;
    }
?>

使用宝塔面板配置说明

若使用宝塔面板环境,请修改网站根目录中的 .user.ini 文件,在 open_basedir 后面追加 :/proc/

open_basedir=/www/wwwroot/your.domain/:/tmp/:/proc/

PHP安全设置调整

在Debian系统上,需要确保PHP可以执行shell命令。修改php.ini文件:

  1. 找到 disable_functions 行,确保 shell_exec, exec, system 未被禁用
  2. 如果在该行找到这些函数,请删除它们(用逗号分隔)

Apache配置(可选)

如果使用Apache服务器,可能需要修改配置允许执行系统命令:

  1. 编辑网站的虚拟主机配置文件或 .htaccess 文件
  2. 添加以下内容:
<Directory /var/www/html>
    Options +ExecCGI
</Directory>

特别说明

  1. 该功能基于handsome主题开发,其他主题需自行修改适配
  2. 代码支持Linux系统,在Debian 10.2上测试
  3. 如果在Debian系统上只能显示CPU状态,请确保已完成上述安全设置调整
  4. 支持各种网络环境下的IP地址解析(电信、联通、移动)
  5. 能够准确识别访客设备、浏览器信息

常见问题排查

如果遇到问题,可以尝试以下排查步骤:

  1. 检查PHP是否有权限执行shell命令

    cd /path/to/your/website
    php -r "echo shell_exec('whoami');"

    如果返回用户名,说明PHP有权限执行shell命令

  2. 检查是否可以访问/proc目录

    php -r "var_dump(file_get_contents('/proc/uptime'));"

    如果返回内容而不是false或错误,说明可以访问/proc目录

  3. 检查必要的命令是否可用

    which top free ifconfig ip

    确保这些命令都可用

如有问题,欢迎在评论区留言,也可私信我

如果觉得我的文章对你有用,请随意赞赏
END
本文作者:
文章标题:handsome主题添加服务器状态栏实现代码(Debian适配版)
本文地址:https://blog.ybyq.wang/archives/325.html
版权说明:若无注明,本文皆Xuan's blog原创,转载请保留文章出处。