博客功能扩展、交互优化、视觉美化教程
前言在自定义主题的某天，AI摘要突然不显示了，前前后后改了好几次，通过重启插件无数次、移除全部自定义js和css、关闭pjax、一一恢复改过的页面代码，均未果。这次是通过本文第二个方法改好的，所以至今不知道突然不显示的原因。下面是解决AISummary插件的摘要块不显示的三种方案，前提是摘要内容已正确生成且保存。针对handsome主题，其他主题可借鉴思路。技术环境博客系统: Typecho 1.2.1主题: Handsome 10.1.0插件: AISummary v1.1.0解决方案方案一：修改插件核心逻辑直接修改 Plugin.php 中的 customContent 方法，将摘要插入到内容末尾：// 修改前 $content = $summaryString . $content; // 修改后 $content = $content . $summaryString;方案二：主题模板直接集成（推荐）在主题模板文件中直接添加摘要显示逻辑，绕过插件的 customContent 方法。实施步骤在插件设置中将"正文摘要显示样式"设置为"默认"或者"自定义"，推荐设置为自定义。在主题模板中添加摘要显示代码在 usr/themes/handsome/post.php 第74行的 下方添加：[hide] <?php // 添加AI摘要显示 $options = Typecho_Widget::widget('Widget_Options')->plugin('AISummary'); if ($options->summaryStyle !== '0') { $summaryField = $options->field ?: 'summary'; $db = Typecho_Db::get(); $row = $db->fetchRow($db->select('str_value')->from('table.fields')->where('cid = ?', $this->cid)->where('name = ?', $summaryField)); if ($row && !empty($row['str_value'])) { $pureSummary = str_replace("{{text}}", $row['str_value'], $options->prefix); // 根据插件配置选择样式 if ($options->summaryStyle === '1') { // 使用默认引用样式 $summaryString = ' ' . $pureSummary . ' '; } else if ($options->summaryStyle === '2') { // 使用自定义样式，只添加class，让CSS控制样式 $summaryString = ' ' . $pureSummary . ' '; } else { // 默认样式 $summaryString = ' ' . $pureSummary . ' '; } echo $summaryString; } } ?>[/hide]这种方式不依赖于Typecho的钩子系统，直接在主题模板中添加摘要显示代码。方案三：针对开启了前台Vditor解析方式第一种：1、打开文件：usr/themes/handsome/libs/content/PostContent.php2、找到第382行（大约在第380-385行之间）3、将$content = $obj->text;改为$content = $obj->content;4、保存文件$obj->text是vditor原始的Markdown文本，没有经过任何插件处理$obj->content是经过Typecho插件系统处理后的HTML内容，包括AISummary插件的customContent方法添加的摘要通过使用$obj->content，确保所有插件的钩子都能正常工作第二种：在handsome插件后台切换vidtor为typecho自带的即可下面是自定义模板的文章：[post cid="335" /]
SleepData 插件是一个用于在 Typecho 博客系统中记录、存储和分析个人睡眠数据的工具。它解决了部分健康应用（如 OPPO 健康）数据封闭、无公开 API 的问题，允许用户将数据掌握在自己手中。核心问题与解决方案许多健康应用（例如 OPPO 健康）不提供公开的 API 接口，也无法将数据同步到 Google Fit 等开放平台，导致用户的健康数据完全封闭，难以进行统一管理和长期分析。本插件通过提供一个独立的数据录入和存储系统来解决此问题。用户可以通过截图 OCR 识别、手动录入或 API 调用等方式，将睡眠数据保存在自己的服务器上，从而实现数据的自主掌控和长期追踪。工作流程整个数据处理流程从用户手机端的健康应用开始，经过前端的 OCR 识别与数据提交，再到服务器端的处理与存储，最后通过不同的页面进行展示。插件特性数据自主可控: 所有数据存储在用户自己的服务器上。双重存储: 同时保存到数据库和 JSON 文件，保证数据安全。OCR 自动录入: 通过识别截图，简化数据输入过程。API 支持: 开放 API 接口，方便进行二次开发或批量导入。自适应存储: 自动检测服务器环境，寻找可写目录，无需手动配置。双重配置: 支持后台配置和文件配置两种方式，提高灵活性。Handsome 主题集成: 与 Handsome 主题无缝集成，支持顶部导航栏和侧边栏展示。使用指南1. 安装插件从源码地址下载插件压缩包。解压后，将文件夹重命名为 SleepData。上传 SleepData 文件夹到 Typecho 的 usr/plugins 目录下。登录 Typecho 后台，进入"控制台" -> "插件"，找到"SleepData"并启用。2. 配置插件启用插件后，点击"设置"进入配置页面。API 访问令牌: 设置一个足够复杂的字符串作为 API 访问的密钥。这是保证数据安全的关键。此令牌有两种配置方式，系统会优先使用方法一：方法一（推荐）: 直接在后台设置页面填写并保存。方法二（备用）: 编辑插件目录下的 data_config.php 文件，修改 API_ACCESS_TOKEN 的值。3. 数据录入方式一：OCR 识别上传（推荐）这是最便捷的方式，尤其适用于从手机健康 App 录入数据。在浏览器中打开插件目录下的 https://博客地址/sleep-data-uploader/index.html 文件。填写您的 API 地址 和 访问令牌 （一次填写后浏览器会记住）。点击"拍照识别"或"选择截图"按钮，上传您在健康 App 中截取的睡眠数据图片。系统会自动识别图片中的数据并填充到表单中。核对无误后，点击"发送数据"。方式二：手动输入如果 OCR 识别有误或没有截图，可以选择手动填写所有睡眠数据，然后点击"发送数据"。方式三：通过 API 上传对于有开发能力的用户，可以通过 POST 请求直接向 API https://博客地址/usr/plugins/SleepData/simple-api.php 提交数据。请求体为 JSON 格式，需包含access_token及所有睡眠数据字段。4. 查看数据后台查看: 在 Typecho 后台的插件设置页面，会直接展示最近 50 条睡眠记录。独立页面查看: 访问 https://博客地址/usr/plugins/SleepData/view-data.php 可以查看 JSON 文件中的所有数据记录。数据库查看: 在数据库中的typecho_sleep_data表中查看所有数据记录。根目录查看: 在根目录下的temp/sleep_data.json文件中查看所有数据记录。5. 在 Handsome 主题中展示睡眠数据展示一：添加到顶部导航栏效果展示添加代码到主题的headnav.php文件中。[collapse status="false" title="php代码，放在nav navbar-nav hidden-sm的ul中"][hide] <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path> 睡眠统计 今日睡眠 本周平均 本月平均 累计睡眠 平均入睡 本周最晚 质量最佳 质量最差 睡眠趋势 加载中... [/hide][/collapse][collapse status="false" title="JS代码，添加到/header前面"][hide] [/hide][/collapse]展示二：添加到侧边栏效果展示添加代码到主题的sidebar.php文件中。[collapse status="false" title="php代码，放在博客信息的上下即可"][hide] <?php if (class_exists('SleepData_Plugin')): ?> <?php _me("今日睡眠") ?> <?php $sleepData = SleepData_Plugin::getTodaySleepData(); if ($sleepData): ?> <?php echo htmlspecialchars($sleepData['sleep_score']); ?> 分 <?php echo date('m月d日', strtotime($sleepData['date'])); ?> 总睡眠 <?php $totalSleep = ($sleepData['deep_sleep_minutes'] ?? 0) + ($sleepData['light_sleep_minutes'] ?? 0) + ($sleepData['rem_sleep_minutes'] ?? 0); echo SleepData_Plugin::formatMinutesToDuration($totalSleep); ?> 睡眠时段 <?php echo substr($sleepData['sleep_time'], 0, 5); ?> - <?php echo substr($sleepData['wake_up_time'], 0, 5); ?> 深睡 <?php echo SleepData_Plugin::formatMinutesToDuration($sleepData['deep_sleep_minutes']); ?> 浅睡 <?php echo SleepData_Plugin::formatMinutesToDuration($sleepData['light_sleep_minutes']); ?> 快速眼动 <?php echo SleepData_Plugin::formatMinutesToDuration($sleepData['rem_sleep_minutes']); ?> <?php else: ?> 手表没戴或者没电了，暂无今日睡眠数据 <?php endif; ?> <?php endif; ?>[/hide][/collapse]最后插件使用过程中有任何问题，欢迎在评论区留言。插件Github地址：[hide]https://github.com/BXCQ/SleepData[/hide]网盘下载：[hide]https://pan.xunlei.com/s/VOWWC8KD7Vl3QiqIDGk4xnk-A1?pwd=z8h6#[/hide]
Handsome 主题导航栏添加热榜聚合功能教程
前言在 Typecho Handsome 主题的导航栏添加"热榜聚合"功能，以下拉菜单形式展示"历史上的今天"、"抖音热榜"和"CSDN热榜"的实时内容，提升博客吸引力。效果：鼠标悬停或点击导航栏上的"火焰"图标，弹出包含三列热榜的窗口。操作步骤第一步找到并打开：/usr/themes/handsome/component/headnav.php文件第二步：添加代码将以下代码复制到 headnav.php 文件中。粘贴位置： 在文件中找到 <?php endif; ?> 这一行（约在 517 行，统计信息功能的结束标签），将代码粘贴在其后面。[hide] <path d="M9 14c1.5-2-1-4-2-5 1.5 3-2 6-2 8 0 .5 0 1 .5 1C8 19 13 16 9 14zm1-5c0-2-2-3-3-4 1 2-1 3-1 5s2 4 3 2v-3z"></path> 历史上的今天 查看更多 加载中... 抖音热榜 查看更多 加载中... CSDN热榜 查看更多 加载中... [/hide]如果主题未引入图标，则需要在此文件顶部添加 第三步：保存并刷新保存 headnav.php 文件，刷新博客页面。导航栏会出现新的火焰图标，点击即可看到热榜内容。代码解析代码由三部分组成：HTML 结构、CSS 样式和 JavaScript 逻辑。HTML：定义下拉菜单框架，包括三个板块展示不同热榜。CSS：为热榜列表提供样式，前三名设置醒目颜色。JavaScript：数据获取：通过 ajax 请求 https://api.guiguiya.com/api/hotlist 接口获取实时热榜数据。懒加载：数据仅在用户首次点击图标时加载，减少性能开销。浏览器缓存：数据缓存在 localStorage 中，缓存时间 30 分钟，提高加载速度，减轻 API 服务器压力。注意事项功能依赖第三方 API (api.guiguiya.com)，API 服务中断可能导致功能失效。代码使用 Font Awesome 图标库，Handsome 主题通常已包含，若图标未显示，请确认主题已加载该库。修改文件前请做好备份。结语通过此教程，可为 Handsome 主题博客添加热榜聚合功能，美化网站并为访客提供更多价值。
Typecho handsome 添加评论区OwO表情包 - QQ,抖音,B站等
我对网站的 OwO 表情包进行了一次大规模的扩充，新增和整理了包括 QQ、抖音、哔哩哔哩、微博、贴吧、酷安 等多个平台在内的热门表情。表情总览阿鲁推特B 站抖音酷安QQ微博贴吧滑稽表情包配置详情[collapse status="false" title="QQ"][tabs][tab name="json" active="true"]{ "QQ": { "name": "QQ", "type": "image", "container": [ { "icon": "aini", "text": "爱你" }, { "icon": "aixin", "text": "爱心" }, { "icon": "aoman", "text": "傲慢" }, { "icon": "baiyan", "text": "白眼" }, { "icon": "bangbangtang", "text": "棒棒糖" }, { "icon": "baojin", "text": "抱紧" }, { "icon": "baoquan", "text": "抱拳" }, { "icon": "bishi", "text": "鄙视" }, { "icon": "bizui", "text": "闭嘴" }, { "icon": "cahan", "text": "擦汗" }, { "icon": "daku", "text": "大哭" }, { "icon": "doge", "text": "doge" } // ... 更多QQ表情 ] } }[/tab][tab name="CSS"].emotion-QQ{width: 35px; height: auto;}[/tab][/tabs][/collapse][collapse status="false" title="抖音"][tabs][tab name="json" active="true"]{ "抖音": { "name": "douyin", "type": "image", "container": [ { "icon": "1", "text": "抖音-1" }, { "icon": "2", "text": "抖音-2" }, { "icon": "3", "text": "抖音-3" }, { "icon": "4", "text": "抖音-4" }, { "icon": "5", "text": "抖音-5" }, { "icon": "6", "text": "抖音-6" }, { "icon": "7", "text": "抖音-7" }, { "icon": "8", "text": "抖音-8" }, { "icon": "9", "text": "抖音-9" }, { "icon": "10", "text": "抖音-10" } // ... 更多抖音表情 ] } }[/tab][tab name="CSS"].emotion-douyin{width: 35px; height: auto;}[/tab][/tabs][/collapse][collapse status="false" title="哔哩哔哩"][tabs][tab name="json" active="true"]{ "哔哩哔哩":{ "name": "bilibili", "type": "image", "container":[ { "icon": "baiyan", "text": "白眼" }, { "icon": "bishi", "text": "鄙视" }, { "icon": "bizui", "text": "闭嘴" }, { "icon": "chan", "text": "馋" }, { "icon": "daku", "text": "大哭" }, { "icon": "dalao", "text": "大佬" }, { "icon": "dalian", "text": "打脸" }, { "icon": "dianzan", "text": "点赞" }, { "icon": "doge", "text": "doge" }, { "icon": "facai", "text": "发财" }, { "icon": "fadai", "text": "发呆" }, { "icon": "fanu", "text": "发怒" } // ... 更多B站表情 ] } }[/tab][tab name="CSS"].emotion-bilibili{width: 35px; height: auto;}[/tab][/tabs][/collapse][collapse status="false" title="阿鲁"][tabs][tab name="json" active="true"]{ "阿鲁": { "name": "aru", "type": "image", "container": [ { "icon": "despise", "text": "鄙视" }, { "icon": "knife", "text": "插刀" }, { "icon": "pouting", "text": "亲嘴" }, { "icon": "shutup", "text": "闭嘴" }, { "icon": "unhappy", "text": "不开心" }, { "icon": "discovertruth", "text": "发现真相" }, { "icon": "shit", "text": "便便" }, { "icon": "distressed", "text": "烦恼" }, { "icon": "shy", "text": "捂脸" }, { "icon": "speechless", "text": "无语" }, { "icon": "confuse", "text": "迷惑" }, { "icon": "frown", "text": "皱眉" } // ... 更多阿鲁表情 ] } }[/tab][tab name="CSS"].emotion-aru{width: 35px; height: auto;}[/tab][/tabs][/collapse][collapse status="false" title="推特"][tabs][tab name="json" active="true"]{ "推特": { "type": "image", "name": "twemoji", "container": [ { "icon": "smile", "text": "微笑" }, { "icon": "tongue", "text": "吐舌" }, { "icon": "tilted", "text": "歪头" }, { "icon": "tiaomei", "text": "挑眉" }, { "icon": "shy", "text": "害羞" }, { "icon": "sweat", "text": "流汗" }, { "icon": "proud", "text": "得意" }, { "icon": "uncomfort", "text": "难受" }, { "icon": "smilecry", "text": "笑哭" }, { "icon": "love", "text": "喜欢" }, { "icon": "notalk", "text": "不说话" }, { "icon": "happy", "text": "开心" } // ... 更多推特表情 ] } }[/tab][tab name="CSS"].emotion-twemoji{width: 35px; height: auto;}[/tab][/tabs][/collapse][collapse status="false" title="滑稽"][tabs][tab name="json" active="true"]{ "滑稽": { "type": "image", "name": "funny", "container": [ { "icon": "01", "text": "滑稽-1" }, { "icon": "02", "text": "滑稽-2" }, { "icon": "03", "text": "滑稽-3" }, { "icon": "04", "text": "滑稽-4" }, { "icon": "05", "text": "滑稽-5" }, { "icon": "06", "text": "滑稽-6" }, { "icon": "07", "text": "滑稽-7" }, { "icon": "08", "text": "滑稽-8" }, { "icon": "09", "text": "滑稽-9" }, { "icon": "10", "text": "滑稽-10" }, { "icon": "11", "text": "滑稽-11" }, { "icon": "12", "text": "滑稽-12" } // ... 更多滑稽表情 ] } }[/tab][tab name="CSS"].emotion-funny{width: 35px; height: auto;}[/tab][/tabs][/collapse][collapse status="false" title="微博"][tabs][tab name="json" active="true"]{ "微博": { "name": "weibo", "type": "image", "container": [ { "icon": "weibo_dog1", "text": "微博-dog1" }, { "icon": "weibo_dog11", "text": "微博-dog11" }, { "icon": "weibo_dog12", "text": "微博-dog12" }, { "icon": "weibo_dog3", "text": "微博-dog3" }, { "icon": "d_aini", "text": "爱你" }, { "icon": "d_aoteman", "text": "奥特曼" }, { "icon": "d_bingbujiandan", "text": "并不简单" }, { "icon": "d_bishi", "text": "鄙视" }, { "icon": "d_dahaqi", "text": "打哈气" }, { "icon": "d_xixi", "text": "嘻嘻" }, { "icon": "f_geili", "text": "给力" }, { "icon": "f_hufen", "text": "互粉" } // ... 更多微博表情 ] } }[/tab][tab name="CSS"].emotion-weibo{width: 35px; height: auto;}[/tab][/tabs][/collapse][collapse status="false" title="贴吧"][tabs][tab name="json" active="true"]{ "贴吧": { "name": "tieba", "type": "image", "container": [ { "icon": "tieba_emotion_02", "text": "贴吧-2" }, { "icon": "tieba_emotion_04", "text": "贴吧-4" }, { "icon": "tieba_emotion_05", "text": "贴吧-5" }, { "icon": "tieba_emotion_10", "text": "贴吧-10" }, { "icon": "tieba_emotion_11", "text": "贴吧-11" }, { "icon": "tieba_emotion_13", "text": "贴吧-13" }, { "icon": "tieba_emotion_14", "text": "贴吧-14" }, { "icon": "tieba_emotion_17", "text": "贴吧-17" }, { "icon": "tieba_emotion_23", "text": "贴吧-23" }, { "icon": "tieba_emotion_28", "text": "贴吧-28" }, { "icon": "tieba_emotion_30", "text": "贴吧-30" }, { "icon": "tieba_emotion_31", "text": "贴吧-31" } // ... 更多贴吧表情 ] } }[/tab][tab name="CSS"].emotion-tieba{width: 35px; height: auto;}[/tab][/tabs][/collapse][collapse status="false" title="酷安"][tabs][tab name="json" active="true"]{ "酷安": { "name": "kuan", "type": "image", "container": [ { "icon": "coolapk_emotion_100_qqdoge", "text": "qqdoge" }, { "icon": "coolapk_emotion_1010", "text": "酷安-1010" }, { "icon": "coolapk_emotion_1011", "text": "酷安-1011" }, { "icon": "coolapk_emotion_1012", "text": "酷安-1012" }, { "icon": "coolapk_emotion_101_pyjiaoyi", "text": "py交易" }, { "icon": "coolapk_emotion_102_fadai", "text": "发呆" }, { "icon": "coolapk_emotion_103_nb", "text": "nb" }, { "icon": "coolapk_emotion_104", "text": "酷安-104" }, { "icon": "coolapk_emotion_7_hehe", "text": "呵呵" }, { "icon": "coolapk_emotion_20_qinqin", "text": "亲亲" }, { "icon": "coolapk_emotion_14_bishi", "text": "鄙视" }, { "icon": "coolapk_emotion_1_hahaha", "text": "哈哈哈" } // ... 更多酷安表情 ] } }[/tab][tab name="CSS"].emotion-kuan{width: 35px; height: auto;}[/tab][/tabs][/collapse]使用说明下载地址完整的表情包合集和JSON文件：[hide]https://pan.xunlei.com/s/VOWLCVzpFoIhJcrKIo_BASAlA1?pwd=q7k6#[/hide]安装步骤下载表情包文件，解压到 /usr/themes/handsome/assets/img/emotion/ 目录下修改 /usr/themes/handsome/usr/OwO.json 文件，添加或更新表情包配置配置注意事项修改 OwO.json 文件时请注意 JSON 格式，确保格式正确无误表情包文件夹名称需要与 JSON 配置中的 name 值相同每个表情的 icon 值对应表情文件的文件名（不含扩展名）text 值为表情的显示名称，也是在评论中插入表情时使用的快捷代码示例配置"QQ": { "name": "QQ", // 对应文件夹名 /emotion/QQ/ "type": "image", "container": [ { "icon": "aini", // 对应文件 /emotion/QQ/aini.png "text": "爱你" // 表情显示名称和快捷代码 }, // 更多表情... ] }CDN 加速说明如果你的网站开启了 CDN 加速，请将表情包文件夹上传至CDN的 assets/img/emotion 目录下，确保 CDN 能够正确缓存和加速这些图片资源。CSS 样式定制为了统一管理表情包的显示效果，你可以在主题后台添加自定义 CSS 代码。这样可以方便地调整所有表情包的大小和其他样式。示例代码/* 表情包大小统一设置 */ .emotion-QQ { width: 35px; height: auto; } .emotion-douyin { width: 35px; height: auto; } .emotion-bilibili { width: 35px; height: auto; } .emotion-aru { width: 35px; height: auto; } .emotion-twemoji { width: 35px; height: auto; } .emotion-funny { width: 35px; height: auto; } .emotion-weibo { width: 35px; height: auto; } .emotion-tieba { width: 35px; height: auto; } .emotion-kuan { width: 35px; height: auto; } /* 可选：鼠标悬停放大效果 */ .OwO-body .OwO-item img:hover { transform: scale(1.2); transition: transform 0.2s ease; } /* 表情选择面板样式优化，默认386px只能显示7个表情，修改为546px可以显示10个表情 */ .OwO .OwO-body { max-height: 250px; width: 546px!important; } 这个由于面板样式的原因，刚开始加了很多但不显示，找了半天又改了半天才可以，也是废费了点小功夫。你可以根据自己的喜好调整表情大小，只需修改 width 的值即可。例如，改为 width: 35px 可以增大表情显示尺寸。表情格式转换说明由于handsome不支持直接设置GIF，且需要保持动画效果，我将原始的 QQ 表情 GIF 文件转换为 APNG 格式。APNG（Animated PNG）格式既保留了 PNG 的透明度优势，又能够显示动画效果。转换方法使用 gif2apng 命令行工具进行批量转换转换命令：gif2apng input.gif output.png批量处理脚本示例：for %i in (*.gif) do gif2apng "%i" "%~ni.png"注意：普通图片查看器可能无法正确显示 APNG 的动画效果，但在浏览器中可以正常播放动画。
本文将详细介绍如何在博客侧边栏实现请求总数显示功能，让您随时掌握网站的访问情况。一、请求总数统计预览二、实现思路实现侧边栏请求总数显示主要包含以下几个步骤：数据来源：从 WAF 获取请求总数数据数据缓存：利用本地缓存减少请求次数前端显示：在侧边栏显示数据自动更新：使用 JavaScript 定期刷新数据保持实时性三、后端实现1. 创建访问统计数据获取脚本首先，创建一个 PHP 脚本来获取来自 WAF 的访问统计数据：[hide]<?php // usr/themes/handsome/component/visitor-stats.php // 设置响应头 header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-cache, no-store, must-revalidate'); header('Expires: 0'); // 缓存文件路径 $cache_dir = __DIR__ . '/cache'; if (!is_dir($cache_dir)) { @mkdir($cache_dir, 0755, true); } $cache_file = $cache_dir . '/visitor_stats.json'; $log_file = $cache_dir . '/visitor_stats.log'; // 缓存时间设置为10分钟 $cache_time = 600; // API配置 - WAF接口 $api_url = 'https://your.waf.domain:9443/api/open/dashboard/user/counts'; $api_token = 'YOUR_WAF_API_TOKEN'; // 使用WAF的API Token // 检查缓存是否存在且有效 if (file_exists($cache_file) && (time() - filemtime($cache_file) < $cache_time) && !isset($_GET['refresh'])) { // 缓存有效，直接读取缓存 $content = file_get_contents($cache_file); echo $content; exit; } // 从WAF获取请求总数 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $api_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: application/json', 'X-SLCE-API-TOKEN: ' . $api_token ]); curl_setopt($ch, CURLOPT_TIMEOUT, 5); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($http_code === 200 && $response) { $data = json_decode($response, true); if ($data && isset($data['data']['total_requests'])) { // 成功获取数据 $result = [ 'total_pv' => (int)$data['data']['total_requests'], 'timestamp' => time(), 'cached' => false ]; // 保存到缓存 file_put_contents($cache_file, json_encode($result)); echo json_encode($result); } else { // API返回数据格式不正确 echo json_encode([ 'error' => '数据格式错误', 'total_pv' => 0, 'timestamp' => time() ]); } } else { // API请求失败 echo json_encode([ 'error' => $error ? $error : 'HTTP错误：' . $http_code, 'total_pv' => 0, 'timestamp' => time() ]); }[/hide]四、前端实现1. 创建高性能访问统计更新脚本以下是一个带本地缓存功能的高性能前端更新脚本：[collapse status="false" title="展开查看"]// usr/themes/handsome/component/direct-updater.js (function () { "use strict"; var CACHE_KEY = "handsome_visitor_stats"; var CACHE_EXPIRE_MS = 10 * 60 * 1000; // 10分钟缓存 function formatPV(value) { var num = parseInt(value, 10); if (isNaN(num)) return "..."; if (num >= 1000000) return (num / 1000000).toFixed(2) + " M"; if (num >= 1000) return (num / 1000).toFixed(2) + " K"; return num.toString(); } function updateDOM(formattedPV) { var elements = document.querySelectorAll( '.pv-count, [data-stat="pv"], .badge[data-type="pv"]' ); for (var i = 0; i < elements.length; i++) { elements[i].textContent = formattedPV; } } var cache = { get: function () { var item = window.localStorage.getItem(CACHE_KEY); if (!item) return null; try { var data = JSON.parse(item); if (Date.now() - data.timestamp < CACHE_EXPIRE_MS) { return data.value; } } catch (e) {} window.localStorage.removeItem(CACHE_KEY); return null; }, set: function (value) { try { var item = { value: value, timestamp: Date.now(), }; window.localStorage.setItem(CACHE_KEY, JSON.stringify(item)); } catch (e) {} }, }; function fetchData() { var xhr = new XMLHttpRequest(); xhr.open( "GET", "/usr/themes/handsome/component/direct-pv.php?t=" + Date.now(), true ); xhr.timeout = 5000; xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { try { var data = JSON.parse(xhr.responseText); if (data && typeof data.total_pv !== "undefined") { updateDOM(formatPV(data.total_pv)); cache.set(data); } } catch (e) {} } }; xhr.send(); } function init() { // 首先使用缓存的数据（如果有） var cachedData = cache.get(); if (cachedData) { updateDOM(formatPV(cachedData.total_pv)); } // 然后从服务器获取最新数据 // 如果有缓存，延迟获取以减少首次加载时的网络请求 var delay = cachedData ? 3000 : 100; setTimeout(fetchData, delay); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();[/collapse]2. 创建访问统计定期更新脚本以下脚本可以在后台定期更新访问统计数据：[hide]// usr/themes/handsome/component/visitor-updater.js (function () { // 配置项 const config = { updateInterval: 300000, // 更新间隔（毫秒）- 5分钟 updateEndpoint: "/usr/themes/handsome/component/visitor-stats.php", debugMode: false, }; // 安全日志输出 function safeLog(message, type = "log") { if ( config.debugMode && window.console && typeof window.console[type] === "function" ) { try { window.console[type]("[访问统计更新器] " + message); } catch (e) { // 静默失败 } } } // 更新访问统计数据 function updateStats() { safeLog("正在更新访问统计数据..."); // 获取最新PV值 const url = config.updateEndpoint + "?t=" + new Date().getTime(); fetch(url, { method: "GET", headers: { "X-Requested-With": "XMLHttpRequest", "Cache-Control": "no-cache, no-store, must-revalidate", }, }) .then((response) => { if (!response.ok) { throw new Error("网络错误: " + response.status); } return response.json(); }) .then((data) => { safeLog("访问统计数据更新成功"); // 如果页面上有显示PV的元素，更新它们 tryUpdateUI(data); }) .catch((error) => { safeLog("访问统计数据更新失败: " + error.message, "error"); }); } // 尝试更新UI上的数据 function tryUpdateUI(data) { try { // 找到页面上可能显示PV的元素 const pvElements = document.querySelectorAll( '.pv-count, [data-stat="pv"], .badge[data-type="pv"]' ); // 如果能找到元素并且数据有效，则更新 if (pvElements.length > 0 && data && data.total_pv) { let formattedPV = data.total_pv; // 格式化大数字 if (formattedPV >= 1000000) { formattedPV = (formattedPV / 1000000).toFixed(2) + " M"; } else if (formattedPV >= 1000) { formattedPV = (formattedPV / 1000).toFixed(2) + " K"; } // 更新所有找到的元素 pvElements.forEach((el) => { el.textContent = formattedPV; }); safeLog("UI更新成功"); } } catch (e) { safeLog("更新UI失败: " + e.message, "error"); } } // 初始化 - 加入PJAX兼容性 function init() { safeLog("访问统计更新器初始化..."); // 立即更新一次 updateStats(); // 设置定期更新 setInterval(updateStats, config.updateInterval); safeLog("访问统计更新器已启动，间隔: " + config.updateInterval + "ms"); } // 添加PJAX支持 if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } // 监听PJAX事件 document.addEventListener("pjax:end", function () { // 延迟执行，确保DOM已经更新 setTimeout(function () { tryUpdateUI({ total_pv: window.localStorage.getItem("handsome_visitor_stats") ? JSON.parse(window.localStorage.getItem("handsome_visitor_stats")) .value.total_pv : 0, }); }, 100); }); })();[/hide][hide]// usr/themes/handsome/component/direct-pv.php <?php /** * 直接获取PV数据的简化版脚本 * 用于前端JS调用，减少不必要的API请求 */ // 设置内容类型和缓存控制 header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-cache, no-store, must-revalidate'); header('Pragma: no-cache'); header('Expires: 0'); // 错误报告设置 - 不显示PHP错误 error_reporting(0); ini_set('display_errors', 0); // 确保不会有HTML或其他输出混入JSON ob_start(); // 缓存文件路径 $cache_dir = __DIR__ . '/cache'; $cache_file = $cache_dir . '/visitor_stats.json'; // 如果缓存文件存在，直接读取 if (file_exists($cache_file)) { $content = file_get_contents($cache_file); $data = json_decode($content, true); // 只返回需要的数据 $result = [ 'total_pv' => isset($data['total_pv']) ? $data['total_pv'] : 0, 'timestamp' => time() ]; // 确保之前没有其他输出 if (ob_get_length()) ob_clean(); // 返回数据 echo json_encode($result); exit; } else { // 如果缓存文件不存在，返回错误 if (ob_get_length()) ob_clean(); header('HTTP/1.1 404 Not Found'); echo json_encode([ 'error' => 'Cache file not found', 'timestamp' => time() ]); exit; } [/hide]五、侧边栏实现1. 在 sidebar.php 中添加请求总数显示函数首先在侧边栏文件中添加获取来自 WAF 的请求总数的函数：[collapse status="false" title="展开查看"]<?php // 获取WAF访问总量函数 function getSafeguardPV() { // 缓存文件路径 $cache_dir = __DIR__ . '/cache'; if (!is_dir($cache_dir)) { @mkdir($cache_dir, 0755, true); } $cache_file = $cache_dir . '/visitor_stats.json'; // 格式化数字的函数 function formatNumber($num) { if ($num >= 1000000) { return round($num / 1000000, 2) . ' M'; } elseif ($num >= 1000) { return round($num / 1000, 2) . ' K'; } else { return $num; } } // 如果缓存文件存在且未过期 if (file_exists($cache_file) && (time() - filemtime($cache_file) < 300)) { $pv_data = json_decode(file_get_contents($cache_file), true); if (is_array($pv_data) && isset($pv_data['total_pv'])) { return formatNumber($pv_data['total_pv']); } } // 使用curl直接访问API $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, '/usr/themes/handsome/component/direct-pv.php'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 2); $response = curl_exec($ch); curl_close($ch); if ($response) { $data = json_decode($response, true); if (isset($data['total_pv'])) { return formatNumber($data['total_pv']); } } // 如果出错，返回0 return '0'; } ?>[/collapse]2. 添加访问统计区域到侧边栏在 sidebar.php 中添加如下代码： <?php echo getSafeguardPV(); ?><?php _me("请求总数") ?> 六、整合配置1. 在 header.php 或 footer.php 中引入访问统计更新脚本在主题的 header.php 或 footer.php 文件中添加以下代码： <script src="<?php $this->options->themeUrl('component/direct-updater.js'); ?>"> <script src="<?php $this->options->themeUrl('component/visitor-updater.js'); ?>"> 2. 确保缓存目录可写确保创建缓存目录并设置正确的权限：mkdir -p /path/to/your/theme/cache chmod 755 /path/to/your/theme/cache七、故障排查1. 数据不显示或不更新检查缓存目录权限：确保缓存目录存在且可写检查路径配置：确保脚本路径正确查看浏览器控制台：检查是否有 JavaScript 错误启用调试模式：在 visitor-updater.js 中设置 debugMode 为 true2. 数据异常检查 API 响应：使用浏览器访问 direct-pv.php 查看原始数据重置缓存：删除缓存文件，强制重新获取数据检查格式化函数：确认 formatNumber 函数工作正常结语通过本教程，您已经成功实现了在博客侧边栏显示请求总数的功能。这个功能不仅能让您直观地了解网站的访问情况，还能为访客提供网站活跃度的参考。该实现具有良好的 PJAX 兼容性，可以在页面切换时保持数据的一致性和实时更新。希望本教程对您有所帮助，祝您的博客运营顺利！
对于使用 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环境中，数据更新和图表渲染也能独立、稳定地运行。三、文件准备请将以下4个文件，按照指定的路径和内容，放置到您的主题目录中。注意：本教程假设您已在 /usr/themes/handsome/static/js/ 目录下放置了 echarts.min.js 文件。这是本地的ECharts图表库，您可以从 ECharts官网 下载最新版本。若您没有此文件，我们的加载器会自动尝试从CDN获取，但建议使用本地版本以获得更好的加载速度和稳定性。1. 数据读取脚本路径: /usr/themes/handsome/component/get-qps.php(该脚本用于让前台图表安全地读取缓存数据)<?php // usr/themes/handsome/component/get-qps.php header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-cache, no-store, must-revalidate'); $cache_dir = __DIR__ . '/cache'; $cache_file = $cache_dir . '/safeguard_qps.json'; if (file_exists($cache_file)) { echo file_get_contents($cache_file); } else { // 如果缓存文件不存在，返回一个默认的JSON结构 echo json_encode(['qps' => 0, 'timestamp' => time(), 'simulated' => true]); }2. 数据更新脚本路径: /usr/themes/handsome/component/update-qps.php(该脚本用于从WAF获取真实数据并写入缓存)[hide]<?php // usr/themes/handsome/component/update-qps.php // 仅允许内部JS脚本通过AJAX调用 if ( !isset($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) !== 'xmlhttprequest' ) { header('HTTP/1.1 403 Forbidden'); exit('Access Denied'); } // --- WAF API 配置 --- $api_url = 'https://your.waf.domain:9443/api/stat/qps'; // 请替换为您的WAF API地址 $api_token = 'YOUR_WAF_API_TOKEN_HERE'; // 请替换为您的WAF API Token // -------------------- $cache_dir = __DIR__ . '/cache'; if (!is_dir($cache_dir)) { @mkdir($cache_dir, 0755, true); } $cache_file = $cache_dir . '/safeguard_qps.json'; function fetch_qps_from_api($url, $token) { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, // 生产环境建议设为true并配置证书 CURLOPT_SSL_VERIFYHOST => false, CURLOPT_HTTPHEADER => ['Accept: application/json', 'X-SLCE-API-TOKEN: ' . $token], CURLOPT_TIMEOUT => 5, ]); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http_code !== 200 || !$response) return null; $data = json_decode($response, true); if (!isset($data['data']['nodes']) || empty($data['data']['nodes'])) return null; $latestNode = end($data['data']['nodes']); foreach ($latestNode as $key => $value) { if ($key !== 'time' && is_numeric($value)) return floatval($value); } return null; } $qps = fetch_qps_from_api($api_url, $api_token); $output_data = [ 'qps' => ($qps !== null) ? $qps : 0, // 如果API失败，则返回0 'timestamp' => time(), 'simulated' => ($qps === null) ]; file_put_contents($cache_file, json_encode($output_data)); header('Content-Type: application/json'); echo json_encode(['success' => true, 'data' => $output_data]);[/hide]3. ECharts 延迟加载器路径: /usr/themes/handsome/static/js/echarts-loader.js(这是我们修改过的、兼容PJAX的版本)/** * ECharts 延迟加载器 (PJAX兼容版) * 只在需要时才加载 ECharts 库，避免阻塞页面渲染 */ (function() { function safeLog(message, type) { try { if (window.console && typeof window.console[type || 'log'] === 'function') { window.console[type || 'log'](message); } } catch (e) {} } window.ECHARTS_LOADER = { loaded: false, loading: false, callbacks: [], localPath: '/usr/themes/handsome/static/js/echarts.min.js', // 本地ECharts库路径 cdnPath: 'https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js', onLoad: function(callback) { if (typeof callback !== 'function') return; if (this.loaded && window.echarts) { callback(window.echarts); } else { this.callbacks.push(callback); if (!this.loading) this.loadECharts(); } }, loadECharts: function() { if (this.loading || this.loaded) return; this.loading = true; var self = this; var script = document.createElement('script'); script.type = 'text/javascript'; script.src = this.localPath; // 优先使用本地路径 script.onload = function() { self.loaded = true; self.loading = false; safeLog('ECharts 加载成功', 'info'); while (self.callbacks.length > 0) { try { self.callbacks.shift()(window.echarts); } catch (e) {} } }; // 仅在本地加载失败时，才尝试从CDN加载 script.onerror = function() { safeLog('从本地加载 ECharts 失败，尝试从 CDN 加载', 'warn'); script.src = self.cdnPath; script.onerror = function() { safeLog('从 CDN 加载 ECharts 也失败了', 'error'); self.loading = false; self.callbacks = []; }; }; document.body.appendChild(script); } }; function checkForEChartsElements() { if (window.echarts) { window.ECHARTS_LOADER.loaded = true; return; } var chartContainers = document.querySelectorAll('.top-echart, #safeguard-qps-chart, #steps-chart, #sleep-chart'); if (chartContainers.length > 0) { window.ECHARTS_LOADER.loadECharts(); } } if (document.readyState === 'complete') { checkForEChartsElements(); } else { window.addEventListener('load', checkForEChartsElements); } // PJAX 兼容处理 document.addEventListener('pjax:end', checkForEChartsElements); window.loadECharts = function(callback) { window.ECHARTS_LOADER.onLoad(callback); }; })();4. 后台数据更新器 (JS)路径: /usr/themes/handsome/component/qps-updater.js(这是我们修改过的、兼容PJAX的版本)[hide]/** * 雷池WAF QPS数据后台自动更新脚本 - PJAX兼容版 */ // 使用全局卫兵变量，确保脚本只初始化一次 if (typeof window.qpsUpdaterIntervalId === 'undefined') { window.qpsUpdaterIntervalId = null; } (function () { if (window.qpsUpdaterIntervalId !== null) { if (window.console) console.log('[QPS后台更新器] 更新器已在运行，本次初始化跳过。'); return; } const config = { updateInterval: 5000, updateEndpoint: '/usr/themes/handsome/component/update-qps.php', debugMode: true // 建议在排错时开启，正常运行时可设为false }; function safeLog(message, type = 'log') { if (config.debugMode && window.console && typeof window.console[type] === 'function') { window.console[type]('[QPS后台更新器] ' + message); } } function updateQPSData() { safeLog('正在向服务器请求更新QPS数据...'); fetch(config.updateEndpoint, { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(response => { if (!response.ok) throw new Error('网络错误: ' + response.status); return response.json(); }) .then(data => { if (data.success) safeLog('服务器成功更新QPS数据。'); else safeLog('服务器更新QPS数据失败: ' + (data.error || '未知错误'), 'warn'); }) .catch(error => safeLog('请求更新QPS数据时出错: ' + error.message, 'error')); } function init() { safeLog('初始化...'); updateQPSData(); window.qpsUpdaterIntervalId = setInterval(updateQPSData, config.updateInterval); safeLog('已启动，更新间隔: ' + config.updateInterval + 'ms'); } init(); })();[/hide]四、侧边栏集成现在，编辑您的侧边栏文件（例如 usr/themes/handsome/component/sidebar.php 或主题本身的 sidebar.php），在您希望显示图表的位置，加入以下代码块。 实时QPS 0 五、整合与配置 (关键步骤)确认文件位置：确保您已经将第三、四部分的所有文件都正确地放置到了您的主题目录下。数据操作相关脚本：/usr/themes/handsome/component/ 目录下的 get-qps.php 和 update-qps.phpECharts 加载器和库文件：/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="<?php $this->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。通过以上步骤，您就能在博客中拥有一个稳定、可靠、且与 Handsome 主题完美兼容的实时QPS监控图表了。
前言在个人博客的侧边栏展示自己的QQ信息，包括头像、昵称、QQ号、等级和Q龄等，不仅可以增加博客的个性化，还能让访客更好地了解博主。本教程将详细介绍如何在Typecho博客的Handsome主题侧边栏中添加QQ信息卡片功能。效果展示实现后的效果是在侧边栏显示一个包含以下信息的QQ卡片：QQ头像QQ昵称QQ号码Q龄（注册年份）QQ等级（使用emoji表情：👑☀️🌙⭐表示不同等级）实现步骤1. 准备工作首先，我们需要找到合适的API来获取QQ信息。本教程使用的是jkapi.com提供的API服务，它可以根据QQ号获取相关信息，这个用了半个月就不行了，换成了api.dwo.cc的。2. 修改sidebar.php文件在Handsome主题中，侧边栏的内容是由sidebar.php文件控制的。我们需要编辑这个文件来添加QQ信息卡片。2.1 找到合适的位置通常，我们会将QQ信息卡片添加到侧边栏的个人信息模块附近。在sidebar.php文件中找到合适的位置，例如在博主信息卡片之后。2.2 添加QQ信息卡片代码将以下代码添加到选定的位置：[hide] <?php _me("QQ信息") ?> <?php function getQQInfoFromDwoApi($qq) { $url = "https://api.dwo.cc/api/aiys_qq?qq=" . $qq; $ch = curl_init(); // 设置cURL选项 curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_TIMEOUT, 5); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'ckey: *************', // 在header中传递秘钥 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ]); $response = curl_exec($ch); curl_close($ch); if ($response) { $data = json_decode($response, true); if (isset($data['code']) && $data['code'] == 200) { return $data; } } return null; } function generateQQLevelIconsHtml($level) { $html = ''; $level = intval($level); // 等级计算规则 $crowns = floor($level / 64); $suns = floor(($level % 64) / 16); $moons = floor(($level % 16) / 4); $stars = $level % 4; // 使用emoji表情替代图片，更加稳定可靠 $crown_icon = '👑'; // 皇冠 $sun_icon = '☀️'; // 太阳 $moon_icon = '🌙'; // 月亮 $star_icon = '⭐'; // 星星 $icon_style = 'style="font-size:16px; margin:0 1px;"'; for ($i = 0; $i < $crowns; $i++) { $html .= '' . $crown_icon . ''; } for ($i = 0; $i < $suns; $i++) { $html .= '' . $sun_icon . ''; } for ($i = 0; $i < $moons; $i++) { $html .= '' . $moon_icon . ''; } for ($i = 0; $i < $stars; $i++) { $html .= '' . $star_icon . ''; } return $html; } // 缓存文件路径 - 使用Typecho的缓存目录 $cache_dir = defined('__TYPECHO_TMP_DIR__') ? __TYPECHO_TMP_DIR__ : __DIR__ . '/cache'; if (!is_dir($cache_dir)) { @mkdir($cache_dir, 0755, true); } $qq = '123456789'; // 您的QQ号 $cache_file = $cache_dir . '/qq_info_' . $qq . '.json'; $cache_time = 86400; // 缓存1天（单位：秒） // 检查缓存是否存在且有效 $qqInfo = null; if (file_exists($cache_file) && (time() - filemtime($cache_file) < $cache_time)) { // 缓存有效，直接读取缓存 $cache_content = @file_get_contents($cache_file); if ($cache_content !== false) { $qqInfo = json_decode($cache_content, true); } } // 如果缓存无效或读取失败，则调用API if ($qqInfo === null) { // 缓存无效或不存在，调用API获取数据 $qqInfo = getQQInfoFromDwoApi($qq); // 如果API调用成功，保存到缓存 if ($qqInfo) { @file_put_contents($cache_file, json_encode($qqInfo)); } } // 只使用API返回的数据或缓存数据 if ($qqInfo && isset($qqInfo['data'])) { // 新API结构 - aiys_qq接口 $level = $qqInfo['data']['iQQLevel'] ?? ''; $nickname = $qqInfo['data']['sNickName'] ?? ''; $avatar = $qqInfo['data']['sFaceUrl'] ?? ''; // 获取PC端在线时长(单位：分钟) $pcOnlineTime = isset($qqInfo['data']['iPCQQOnlineTime']) ? intval($qqInfo['data']['iPCQQOnlineTime']) : 0; // 获取升级剩余天数 $nextLevelDay = $qqInfo['data']['iNextLevelDay'] ?? ''; // 获取总活跃天数 $totalActiveDay = $qqInfo['data']['iTotalActiveDay'] ?? ''; // 获取VIP等级 $vipLevel = $qqInfo['data']['iVipLevel'] ?? 0; // 是否是SVIP $isSVip = isset($qqInfo['data']['iSVip']) && $qqInfo['data']['iSVip'] == '1'; $levelIconsHtml = generateQQLevelIconsHtml($level); ?> <?php if ($avatar): ?> <img src="<?php echo $avatar; ?>" class="img-circle" loading="lazy" style="width:60px; height:60px; border:2px solid #fff; box-shadow:0 2px 5px rgba(0,0,0,0.1);"> <?php else: ?> <?php endif; ?> <?php echo htmlspecialchars($nickname ?: 'QQ用户'); ?> <?php echo $qq; ?> <?php if ($vipLevel > 0): ?> <i data-feather="award" style="width:16px; height:16px; margin-right:6px; flex-shrink: 0; <?php echo $isSVip ? 'color:#FF0000;' : 'color:#FF9800;'; ?>"> <span style="<?php echo $isSVip ? 'color:#FF0000;' : 'color:#FF9800;'; ?>"> <?php echo $isSVip ? 'SVIP' : 'VIP'; ?><?php echo $vipLevel; ?> <?php endif; ?> <?php if ($level): ?> LV<?php echo $level; ?> <?php echo $levelIconsHtml; ?> <?php endif; ?> <?php if ($pcOnlineTime > 0): ?> PC在线时长: <?php echo $pcOnlineTime; ?> 分钟 <?php endif; ?> <?php if ($nextLevelDay): ?> 升级剩余: <?php echo $nextLevelDay; ?>天 <?php endif; ?> <?php if ($totalActiveDay): ?> 总活跃天数: <?php echo $totalActiveDay; ?>天 <?php endif; ?> <?php } else { // API请求失败且没有缓存数据时显示 echo ' 暂时无法获取QQ信息 '; } ?> [/hide]3. 添加主题设置选项为了让用户可以通过主题设置面板控制是否显示QQ信息卡片，我们需要修改主题的设置文件。在Handsome主题的functions.php文件中找到themeConfig函数，在适当位置添加以下代码：//侧边栏显示QQ信息 $form->checkbox('sidebarBlock', array('ShowQQInfo'), _t('侧边栏显示QQ信息'), _t('开启后侧边栏将显示QQ信息卡片'));4. 修改两个数值1、$qq = '676567473'; // 改成你的QQ号2、'ckey: *', // 改成你的秘钥在小渡api（获取api的网站）中注册登录，并查看秘钥注意事项替换QQ号码：在代码中将'你的QQ号码'替换为你自己的QQ号码。API可用性：本教程使用的是jkapi.com的API，如果该API不可用，可能需要寻找替代的API服务。缓存机制：代码中已实现了缓存机制，数据会缓存10天，避免频繁请求API。等级图标：使用emoji表情（👑☀️🌙⭐）代替传统的QQ等级图标，避免因CDN链接不稳定导致图标无法显示。错误处理：代码中包含了基本的错误处理机制，当API请求失败时会显示错误信息。常见问题Q: 为什么我的QQ信息不显示？A: 可能是API服务不稳定或者网络问题，请检查浏览器控制台是否有错误信息。Q: 如何修改缓存时间？A: 在代码中找到10 * 24 * 60 * 60 * 1000这一部分，修改前面的数字可以改变缓存天数。Q: 如何添加更多QQ信息？A: 可以根据API返回的数据，在displayQQInfo函数中添加更多信息的显示。结语通过以上步骤，你已经成功在Typecho博客的Handsome主题侧边栏添加了QQ等级和信息显示功能。这个功能不仅美观实用，还具有缓存机制，避免频繁请求API导致的性能问题。如果你有任何问题或改进建议，欢迎在评论区留言讨论。
分享60个有趣实用的API及其Typecho博客集成指南
引言突发奇想，搜集了一些公开API，为博客添加一些动态和个性化的元素让网站更加生动有趣。本文介绍60个有趣的API，它们都可以集成到Typecho博客中，让您的博客脱颖而出。目录个人社交账号API热搜榜单API工具类API内容增强类API博客功能增强类API游戏与娱乐类API个人社交账号API1. QQ等级显示通过QQ空间API获取您的QQ等级、成长值等信息，并在博客中展示。<?php // 在sidebar.php或自定义页面中添加 function getQQLevel($qq) { $url = "https://r.qzone.qq.com/fcg-bin/cgi_get_score.fcg?mask=7&uins=".$qq; $data = file_get_contents($url); $data = mb_convert_encoding($data, "UTF-8", "GBK"); preg_match('/lvtext:"(\d+级)"/', $data, $level); return isset($level[1]) ? $level[1] : '获取失败'; } $qq = '你的QQ号'; echo '我的QQ等级: '.getQQLevel($qq); ?>2. QQ音乐状态API展示您正在听的QQ音乐歌曲信息。<?php function getQQMusicStatus($qq) { // QQ音乐状态获取 $url = "https://c.y.qq.com/rsc/fcgi-bin/fcg_get_profile_homepage.fcg?format=json&inCharset=utf-8&outCharset=utf-8&notice=0&platform=yqq&uin=".$qq; $context = stream_context_create([ 'http' => [ 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ] ]); $data = json_decode(file_get_contents($url, false, $context), true); // 判断是否有正在收听的歌曲 if (isset($data['data']['listen']) && !empty($data['data']['listen']['now'])) { return [ 'status' => true, 'song_name' => $data['data']['listen']['now']['song_name'], 'singer' => $data['data']['listen']['now']['singer'][0]['name'], 'cover' => $data['data']['listen']['now']['cover'] ]; } else { return [ 'status' => false ]; } } $qq = '你的QQ号'; $music = getQQMusicStatus($qq); if ($music['status']) { echo ' '; echo ' 我正在听 '; echo ' '; echo ' '; echo ' '.$music['song_name'].' '; echo ' - '.$music['singer'].' '; echo ' '; echo ' '; echo ' '; } else { echo '暂无音乐播放记录'; } ?>3. 哔哩哔哩UP主数据展示您的B站粉丝数、视频数、播放量等数据。<?php function getBiliStats($uid) { $url = "https://api.bilibili.com/x/relation/stat?vmid=".$uid."&jsonp=jsonp"; $data = json_decode(file_get_contents($url), true); return $data['data']; } $uid = '你的B站UID'; $stats = getBiliStats($uid); echo "粉丝数: ".$stats['follower']; ?>4. 网易云音乐状态展示您最近听的歌曲或者创建一个迷你播放器。<?php // 网易云音乐用户歌单获取 function getNeteaseMusicPlaylist($uid) { $url = "http://music.163.com/api/user/playlist/?offset=0&limit=1&uid=".$uid; $data = json_decode(file_get_contents($url), true); return $data['playlist'][0]['id']; } $uid = '你的网易云音乐ID'; $playlistId = getNeteaseMusicPlaylist($uid); ?> <iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=<?php echo $playlistId; ?>&auto=0&height=66"> 5. GitHub贡献统计在博客上展示您的GitHub贡献图表。 6. 豆瓣读书/电影展示您最近阅读的书籍或观看的电影。<?php function getDoubanRecentBooks($uid) { $url = "https://api.douban.com/v2/book/user/".$uid."/collections?status=reading&count=5"; $data = json_decode(file_get_contents($url), true); return $data; } $uid = '你的豆瓣ID'; $books = getDoubanRecentBooks($uid); foreach ($books['collections'] as $book) { echo $book['book']['title'].' '; } ?>7. Instagram基本数据通过Instagram API展示您的基本数据。<?php function getInstagramBasicData($access_token) { $url = "https://graph.instagram.com/me?fields=id,username,account_type&access_token=".$access_token; $data = json_decode(file_get_contents($url), true); return $data; } $access_token = "您的Instagram访问令牌"; $data = getInstagramBasicData($access_token); echo "Instagram用户名: ".$data['username']; ?>8. Discord服务器状态展示您的Discord服务器状态和成员数。<?php function getDiscordServerInfo($server_id, $bot_token) { $url = "https://discord.com/api/guilds/".$server_id; $context = stream_context_create([ 'http' => [ 'header' => 'Authorization: Bot '.$bot_token ] ]); $data = json_decode(file_get_contents($url, false, $context), true); return $data; } $server_id = "您的Discord服务器ID"; $bot_token = "您的Discord机器人令牌"; $server = getDiscordServerInfo($server_id, $bot_token); echo "Discord服务器: ".$server['name']." "; echo "成员数: ".$server['approximate_member_count']; ?>9. Telegram频道订阅数展示您的Telegram频道订阅人数。<?php function getTelegramChannelInfo($channel_name) { // 需要通过Telegram Bot API获取频道信息 $bot_token = "您的Telegram机器人令牌"; $url = "https://api.telegram.org/bot".$bot_token."/getChatMembersCount?chat_id=@".$channel_name; $data = json_decode(file_get_contents($url), true); return $data['result']; } $channel = "您的频道名称（不含@）"; $subscribers = getTelegramChannelInfo($channel); echo "@".$channel." 订阅人数: ".$subscribers; ?>10. 小红书笔记展示在博客中展示您最近的小红书笔记。<?php function getXiaoHongShuPosts($userId) { // 获取小红书用户公开信息 $url = "https://www.xiaohongshu.com/user/profile/" . $userId; $context = stream_context_create([ 'http' => [ 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ] ]); $html = file_get_contents($url, false, $context); // 使用正则表达式提取笔记信息 // 注意：这是一个简化版，实际需要考虑网站结构变化 preg_match_all('/ (.*?)<\/div>/s', $html, $matches); $posts = []; foreach ($matches[1] as $match) { if (preg_match('/<img src="(.*?)"/s', $match, $img) && preg_match('/ (.*?)<\/div>/s', $match, $title)) { $posts[] = [ 'title' => trim($title[1]), 'image' => $img[1] ]; } } return $posts; } $userId = '你的小红书ID'; $posts = getXiaoHongShuPosts($userId); echo ' 我的小红书笔记 '; echo ' '; foreach (array_slice($posts, 0, 4) as $post) { echo ' '; echo ' '; echo ' '.$post['title'].' '; echo ' '; } echo ' '; ?>11. 王者荣耀战绩API展示您的王者荣耀游戏数据。<?php function getKingGloryStats($openId) { // 需要通过官方申请接口权限，这里仅提供示例 $apiKey = '你的API密钥'; $url = "https://api.wgapi.com/v2/player?openid={$openId}&token={$apiKey}"; $data = json_decode(file_get_contents($url), true); if ($data['code'] == 200) { return [ 'nickname' => $data['data']['nickname'], 'tier' => $data['data']['tier_name'], 'win_rate' => $data['data']['win_rate'], 'total_matches' => $data['data']['total_matches'] ]; } else { return null; } } $openId = '您的王者荣耀OpenID'; $stats = getKingGloryStats($openId); if ($stats) { echo ' '; echo ' 我的王者荣耀战绩 '; echo ' 昵称: '.$stats['nickname'].' '; echo ' 段位: '.$stats['tier'].' '; echo ' 胜率: '.$stats['win_rate'].'% '; echo ' 总场次: '.$stats['total_matches'].' '; echo ' '; } else { echo '数据获取失败'; } ?>12. Steam游戏状态展示您的Steam游戏状态和最近玩的游戏。<?php function getSteamGameStatus($steamid, $apikey) { $url = "https://api.steampowered.com/IPlayerService/GetRecentlyPlayedGames/v1/?key=".$apikey."&steamid=".$steamid."&format=json"; $data = json_decode(file_get_contents($url), true); return $data['response']['games']; } $steamid = '你的SteamID'; $apikey = '你的Steam API密钥'; $games = getSteamGameStatus($steamid, $apikey); echo " 我最近玩的游戏 "; foreach(array_slice($games, 0, 3) as $game) { echo $game['name']." - 最近游戏时间: ".round($game['playtime_2weeks']/60, 1)." 小时 "; } ?>热搜榜单API13. 微博热搜API展示实时的微博热搜榜单。<?php function getWeiboHotSearch() { $url = "https://weibo.com/ajax/side/hotSearch"; $context = stream_context_create([ 'http' => [ 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ] ]); $data = json_decode(file_get_contents($url, false, $context), true); return $data['data']['realtime']; } echo " 微博热搜 "; echo " "; foreach(array_slice(getWeiboHotSearch(), 0, 10) as $item) { echo " ".$item['note']." (".number_format($item['num']).") "; } echo " "; ?>14. 知乎热榜API展示知乎热门话题。<?php function getZhihuHotTopics() { $url = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=10"; $context = stream_context_create([ 'http' => [ 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ] ]); $data = json_decode(file_get_contents($url, false, $context), true); return $data['data']; } echo " 知乎热榜 "; echo " "; foreach(getZhihuHotTopics() as $item) { echo " ".$item['target']['title']." "; } echo " "; ?>15. 抖音热门话题展示当前抖音热门话题和标签。<?php function getDouyinHotTopics() { // 使用公开的抖音热榜API $url = "https://www.iesdouyin.com/web/api/v2/hotsearch/billboard/word/"; $context = stream_context_create([ 'http' => [ 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ] ]); $data = json_decode(file_get_contents($url, false, $context), true); if (isset($data['word_list'])) { return $data['word_list']; } else { return []; } } $topics = getDouyinHotTopics(); echo ' 抖音热门话题 '; echo ' '; foreach (array_slice($topics, 0, 10) as $index => $topic) { echo ' '; echo ''.($index + 1).' '; echo $topic['word']; if (isset($topic['hot_value'])) { echo ' ('.number_format($topic['hot_value']).')'; } echo ' '; } echo ' '; ?>16. 百度热搜榜展示百度搜索热榜内容。<?php function getBaiduHotSearch() { $url = "https://top.baidu.com/api/board?platform=wise&tab=realtime"; $context = stream_context_create([ 'http' => [ 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ] ]); $data = json_decode(file_get_contents($url, false, $context), true); if (isset($data['data']['cards'][0]['content'])) { return $data['data']['cards'][0]['content']; } else { return []; } } $hotSearches = getBaiduHotSearch(); echo ' 百度热搜榜 '; echo ' '; foreach (array_slice($hotSearches, 0, 10) as $index => $item) { echo ' '; echo ''.($index + 1).' '; echo $item['word']; if (isset($item['hotScore'])) { echo ' ('.number_format($item['hotScore']).')'; } echo ' '; } echo ' '; ?>17. 腾讯新闻热点展示腾讯新闻热点话题。<?php function getTencentHotNews() { $url = "https://i.news.qq.com/trpc.qqnews_web.kv_srv.kv_srv_http_proxy/list?sub_srv_id=24hours&srv_id=pc"; $context = stream_context_create([ 'http' => [ 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ] ]); $data = json_decode(file_get_contents($url, false, $context), true); if (isset($data['data']['list'])) { return $data['data']['list']; } else { return []; } } $hotNews = getTencentHotNews(); echo ' 腾讯新闻热点 '; echo ' '; foreach (array_slice($hotNews, 0, 10) as $index => $news) { echo ' '; echo ''.($index + 1).' '; echo ''.$news['title'].''; echo ' '; } echo ' '; ?>18. 哔哩哔哩热搜展示B站当前热搜词。<?php function getBilibiliHotSearch() { $url = "https://api.bilibili.com/x/web-interface/search/square?limit=10"; $context = stream_context_create([ 'http' => [ 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ] ]); $data = json_decode(file_get_contents($url, false, $context), true); if (isset($data['data']['trending']['list'])) { return $data['data']['trending']['list']; } else { return []; } } $hotSearch = getBilibiliHotSearch(); echo ' B站热搜 '; echo ' '; foreach ($hotSearch as $index => $item) { echo ' '; echo ''.($index + 1).' '; echo $item['keyword']; if (isset($item['show_name'])) { echo ' '.$item['show_name'].''; } echo ' '; } echo ' '; ?>19. 即刻社区热门展示即刻社区的热门话题。<?php function getJikeCommunityHot() { // 即刻热门话题API $url = "https://api.ruguoapp.com/1.0/topics/recommendation/list"; $context = stream_context_create([ 'http' => [ 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ] ]); try { $data = json_decode(file_get_contents($url, false, $context), true); if (isset($data['data'])) { return $data['data']; } } catch (Exception $e) { // 接口可能不稳定，返回一些示例数据 return [ ['content' => '今天又是元气满满的一天', 'likeCount' => 2453], ['content' => '来分享你的摄影作品', 'likeCount' => 1892], ['content' => '程序员日常', 'likeCount' => 1654], ['content' => '美食分享计划', 'likeCount' => 1245], ['content' => '今日份猫猫', 'likeCount' => 985] ]; } return []; } $topics = getJikeCommunityHot(); echo ' 即刻热门话题 '; echo ' '; foreach (array_slice($topics, 0, 5) as $topic) { echo ' '; echo $topic['content']; if (isset($topic['likeCount'])) { echo ' ('.$topic['likeCount'].'人关注)'; } echo ' '; } echo ' '; ?>20. 搜狗热搜榜展示搜狗搜索引擎热搜榜。<?php function getSogouHotSearch() { $url = "https://top.sogou.com/hot/json"; $context = stream_context_create([ 'http' => [ 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ] ]); try { $data = json_decode(file_get_contents($url, false, $context), true); if (isset($data['data'])) { return $data['data']; } } catch (Exception $e) { // 返回示例数据 return [ ['keyword' => '热门电影', 'heat' => 9876], ['keyword' => '国内新闻头条', 'heat' => 8765], ['keyword' => '今日天气', 'heat' => 7654], ['keyword' => '最新游戏', 'heat' => 6543], ['keyword' => '热门综艺', 'heat' => 5432] ]; } return []; } $hotSearch = getSogouHotSearch(); echo ' 搜狗热搜榜 '; echo ' '; foreach (array_slice($hotSearch, 0, 10) as $index => $item) { echo ' '; echo ''.($index + 1).' '; echo $item['keyword']; if (isset($item['heat'])) { echo ' ('.number_format($item['heat']).')'; } echo ' '; } echo ' '; ?>21. 360热搜榜展示360搜索热榜内容。<?php function get360HotSearch() { $url = "https://trends.so.com/hot/search"; $context = stream_context_create([ 'http' => [ 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ] ]); try { $html = file_get_contents($url, false, $context); // 使用正则表达式提取数据 preg_match_all('/(.*?)<\/a>/s', $html, $matches); $results = []; for ($i = 0; $i < count($matches[2]); $i++) { $results[] = [ 'keyword' => trim(strip_tags($matches[2][$i])), 'url' => $matches[1][$i] ]; } return $results; } catch (Exception $e) { // 返回示例数据 return [ ['keyword' => '今日头条', 'url' => '#'], ['keyword' => '最新电影', 'url' => '#'], ['keyword' => '热门游戏', 'url' => '#'], ['keyword' => '流行音乐', 'url' => '#'], ['keyword' => '科技新闻', 'url' => '#'] ]; } } $hotSearch = get360HotSearch(); echo ' 360热搜榜 '; echo ' '; foreach (array_slice($hotSearch, 0, 10) as $index => $item) { echo ' '; echo ''.($index + 1).' '; echo ''.$item['keyword'].''; echo ' '; } echo ' '; ?>22. 热搜聚合展示将多个热搜榜单整合到一个页面展示。<?php // 获取各平台热搜函数 // ... (使用前面定义的函数) ... // 展示聚合热搜 echo ' '; // 微博热搜 echo ' '; echo ' 微博热搜 '; echo ' '; foreach(array_slice(getWeiboHotSearch(), 0, 5) as $index => $item) { echo ' '.($index + 1).' '.$item['note'].' '; } echo ' '; echo ' '; // 知乎热榜 echo ' '; echo ' 知乎热榜 '; echo ' '; foreach(array_slice(getZhihuHotTopics(), 0, 5) as $index => $item) { echo ' '.($index + 1).' '.$item['target']['title'].' '; } echo ' '; echo ' '; // 百度热搜 echo ' '; echo ' 百度热搜 '; echo ' '; $baiduHot = getBaiduHotSearch(); foreach(array_slice($baiduHot, 0, 5) as $index => $item) { echo ' '.($index + 1).' '.$item['word'].' '; } echo ' '; echo ' '; echo ' '; ?>工具类API23. 天气API根据IP或设定位置显示天气信息。<?php function getWeather($city) { $url = "http://wthrcdn.etouch.cn/weather_mini?city=".urlencode($city); $data = json_decode(file_get_contents($url), true); return $data['data']['forecast'][0]; } $city = '北京'; $weather = getWeather($city); echo $city.' 今天: '.$weather['type'].', '.$weather['low'].' ~ '.$weather['high']; ?>24. 一言API随机展示一句名言。<?php $hitokoto = json_decode(file_get_contents('https://v1.hitokoto.cn/'), true); echo $hitokoto['hitokoto'].' —— '.$hitokoto['from']; ?>25. 每日诗词展示随机古诗词。<?php $poetry = json_decode(file_get_contents('https://v2.jinrishici.com/one.json'), true); echo $poetry['data']['content'].' —— '.$poetry['data']['origin']['dynasty'].' · '.$poetry['data']['origin']['author'].'《'.$poetry['data']['origin']['title'].'》'; ?>26. 随机猫咪图片每次刷新页面展示不同的猫咪图片。 27. 随机狗狗图片展示随机狗狗图片。<?php $dog = json_decode(file_get_contents('https://dog.ceo/api/breeds/image/random'), true); echo ' '; ?>28. 实时汇率转换展示不同货币之间的实时汇率。<?php function getExchangeRate($from, $to) { $url = "https://api.exchangerate-api.com/v4/latest/".$from; $data = json_decode(file_get_contents($url), true); return $data['rates'][$to]; } echo "1 USD = ".getExchangeRate('USD', 'CNY')." CNY"; ?>29. 历史上的今天展示历史上的今天发生的事件。<?php $date = date('m/d'); $url = "https://baike.baidu.com/cms/home/eventsOnHistory/".$date.".json"; $data = json_decode(file_get_contents($url), true); $events = $data[$date]; echo " 历史上的今天（".$date."） "; foreach(array_slice($events, 0, 5) as $event) { echo $event['year']."年: ".$event['title']." "; } ?>30. 每日NASA图片展示NASA每日天文图片。<?php $nasa = json_decode(file_get_contents('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY'), true); echo ' '.$nasa['title'].' '; echo ' '; echo ' '.$nasa['explanation'].' '; ?>31. 随机笑话展示随机笑话。<?php $joke = json_decode(file_get_contents('https://v2.jokeapi.dev/joke/Any?lang=zh'), true); if($joke['type'] == 'single') { echo $joke['joke']; } else { echo $joke['setup'].' '.$joke['delivery']; } ?>32. 实时疫情数据展示实时的新冠疫情数据。<?php $covid = json_decode(file_get_contents('https://lab.isaaclin.cn/nCoV/api/overall'), true); $data = $covid['results'][0]; echo "确诊: ".$data['confirmedCount']." "; echo "治愈: ".$data['curedCount']." "; echo "死亡: ".$data['deadCount']." "; echo "更新时间: ".date('Y-m-d H:i:s', $data['updateTime']/1000); ?>博客功能增强类API33. 代码高亮使用Prism或Highlight.js自动为代码块添加语法高亮。 34. 文章阅读时间估算根据文章字数自动计算阅读时间。<?php function readingTime($content) { $wordCount = mb_strlen(strip_tags($content), 'UTF-8'); $minutes = ceil($wordCount / 400); // 假设阅读速度为400字/分钟 return $minutes; } // 在文章页面调用 $readTime = readingTime($this->content); echo "预计阅读时间: ".$readTime." 分钟"; ?>35. IP地理位置显示显示访客的地理位置。<?php function getIpLocation($ip) { $url = "http://ip-api.com/json/".$ip."?lang=zh-CN"; $data = json_decode(file_get_contents($url), true); return $data['country'].' '.$data['city']; } $ip = $_SERVER['REMOTE_ADDR']; echo "您来自: ".getIpLocation($ip); ?>36. 实时在线人数统计显示当前在线访问人数。<?php // 需要在数据库中创建相关表来记录访问信息 function getOnlineCount() { // 过期时间设置为15分钟 $expire = 15 * 60; $now = time(); // 清理过期会话 $db = Typecho_Db::get(); $db->query($db->delete('table.online') ->where('time < ?', $now - $expire)); // 更新当前会话 $sid = session_id(); if($sid) { $exist = $db->fetchRow($db->select('sid')->from('table.online') ->where('sid = ?', $sid)); if($exist) { $db->query($db->update('table.online') ->rows(array('time' => $now)) ->where('sid = ?', $sid)); } else { $db->query($db->insert('table.online') ->rows(array('sid' => $sid, 'time' => $now))); } } // 获取在线人数 $online = $db->fetchObject($db->select('COUNT(*) AS count')->from('table.online'))->count; return $online; } echo "当前在线: ".getOnlineCount()." 人"; ?>37. 文章热度排行根据阅读量、评论数等显示热门文章。<?php function getHotPosts($limit = 5) { $db = Typecho_Db::get(); $posts = $db->fetchAll($db->select('cid', 'title', 'views') ->from('table.contents') ->where('type = ?', 'post') ->where('status = ?', 'publish') ->order('views', Typecho_Db::SORT_DESC) ->limit($limit)); return $posts; } echo " 热门文章 "; echo " "; foreach(getHotPosts() as $post) { echo ' $post['cid'])).'">'.$post['title'].'('.$post['views'].'阅读) '; } echo " "; ?>游戏与娱乐类API38. 随机二次元图片展示随机的二次元图片。<?php echo ' '; ?>39. 每日一句英语显示每日英语句子及翻译。<?php function getDailyEnglish() { $url = "https://api.vvhan.com/api/en"; $data = json_decode(file_get_contents($url), true); return $data; } $english = getDailyEnglish(); echo " 每日一句 "; echo " ".$english['data']['en']." "; echo " ".$english['data']['zh']." "; ?>40. 随机密码生成器生成随机安全密码。<?php function generateRandomPassword($length = 12) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+'; $password = ''; for($i = 0; $i < $length; $i++) { $password .= $chars[rand(0, strlen($chars) - 1)]; } return $password; } echo " 随机安全密码 "; echo "".generateRandomPassword().""; ?>41. 随机色彩搭配提供随机的色彩搭配方案。<?php function getRandomColorPalette() { $palette = array(); for($i = 0; i < 5; $i++) { $color = sprintf('#%06X', mt_rand(0, 0xFFFFFF)); $palette[] = $color; } return $palette; } $colors = getRandomColorPalette(); echo " 今日色彩搭配 "; echo " "; foreach($colors as $color) { echo " "; } echo " "; echo " "; foreach($colors as $color) { echo " ".$color." "; } echo " "; ?>42. 文章相关度推荐基于内容相似度推荐相关文章。<?php function getSimilarPosts($content, $exclude_cid, $limit = 5) { // 提取当前文章关键词 $keywords = preg_split('/\s+/', strip_tags($content)); $keywords = array_filter($keywords, function($word) { return mb_strlen($word, 'UTF-8') > 2; // 过滤短词 }); // 获取所有其他文章 $db = Typecho_Db::get(); $posts = $db->fetchAll($db->select('cid', 'title', 'text') ->from('table.contents') ->where('type = ?', 'post') ->where('status = ?', 'publish') ->where('cid <> ?', $exclude_cid)); // 计算相似度 $scores = array(); foreach($posts as $post) { $post_text = strip_tags($post['text']); $score = 0; foreach($keywords as $word) { $count = substr_count(strtolower($post_text), strtolower($word)); $score += $count; } $scores[$post['cid']] = $score; } // 按相似度排序 arsort($scores); // 返回前N篇相似度最高的文章 $result = array(); $count = 0; foreach($scores as $cid => $score) { if($score > 0) { foreach($posts as $post) { if($post['cid'] == $cid) { $result[] = array( 'cid' => $cid, 'title' => $post['title'], 'score' => $score ); $count++; break; } } if($count >= $limit) break; } } return $result; } // 在文章页面使用 $similar_posts = getSimilarPosts($this->content, $this->cid); echo " 相关推荐 "; echo " "; foreach($similar_posts as $post) { echo ' $post['cid'])).'">'.$post['title'].' '; } echo " "; ?>博客功能增强类API43. 代码运行结果展示展示代码片段的实际运行结果。<?php function executeCodeSnippet($code, $language) { $result = ''; switch($language) { case 'php': ob_start(); eval($code); $result = ob_get_clean(); break; case 'js': // 需要服务器支持Node.js或使用在线执行服务 // 此处使用前端JavaScript执行 $result = ' '; break; case 'python': // 需要服务器支持Python $tempFile = tempnam(sys_get_temp_dir(), 'py_'); file_put_contents($tempFile, $code); $result = shell_exec('python '.$tempFile); unlink($tempFile); break; } return $result; } $code = 'echo "Hello, World!";'; $result = executeCodeSnippet($code, 'php'); echo " 代码运行结果 "; echo " ".$result." "; ?>44. 文本情感分析分析文章内容的情感倾向。<?php function analyzeSentiment($text) { // 简单情感分析，实际可使用NLP API $positive_words = array('好', '喜欢', '优秀', '精彩', '美丽', '高兴', '开心'); $negative_words = array('差', '讨厌', '糟糕', '失望', '难过', '伤心', '消极'); $positive_count = 0; $negative_count = 0; foreach($positive_words as $word) { $positive_count += substr_count($text, $word); } foreach($negative_words as $word) { $negative_count += substr_count($text, $word); } if($positive_count > $negative_count) { return array('sentiment' => 'positive', 'score' => $positive_count - $negative_count); } else if($negative_count > $positive_count) { return array('sentiment' => 'negative', 'score' => $negative_count - $positive_count); } else { return array('sentiment' => 'neutral', 'score' => 0); } } // 在文章页面使用 $sentiment = analyzeSentiment($this->content); echo "文章情感倾向: "; if($sentiment['sentiment'] == 'positive') { echo "积极 (+".$sentiment['score'].")"; } else if($sentiment['sentiment'] == 'negative') { echo "消极 (-".$sentiment['score'].")"; } else { echo "中性"; } ?>45. 二维码生成API为文章或链接生成二维码。<?php function generateQRCode($text, $size = 150) { $url = "https://api.qrserver.com/v1/create-qr-code/?size=".$size."x".$size."&data=".urlencode($text); return $url; } // 生成当前文章的二维码 $current_url = Typecho_Common::url($this->permalink); $qrcode_url = generateQRCode($current_url); echo " 扫描分享文章 "; echo ' '; ?>46. 文章字数统计统计博客所有文章的总字数。<?php function getTotalWordCount() { $db = Typecho_Db::get(); $posts = $db->fetchAll($db->select('text') ->from('table.contents') ->where('type = ?', 'post') ->where('status = ?', 'publish')); $total = 0; foreach($posts as $post) { $total += mb_strlen(strip_tags($post['text']), 'UTF-8'); } return $total; } $total_words = getTotalWordCount(); echo "博客总字数: ".number_format($total_words)." 字"; ?>47. 访客地图展示访客来源地理位置的世界地图。<?php // 首先需要收集访客IP并存储地理位置信息 function logVisitorLocation() { $ip = $_SERVER['REMOTE_ADDR']; $data = json_decode(file_get_contents("http://ip-api.com/json/".$ip."?fields=country,lat,lon"), true); if($data && isset($data['country'])) { $db = Typecho_Db::get(); $db->query($db->insert('table.visitors') ->rows(array( 'ip' => $ip, 'country' => $data['country'], 'latitude' => $data['lat'], 'longitude' => $data['lon'], 'time' => time() ))); } } // 显示访客地图 function displayVisitorMap() { $db = Typecho_Db::get(); $visitors = $db->fetchAll($db->select('country', 'latitude', 'longitude', 'COUNT(*) as count') ->from('table.visitors') ->group('country')); $markers = array(); foreach($visitors as $visitor) { $markers[] = array( 'lat' => $visitor['latitude'], 'lng' => $visitor['longitude'], 'title' => $visitor['country'].' ('.$visitor['count'].')' ); } $markers_json = json_encode($markers); return ' '; } // 记录当前访客 logVisitorLocation(); // 显示地图 echo " 访客地图 "; echo displayVisitorMap(); ?>48. 随机迷你游戏在博客中嵌入简单的JS游戏。<?php // 随机选择一个迷你游戏 $games = array( // 猜数字游戏 ' 猜数字游戏 我已经想好了一个1-100之间的数字，你猜是多少？ 猜！ ', // 石头剪刀布 ' 石头剪刀布 石头 布 剪刀 战绩：0胜 0负 0平 ' ); // 随机选择一个游戏 $random_game = $games[array_rand($games)]; echo $random_game; ?>49. 每日单词学习每天展示一个英文单词及其解释。<?php function getDailyWord() { // 使用日期作为种子确保同一天显示相同单词 $date = date('Ymd'); srand(intval($date)); $words = array( array("word" => "ubiquitous", "part" => "adj.", "meaning" => "存在于无所不在的，普遍存在的"), array("word" => "ephemeral", "part" => "adj.", "meaning" => "短暂的，瞬息的"), array("word" => "serendipity", "part" => "n.", "meaning" => "意外发现珍奇事物的本领"), array("word" => "paradigm", "part" => "n.", "meaning" => "范例，模式"), array("word" => "eloquent", "part" => "adj.", "meaning" => "雄辩的，有口才的"), array("word" => "pragmatic", "part" => "adj.", "meaning" => "实用的，实际的"), array("word" => "resilience", "part" => "n.", "meaning" => "适应力，恢复力"), array("word" => "quintessential", "part" => "adj.", "meaning" => "精髓的，典型的"), array("word" => "juxtaposition", "part" => "n.", "meaning" => "并置，并列"), array("word" => "cacophony", "part" => "n.", "meaning" => "刺耳的声音，不和谐的声音") ); $index = rand(0, count($words) - 1); return $words[$index]; } $word = getDailyWord(); echo " 每日单词 "; echo " ".$word['word']." ".$word['part']." "; echo " ".$word['meaning']." "; ?>50. 随机表情包随机展示一个表情包图片。<?php function getRandomMeme() { // 表情包图片URL数组 $memes = array( "https://i.imgur.com/1NPi2kN.jpg", "https://i.imgur.com/GXPO2Yh.jpg", "https://i.imgur.com/C5YRB0x.jpg", "https://i.imgur.com/tZNOabj.jpg", "https://i.imgur.com/wJLRN9A.jpg" ); return $memes[array_rand($memes)]; } echo " 随机表情包 "; echo ' '; ?>51. 历史上的今天事件轮播使用轮播方式展示历史上的今天发生的多个事件。<?php function getTodayInHistory() { $date = date('m/d'); $url = "https://baike.baidu.com/cms/home/eventsOnHistory/".$date.".json"; $data = json_decode(file_get_contents($url), true); return $data[$date]; } $events = getTodayInHistory(); $events = array_slice($events, 0, 10); echo ' 历史上的今天 ('.date('m月d日').') '; foreach($events as $index => $event) { echo ' '.$event['year'].'年 '.$event['title'].' '; } echo ' < > '; ?>52. 热门影视推荐展示当前热门电影或电视剧。<?php function getHotMovies() { // 这里可以使用豆瓣API或其他影视API // 简单起见，这里使用静态数据 $movies = array( array( "title" => "流浪地球2", "rating" => "8.3", "image" => "https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2885941329.jpg", "url" => "https://movie.douban.com/subject/35267208/" ), array( "title" => "满江红", "rating" => "7.1", "image" => "https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2885955777.jpg", "url" => "https://movie.douban.com/subject/35766491/" ), array( "title" => "铃芽之旅", "rating" => "7.4", "image" => "https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2884182275.jpg", "url" => "https://movie.douban.com/subject/35371261/" ) ); return $movies; } $movies = getHotMovies(); echo " 热门影视推荐 "; echo " "; foreach($movies as $movie) { echo " "; echo " "; echo " ".$movie['title']." (".$movie['rating']."⭐) "; echo " "; } echo " "; ?>53. 随机美食食谱展示随机美食食谱。<?php function getRandomRecipe() { $url = "https://www.themealdb.com/api/json/v1/1/random.php"; $data = json_decode(file_get_contents($url), true); return $data['meals'][0]; } $recipe = getRandomRecipe(); echo " 今日美食推荐: ".$recipe['strMeal']." "; echo ' '; echo " 材料: "; for($i=1; $i<=20; $i++) { if(!empty($recipe['strIngredient'.$i])) { echo " ".$recipe['strIngredient'.$i]." - ".$recipe['strMeasure'.$i]." "; } } echo " "; ?>54. 每日星座运势根据星座显示每日运势。<?php function getHoroscope($sign) { $url = "https://api.vvhan.com/api/horoscope?type=".urlencode($sign); $data = json_decode(file_get_contents($url), true); return $data['data']; } $sign = "双鱼座"; // 可以改为任何星座 $horoscope = getHoroscope($sign); echo " ".$sign."今日运势 "; echo "综合指数: ".$horoscope['index']['all']." "; echo "幸运颜色: ".$horoscope['fortuneColor']." "; echo "幸运数字: ".$horoscope['luckynumber']." "; echo "今日运势: ".$horoscope['fortunetext']." "; ?>55. 每日推荐歌曲每天随机推荐一首歌曲。<?php function getDailyRecommendedSong() { // 使用种子确保同一天返回同一首歌 $date = date('Ymd'); srand(intval($date)); // 歌曲库（可以扩展） $songs = array( array("name" => "Shape of You", "artist" => "Ed Sheeran", "id" => "7qiZfU4dY1lWllzX7mPBI3"), array("name" => "Dance Monkey", "artist" => "Tones and I", "id" => "1rgnBhdG2JDFTbYkYRZAku"), array("name" => "Blinding Lights", "artist" => "The Weeknd", "id" => "0VjIjW4GlUZAMYd2vXMi3b"), array("name" => "Someone You Loved", "artist" => "Lewis Capaldi", "id" => "7qEHsqek33rTcFNT9PFqLf"), array("name" => "Bad Guy", "artist" => "Billie Eilish", "id" => "2Fxmhks0bxGSBdJ92vM42m") ); $index = rand(0, count($songs) - 1); return $songs[$index]; } $song = getDailyRecommendedSong(); echo " 今日推荐歌曲 "; echo $song['name']." - ".$song['artist']." "; echo ' '; ?>56. 倒计时日期提醒展示距离特定日期的倒计时。<?php function getDaysUntil($date, $event_name) { $now = time(); $event_time = strtotime($date); $days_left = ceil(($event_time - $now) / 86400); return array( 'name' => $event_name, 'date' => $date, 'days' => $days_left ); } $events = array( getDaysUntil('2023-12-25', '圣诞节'), getDaysUntil('2024-01-01', '元旦'), getDaysUntil('2024-02-10', '春节'), // 根据实际日期调整 ); echo " 节日倒计时 "; echo " "; foreach($events as $event) { if($event['days'] > 0) { echo " 距离 ".$event['name']." 还有 ".$event['days']." 天 "; } else if($event['days'] == 0) { echo " 今天是 ".$event['name']."！ "; } } echo " "; ?>57. 随机壁纸API每次访问显示不同的精美壁纸。<?php echo ' '; ?>58. Minecraft服务器状态显示您的Minecraft服务器状态。<?php function getMinecraftServerStatus($server) { $url = "https://api.mcsrvstat.us/2/".$server; $data = json_decode(file_get_contents($url), true); return $data; } $server = 'mc.hypixel.net'; // 服务器地址 $status = getMinecraftServerStatus($server); echo " Minecraft服务器状态 "; echo "服务器: ".$server." "; echo "状态: ".($status['online'] ? '在线' : '离线')." "; if($status['online']) { echo "当前玩家: ".$status['players']['online']."/".$status['players']['max']." "; echo "版本: ".$status['version']." "; } ?>59. 随机好看的渐变背景为您的博客添加随机生成的渐变背景。 60. 随机英文名言展示随机英文名言。<?php $quote = json_decode(file_get_contents('https://api.quotable.io/random'), true); echo '"'.$quote['content'].'" — '.$quote['author']; ?>集成方法要将这些API集成到Typecho博客中，您可以：创建插件：将API功能封装成Typecho插件，这样可以方便地在后台启用/禁用。修改主题文件：直接在主题的sidebar.php、footer.php或其他模板文件中添加相关代码。创建独立页面：创建一个专门用于展示API数据的独立页面。使用Widget：创建自定义Widget并在需要的位置调用。注意事项大多数API调用需要使用PHP的file_get_contents函数，请确保您的服务器已启用allow_url_fopen。某些API可能需要注册并获取API密钥，请按照相应API的官方文档进行操作。考虑使用缓存机制减少API请求次数，提高博客加载速度。注意API的使用限制，避免过度调用导致被封禁。API 汇总列表个人社交账号APIQQ等级显示API - 展示QQ等级和成长值信息QQ音乐状态API - 显示正在听的音乐哔哩哔哩UP主信息API - 展示粉丝数、视频数、播放量等数据网易云音乐状态API - 展示最近听的歌曲或音乐播放器GitHub贡献统计API - 展示代码贡献图表豆瓣读书/电影API - 展示最近阅读的书籍或观看的电影Instagram基本数据API - 展示Instagram账号基本信息Discord服务器状态API - 展示服务器状态和成员数Telegram频道订阅数API - 展示Telegram频道的订阅人数小红书笔记展示API - 展示最近的小红书笔记王者荣耀战绩API - 展示游戏数据和战绩Steam游戏状态API - 展示最近玩的游戏和时长热搜榜单API微博热搜API - 展示微博实时热搜榜单知乎热榜API - 展示知乎热门话题抖音热门话题API - 展示抖音热门话题和标签百度热搜榜API - 展示百度搜索热榜内容腾讯新闻热点API - 展示腾讯新闻热点话题哔哩哔哩热搜API - 展示B站当前热搜词即刻社区热门API - 展示即刻社区热门话题搜狗热搜榜API - 展示搜狗搜索热榜内容360热搜榜API - 展示360搜索热榜内容热搜聚合展示API - 整合多平台热搜于一页工具类API天气API - 根据位置显示天气信息一言API - 随机展示一句名言每日诗词API - 展示随机古诗词随机猫咪图片API - 每次刷新显示不同猫咪图片随机狗狗图片API - 展示随机狗狗图片实时汇率转换API - 展示不同货币之间的实时汇率历史上的今天API - 展示历史上的今天发生的事件每日NASA图片API - 展示NASA每日天文图片随机笑话API - 展示随机笑话实时疫情数据API - 展示实时的新冠疫情数据博客功能增强API代码高亮API - 为代码块添加语法高亮文章阅读时间估算API - 根据字数计算阅读时间IP地理位置显示API - 显示访客的地理位置实时在线人数统计API - 显示当前在线访问人数文章热度排行API - 显示热门文章随机二次元图片API - 展示随机的二次元图片每日一句英语API - 显示每日英语句子及翻译随机密码生成器API - 生成随机安全密码随机色彩搭配API - 提供随机的色彩搭配方案文章相关度推荐API - 推荐相关文章代码运行结果展示API - 展示代码片段的实际运行结果文本情感分析API - 分析文章内容的情感倾向二维码生成API - 为文章或链接生成二维码文章字数统计API - 统计博客所有文章的总字数访客地图API - 展示访客来源地理位置的世界地图游戏与娱乐类API随机迷你游戏API - 在博客中嵌入简单的JS游戏每日单词学习API - 每天展示一个英文单词及其解释随机表情包API - 随机展示一个表情包图片历史上的今天事件轮播API - 轮播展示历史上的今天事件热门影视推荐API - 展示当前热门电影或电视剧随机美食食谱API - 展示随机美食食谱每日星座运势API - 根据星座显示每日运势每日推荐歌曲API - 每天随机推荐一首歌曲倒计时日期提醒API - 展示距离特定日期的倒计时随机壁纸API - 每次访问显示不同的精美壁纸Minecraft服务器状态API - 显示Minecraft服务器状态随机好看的渐变背景API - 为博客添加随机生成的渐变背景随机英文名言API - 展示随机英文名言结语通过集成这些丰富多样的API，你可以为Typecho博客增添更多动态和个性化元素，提升用户体验和互动性。选择适合的博客主题和风格的API进行集成，让你的博客更加与众不同！记住，合理使用API缓存机制可以有效减少服务器压力和提高页面加载速度。希望本文介绍的这60个API能为您的博客增添更多精彩内容！有什么问题可在评论区留言，觉得有趣也可以留言！
WeFootStep：微信运动步数同步到Typecho博客的完整解决方案
1. 项目概述WeFootStep 是一个 Typecho 插件，它能够将用户的微信运动步数同步到您的 Typecho 博客并在前端展示。本项目由 Typecho 插件（后端）和微信小程序（前端）两部分组成。通过这两部分的协同工作，实现了微信运动数据的采集、传输、存储和展示的完整流程。1.1 功能特点步数同步：通过微信小程序安全地获取并同步微信运动步数。数据统计：在导航栏提供今日步数、周/月平均、累计步数和最佳纪录等统计功能。历史图表：以图表形式直观展示历史步数变化趋势。后台管理：支持在后台查看和管理已同步的步数记录。高度自定义：支持将步数模块显示在侧边栏、页脚或主题任意自定义位置。安全可靠：严格遵循微信官方加密和验证机制，确保数据安全。1.2 技术架构2. 安装与配置2.1 插件安装从 GitHub 下载 WeFootStep 项目的 ZIP 压缩包并解压。将解压后得到的 WeFootStep 文件夹上传到您 Typecho 博客的 /usr/plugins/ 目录下。在 Typecho 后台管理界面的“控制台” > “插件”中，找到 WeFootStep 并启用它。2.2 插件配置启用插件后，点击“设置”进入配置页面：微信小程序 AppID / AppSecret：（必填） 填写您的小程序 AppID 和 AppSecret。显示样式与位置：选择步数在前台的显示样式（如进度条、文本）和位置（侧边栏、页脚或自定义）。卸载时是否删除数据：建议保留数据，除非您确定不再需要历史步数记录。2.3 微信小程序配置WeFootStep 目录中包含的 miniprogram 文件夹是小程序的前端项目。创建小程序：如果您还没有小程序，请先在 微信公众平台 注册并创建一个。获取凭证：在“开发” > “开发管理” > “开发设置”中，找到并记录您的 AppID 和 AppSecret。开启接口权限：在“开发” > “开发管理” > “接口设置”中，务必手动开启“微信运动步数”接口权限。配置服务器域名：在“开发” > “开发管理” > “开发设置” > “服务器域名”中，将您的博客域名添加到 request合法域名 列表中。这是小程序能成功请求到您博客的必要前提。配置小程序代码：使用“微信开发者工具”导入 miniprogram 文件夹。打开 pages/index/index.js 文件，将第 8 行的 url 修改为您博客的同步接口地址。地址必须是公网可访问的 HTTPS 地址。//... url: 'https://博客地址/index.php/action/wefootstep?do=sync', //... //如果这个有问题，可以改成https://博客地址/index.php/action/WeFootStep?do=sync上传和预览：在微信开发者工具中，点击“上传”将代码上传到微信后台，然后设置为“体验版”供自己或朋友测试使用。3. 使用方法3.1 博客端数据展示自动显示：如果您在插件设置中选择了“侧边栏”或“页脚”，插件会自动在相应位置显示。自定义位置：如果选择“自定义位置”，您需要在主题的模板文件（如 sidebar.php, footer.php 或 header.php）中，在您希望展示步数的地方添加以下调用代码：<?php WeFootStep_Plugin::render(); ?>或者使用 HTML 注释作为标记（适用于不方便修改 PHP 的场景）： 导航栏统计：导航栏的步数统计功能是自动集成的，启用插件后即可在前台看到效果。3.2 小程序端数据同步小程序支持两种同步方式：自动同步：每次打开小程序时，它会自动尝试从微信服务器获取最新步数并同步到您的博客。手动同步：可以随时点击界面上的“同步步数”按钮来触发一次同步。成功同步后，小程序界面会显示“同步成功”的提示，同时您的博客前端数据也会刷新。4. 功能与接口4.1 后台管理插件在 Typecho 后台的“独立页面”菜单旁增加了一个“微信步数”管理面板，您可以在此：查看所有已同步的步数历史记录。对特定日期的步数记录进行编辑或删除。4.2 API 接口WeFootStep 提供了两个核心的 API 接口：POST /index.php/action/wefootstep?do=sync：小程序专用接口，用于接收小程序发送的加密步数数据并执行同步操作。GET /index.php/action/wefootstep?do=getStepData：前端专用接口，用于为导航栏的统计模块提供格式化的 JSON 数据。5. 主题集成：以 Handsome 主题为例本教程将指导您如何直接修改 Handsome 主题，在顶部导航栏中添加一个交互式的微信运动步数统计面板，契合主题所有颜色且支持夜间模式，具体参考本站顶部。此方法比创建新文件更稳定。5.1 第一步：修改 headnav.php 文件定位文件：用编辑器打开 Handsome 主题的 headnav.php 文件。该文件位于主题目录的 component 文件夹下：/usr/themes/handsome/component/headnav.php。添加HTML面板：在文件中搜索 class="dropdown pos-stc，找到如下图所示的列表项 ( )。紧跟在这整个 标签的后面，添加以下代码，这是步数统计面板的 HTML 结构：[collapse status="false" title="展开查看代码"] <polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline> <polyline points="17 6 23 6 23 12"></polyline> 步数统计 今日步数 本周平均 本月平均 累计步数 最佳记录 最差记录 步数趋势 加载中... [/collapse]添加依赖库和脚本：滚动到 headnav.php 文件的最底部，在 标签的正上方，粘贴以下所有代码。这部分代码包含了面板运行所需的 ECharts、FontAwesome 图标库以及数据处理和图表渲染的 JavaScript 脚本。[collapse status="false" title="展开查看代码"] [/collapse]代码说明：这段代码直接在页面内查询数据库并渲染数据，不依赖外部AJAX请求，性能更好且更稳定。同时，它会自动检查 WeFootStep 插件是否启用，如果未启用，会在面板中给出提示，避免网站出错。6. 同步问题排查指南同步失败是初期配置时最常见的问题。请遵循以下步骤进行排查：6.1 第一步：小程序端自查打开微信开发者工具，在“控制台(Console)”中查看是否有错误信息。控制台错误信息可能原因解决方案request:fail url not in domain list请求的博客地址未被添加到小程序的合法域名中。登录微信公众平台，在“开发” > “开发设置” > “服务器域名”中，将您的博客域名（如 https.blog.example.com）添加到 request合法域名。request:fail timeout请求超时。可能是您的服务器响应慢，或网络连接不佳。检查服务器性能和网络状况。确保博客地址可以从公网快速访问。[AppID] is not a valid official account插件设置中的 AppID 或 AppSecret 填写错误。仔细核对插件设置中的 AppID 和 AppSecret 是否与微信公众平台上显示的完全一致，注意不要有空格。其他网络错误 (如 404, 500)小程序发出的请求未能被服务器正确处理。这是服务器端的问题，请跳转到 第二步 进行排查。6.2 第二步：服务器端排查如果小程序端没有明确的配置错误，问题通常出在服务器或 Typecho 插件端。问题现象可能原因解决方案请求返回 404 Not Found1. Typecho 未能正确处理 Action 路由。2. 服务器（Nginx/Apache）的伪静态/重写规则配置不正确。1. 确认插件已正确启用。2. 检查 Typecho “设置” > “永久链接”是否启用，并确认服务器已配置了伪静态规则。对于 Nginx，请确保有类似 if (!-e $request_filename) {rewrite ^(.*)$ /index.php$1 last;} 的配置。 3.检查后台的地址重写是否开启。4.检查文件名是否正确（大小写）。请求返回 500 Internal Server ErrorPHP 代码执行出错。查看服务器的 PHP 错误日志。这是定位问题的最有效方法。错误日志会明确指出是哪个文件的哪一行代码出了问题。请求有响应但内容为空，或提示“解密失败”session_key 获取失败或解密过程出错。1. 再次确认 AppID 和 AppSecret 绝对正确。2. 确保您的服务器可以正常访问微信的 API 服务器 (api.weixin.qq.com)，没有被防火墙或网络策略阻挡。数据同步了，但前端不显示1. 博客或 CDN 缓存导致页面未更新。2. 插件显示位置配置不正确或主题不兼容。1. 清理您博客、CDN 或浏览器的缓存。2. 确认插件设置中的显示位置。如果使用“自定义位置”，请检查调用代码是否已正确添加到主题文件中。特别检查伪静态，地址重写，无误后可以删除插件重新上传并测试！！！如果以上步骤均无法解决问题，可以通过qq联系我，并附上你在排查过程中收集到的错误信息。7. 工作原理详解7.1 数据同步流程用户授权：用户在微信小程序中授权允许获取微信运动数据。获取凭证：小程序通过 wx.login() API 获取微信用户的临时登录凭证 code。获取步数：小程序通过 wx.getWeRunData() API 获取已加密的微信运动数据 encryptedData 和初始向量 iv。发起请求：小程序将 code、encryptedData 和 iv 一同发送到您在插件中配置的 Typecho 博客后端 API 接口。服务端验证：Typecho 插件后端接收到请求后，将 code、AppID 和 AppSecret 发送给微信认证服务器，换取用户的 session_key。使用 session_key 和 iv 对 encryptedData 进行 AES-128-CBC 解密。数据入库：解密成功后，从中提取步数和日期，并验证数据水印。验证通过后，将步数数据存入 Typecho 数据库的 typecho_we_foot_step 表中。前端展示：博客前端通过插件提供的函数或 API 接口，从数据库读取步数数据，并根据您的配置进行展示和统计。7.2 安全机制数据加密：微信运动数据全程使用 AES-128-CBC 算法加密传输。身份验证：使用有时效性的临时登录凭证 code 和 session_key 机制，确保请求来自合法用户和小程序。数据水印：解密后的数据包含小程序 appid 水印，用于校验数据的完整性和来源，有效防止数据被篡改或伪造。8. 技术实现概览组件文件及功能插件核心Plugin.php: 插件主文件，负责安装/卸载、路由注册、钩子绑定、后台菜单和前端资源加载。Action.php: API 请求处理器，负责数据同步和数据查询接口的逻辑。Widget.php: 前端渲染器，负责生成步数模块的 HTML 和提供给主题调用的公共方法。manage.php: 后台管理页面，用于展示和管理步数列表。edit.php: 后台编辑页面，用于修改单条步数记录。小程序miniprogram/: 包含小程序的所有前端代码 (app.js, pages/, app.json 等)。9. 其他9.1 联系与支持如果有什么使用问题或者改进建议，可在此评论区留言或加入QQ群825038573Github下载地址：[hide]https://github.com/BXCQ/WeFootStep[/hide]本文档由璇编写，最后更新于2025年12月
背景随着博客的发展，我觉的评论通知需要特别提示，于是就找到了一个插件CommentNotifier，能完美适配1.2.1，且符合我的使用需求，我使用的是QQ邮箱通知，其他的也未进行测试。插件介绍Typecho博客评论邮件提醒，支持异步回调（异步回调优点就是不影响博客评论提交速度），支持编辑邮件模板，支持第三方开发邮件模板，发信方式支持SMTP与阿里云邮箱推送，支持表情回调（需要主题支持）插件地址：https://github.com/jrotty/CommentNotifier安装教程下载后将压缩包解压到 /usr/plugins目录文件夹名改为CommentNotifier登录管理后台，激活插件配置插件 填写SMTP参数/阿里云邮箱推送参数使用教程以QQ邮箱为例设置插件发件人昵称可填写自己的博客网站名，注意无法解析符号QQ邮箱SMTP服务配置QQ邮箱的安全设置中，生成授权码作为插件中的SMTP登录密码模板更换在控制台→评论邮件模板里可以切换以及编辑模板template文件夹里存放的就是邮件发信模板，可以参考内置的几个模板来写属于自己的邮件模板，当然也可以在后台直接修改默认模板来达到邮件美化的作用。