Typecho Handsome 添加实时QPS图表 - 兼容主题pjax
文章目录
-
- 一、最终效果
- 二、实现原理
- 三、文件准备
-
-
- 1. 数据读取脚本
- 2. 数据更新脚本
- 3. ECharts 延迟加载器
- 4. 后台数据更新器 (JS)
-
- 四、侧边栏集成
- 五、整合与配置 (关键步骤)
- 六、故障排查
对于使用 Typecho Handsome 主题的博主来说,在侧边栏添加一个能实时反馈网站负载的 QPS (每秒查询率) 图表,是一项非常酷的功能。然而,Handsome 主题强大的 PJAX 无刷新加载特性,也给这类需要持续运行的动态脚本带来了挑战。
本教程将详细介绍如何创建一个与 PJAX 完全兼容的实时 QPS 图表,彻底解决在页面跳转后图表“罢工”、数据不再更新的问题。
一、最终效果
我们最终实现的效果是,在博客侧边栏有一个动态的QPS图表,无论您是首次加载页面,还是通过PJAX在不同页面间导航,这个图表都能持续、稳定地从服务器获取最新数据并实时渲染。
二、实现原理
为了完美兼容PJAX,我们将整个功能拆分为三个核心部分,各司其职:
- 后台数据更新器:这是“数据源”的核心。它由一个JavaScript脚本 (
qps-updater.js
) 和一个PHP脚本 (update-qps.php
) 组成。qps-updater.js
会在后台持续运行,每隔5秒就向update-qps.php
发起请求,后者则负责从您的WAF(如长亭雷池)获取最新的QPS数据,并将其写入一个简单的服务器缓存文件 (safeguard_qps.json
)。 - 前台图表渲染器:这是“看得见”的部分。它位于您的侧边栏 (
sidebar.php
),包含一个图表容器 () 和一段独立的 ECharts 渲染脚本。这段脚本只做一件事:同样每隔5秒,从另一个PHP脚本 (get-qps.php
) 读取上述的缓存文件,然后将数据绘制成图表。- PJAX 兼容粘合剂:这是解决问题的关键。我们将前台图表的初始化逻辑,封装在一个全局函数
initQPSChartForHandsome()
中。然后,我们只需在 Handsome 主题后台的PJAX回调设置中填入这个函数名。这样,每次PJAX导航完成,主题就会自动调用这个函数,它会先彻底清理掉旧的图表实例和定时器,再重新初始化一个新的,从而实现了完美的无缝衔接。这种前后端分离、逻辑解耦的架构,确保了即使在复杂的PJAX环境中,数据更新和图表渲染也能独立、稳定地运行。
三、文件准备
所有脚本代码请看原文:https://blog.ybyq.wang/archives/776.html
请将以下4个文件,按照指定的路径和内容,放置到您的主题目录中。
注意:本教程假设您已在
/usr/themes/handsome/static/js/
目录下放置了echarts.min.js
文件。这是本地的ECharts图表库,您可以从 ECharts官网 下载最新版本。若您没有此文件,我们的加载器会自动尝试从CDN获取,但建议使用本地版本以获得更好的加载速度和稳定性。1. 数据读取脚本
路径:
/usr/themes/handsome/component/get-qps.php
(该脚本用于让前台图表安全地读取缓存数据)2. 数据更新脚本
路径:
/usr/themes/handsome/component/update-qps.php
(该脚本用于从WAF获取真实数据并写入缓存)3. ECharts 延迟加载器
路径:
/usr/themes/handsome/static/js/echarts-loader.js
(这是我们修改过的、兼容PJAX的版本)4. 后台数据更新器 (JS)
路径:
/usr/themes/handsome/component/qps-updater.js
(这是我们修改过的、兼容PJAX的版本)四、侧边栏集成
现在,编辑您的侧边栏文件(例如
usr/themes/handsome/component/sidebar.php
或主题本身的sidebar.php
),在您希望显示图表的位置,加入以下代码块。<!-- 雷池WAF实时QPS图表 --><section id=\"safeguard_qps_chart_widget\" class=\"widget widget_categories wrapper-md padder-v-none clear\"> <h5 class=\"widget-title m-t-none\">实时QPS</h5> <div class=\"panel wrapper-sm padder-v-ssm\"> <div class=\"safeguard-qps-chart-container\" style=\"position:relative; height:180px; padding:15px; border-radius:8px; box-shadow:0 2px 5px rgba(0,0,0,0.05);\"> <div style=\"display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;\"> <span style=\"font-size:16px; font-weight:bold; color:#333;\"> <i data-feather=\"activity\" style=\"width:16px; height:16px; vertical-align:middle; margin-right:4px; color:#20c997;\"></i> <span id=\"current-qps\">0</span> </span> <span id=\"refresh-btn\" style=\"cursor:pointer; color:#20c997;\"> <i data-feather=\"refresh-cw\" style=\"width:14px; height:14px;\"></i> </span> </div> <div id=\"safeguard-qps-chart\" style=\"width:100%; height:120px;\"></div> </div> <script> (function() { // 使用闭包管理状态,避免全局污染 let qpsChart = null; let intervalId = null; let qpsData = []; let timeLabels = []; const maxDataPoints = 15; function cleanup() { if (intervalId) clearInterval(intervalId); if (qpsChart) { try { qpsChart.dispose(); } catch (e) {} } qpsChart = null; intervalId = null; qpsData = []; timeLabels = []; const chartElement = document.getElementById(\'safeguard-qps-chart\'); if (chartElement) chartElement.removeAttribute(\'data-qps-init\'); } // 将初始化函数暴露到全局,以便Handsome主题回调 window.initQPSChartForHandsome = function() { cleanup(); const chartElement = document.getElementById(\'safeguard-qps-chart\'); if (!chartElement) return; function fetchQPSData() { fetch(\'/usr/themes/handsome/component/get-qps.php?t=\' + Date.now()) .then(res => res.json()) .then(data => { if (!data) return; const qpsVal = parseFloat(data.qps || 0); const now = new Date(); const timeStr = now.getHours() + \':\' + (\'0\' + now.getMinutes()).slice(-2) + \':\' + (\'0\' + now.getSeconds()).slice(-2); const currentQpsEl = document.getElementById(\'current-qps\'); if (currentQpsEl) currentQpsEl.textContent = qpsVal.toFixed(2); qpsData.push(qpsVal); timeLabels.push(timeStr); if (qpsData.length > maxDataPoints) { qpsData.shift(); timeLabels.shift(); } drawChart(); }).catch(console.error); } function drawChart() { if (!window.loadECharts) return; window.loadECharts(function(echarts) { try { if (!qpsChart) { qpsChart = echarts.init(chartElement); window.addEventListener(\'resize\', () => qpsChart && qpsChart.resize()); } qpsChart.setOption({ tooltip: { trigger: \'axis\', formatter: p => `${timeLabels[p[0].dataIndex]}
QPS: ${p[0].value}` }, grid: { left: \'3%\', right: \'10%\', bottom: \'10%\', top: \'10%\', containLabel: true }, xAxis: { type: \'category\', data: timeLabels, show: false }, yAxis: { type: \'value\', splitLine: { lineStyle: { type: \'dashed\' } } }, series: [{ data: qpsData, type: \'line\', smooth: true, showSymbol: false, lineStyle: { color: \'#20c997\' }, areaStyle: { color: \'#20c997\', opacity: 0.1 } }] }); } catch (e) { console.error(\"ECharts渲染失败:\", e); } }); } // 动态加载后台更新器脚本 if (typeof window.qpsUpdaterLoaded === \'undefined\') { const updaterScript = document.createElement(\'script\'); updaterScript.src = \'/usr/themes/handsome/component/qps-updater.js?v=\' + Date.now(); document.body.appendChild(updaterScript); window.qpsUpdaterLoaded = true; } fetchQPSData(); intervalId = setInterval(fetchQPSData, 5000); const refreshBtn = document.getElementById(\'refresh-btn\'); if (refreshBtn) { const newBtn = refreshBtn.cloneNode(true); refreshBtn.parentNode.replaceChild(newBtn, refreshBtn); newBtn.addEventListener(\'click\', fetchQPSData); } }; // 首次加载时自动运行 document.addEventListener(\'DOMContentLoaded\', window.initQPSChartForHandsome); })(); </script> </div></section>五、整合与配置 (关键步骤)
-
确认文件位置:确保您已经将第三、四部分的所有文件都正确地放置到了您的主题目录下。
- 数据操作相关脚本:
/usr/themes/handsome/component/
目录下的get-qps.php
和update-qps.php
- ECharts 加载器和库文件:
/usr/themes/handsome/static/js/
目录下的echarts-loader.js
和echarts.min.js
- 后台更新脚本:
/usr/themes/handsome/component/
目录下的qps-updater.js
- 数据操作相关脚本:
-
准备ECharts库:如果您还没有,请从ECharts官网下载最新版本的
echarts.min.js
文件,并将其放置于/usr/themes/handsome/static/js/
目录下,与加载器脚本位于同一目录。这是本地图表库文件,可提供更好的加载性能。 -
创建缓存目录:手动在
/usr/themes/handsome/component/
目录下创建一个名为cache
的文件夹,并确保PHP对它有写入权限(权限755
或777
)。 -
引入核心加载器:编辑主题的
header.php
或footer.php
文件,确保echarts-loader.js
被引入。您只需添加一行:
<script src=\"options->themeUrl(\'static/js/echarts-loader.js\'); ?>\">
-
配置 Handsome 主题 PJAX 回调:
- 进入您的 Handsome 主题后台。
- 找到 PJAX相关设置(通常在\"全局设置\"或\"速度优化\"等菜单下)。
- 检查\"PJAX回调函数\"输入框中的内容:
- 如果输入框为空:直接填入
initQPSChartForHandsome
- 如果已有其他函数:您需要采用以下方式整合多个函数(请勿直接覆盖现有内容):
// 保留您现有的所有函数代码...// 在最后添加QPS图表初始化if (typeof initQPSChartForHandsome === \'function\') { initQPSChartForHandsome();}
- 如果输入框为空:直接填入
- 保存设置。
六、故障排查
如果配置后图表未显示或数据不更新,请按以下步骤检查:
- 清除浏览器缓存,强制加载最新的JS文件。
- 检查缓存目录权限:确认
/usr/themes/handsome/component/cache
目录存在且可写。 - 打开浏览器开发者工具 (F12):
- 切换到 “控制台 (Console)” 选项卡,查看是否有红色的错误信息。我们开启了调试模式,您应该能看到
[QPS后台更新器]
开头的日志,这表示后台更新脚本正在工作。 - 切换到 “网络 (Network)” 选项卡,筛选
Fetch/XHR
,检查是否每5秒都有对update-qps.php
和get-qps.php
的网络请求,以及它们的返回状态是否为200
。
- 切换到 “控制台 (Console)” 选项卡,查看是否有红色的错误信息。我们开启了调试模式,您应该能看到
通过以上步骤,您就能在博客中拥有一个稳定、可靠、且与 Handsome 主题完美兼容的实时QPS监控图表了。
作者:xuan
个人博客:https://blog.ybyq.wang
原文链接:https://blog.ybyq.wang/archives/776.html
欢迎访问我的博客,获取更多技术文章和教程。 - PJAX 兼容粘合剂:这是解决问题的关键。我们将前台图表的初始化逻辑,封装在一个全局函数