从服务器选择、环境搭建到 Typecho/WordPress 部署与主题配置的全流程建站清单。
对自己的博客做了几下 Apache Bench 测试，出乎意料
昨晚想着给自己的网站做些安全与压力方面的测试，今天就动手实践了一下。首先下载并准备 Apache Bench（ab 压力测试工具），我用的是 win11 版。在解压后的 Apache 的 bin 目录中打开 cmd。简单看了下常用参数后开始测试，一共跑了三组：abs.exe -n 100 -c 5 https://blog.ybyq.wang/abs.exe -n 10000 -c 50 https://blog.ybyq.wang/abs.exe -n 20000 -c 50 https://blog.ybyq.wang/参数说明（简要）-n: 总请求数（Total requests）。-c: 并发数（Concurrency），同一时刻发起的并行请求数。并发设置过大容易触发 CDN 限速/封顶或放大带宽消耗。最后一轮测试时，网站突然无法访问。打开 CDN 的资源监控一看，果然触发了我设置的带宽封顶限制。手动解除后，访问就恢复了。至于雷池 WAF，一条都没拦截；部署雷池的云服务器负载也基本没有波动。这次测试大概消耗了 CDN 约 2 GB 流量。下次我先关掉 CDN，再猛猛测几轮看看源站的真实表现。
前言在自定义主题的某天，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" /]
零成本建站：将 Windows 电脑变身为个人网站服务器
一、环境准备1.1 所需软件与资源XAMPP: 集成了 Apache、MariaDB、PHP 和 Perl 的跨平台 Web 开发环境Typecho: 轻量级开源博客程序，基于 PHP公网访问方案: 内网穿透工具（花生壳、frp）或固定 IP 方案文本编辑器: 用于修改配置文件（如 VS Code、Notepad++ 等）二、本地服务器配置2.1 XAMPP 安装与初始化从Apache Friends官网下载适合您操作系统的XAMPP安装包执行安装程序，建议安装路径避开系统目录（如C:\Program Files），本指南使用D:\xampp安装完成后，启动XAMPP控制面板依次启动Apache和MySQL服务如果 MySQL 无法启动，请参见文末“附录A：常见问题与排查”。在浏览器中访问http://localhost验证服务器运行状态2.2 数据库创建Typecho 需要一个 MySQL 数据库来存储内容和配置信息：访问http://localhost/phpmyadmin/进入数据库管理界面点击左侧导航栏中的"新建"选项在表单中输入数据库名称（如typecho_db）选择字符集为utf8mb4_general_ci（支持完整的Unicode字符，包括Emoji）点击"创建"按钮完成数据库初始化安全提示: 在生产环境中，应为数据库创建专用用户并限制权限，而非使用默认的root账户。可在phpMyAdmin的"用户账户"→"添加用户"中完成此操作。三、Typecho博客部署3.1 程序文件安装从Typecho官方网站下载最新版本的程序包解压下载的压缩文件将解压后的所有文件复制到 XAMPP 的网站目录中：D:\xampp\htdocs\typecho\3.2 执行安装向导在浏览器中访问http://localhost/typecho/启动安装程序在欢迎页面点击"我准备好了，开始下一步"在数据库配置页面填写以下信息：数据库适配器: MySQL数据库地址: localhost数据库用户名: root（XAMPP 默认）数据库密码: （默认为空）数据库名: typecho_db（您在上一步创建的数据库）表前缀: typecho_（可保持默认）点击"确认，开始安装"按钮在站点配置页面设置管理员账户信息：站点名称站点描述管理员用户名管理员密码管理员邮箱完成安装后，您可以通过以下地址访问：博客前台：http://localhost/typecho/管理后台：http://localhost/typecho/admin/至此，本地博客系统已完成基础配置，可以进行文章发布和管理。3.3 Apache 网站根目录配置关键步骤: 为了让访问您的域名或IP时直接显示博客内容（而非XAMPP默认欢迎页），需要修改Apache配置，将根目录指向Typecho文件夹：在XAMPP控制面板中停止Apache服务备份原始配置文件：D:\xampp\apache\conf\httpd.conf使用文本编辑器打开httpd.conf文件搜索DocumentRoot并找到以下配置：# 原配置 DocumentRoot "D:/xampp/htdocs" <Directory "D:/xampp/htdocs">修改为指向Typecho目录：# 新配置 DocumentRoot "D:/xampp/htdocs/typecho" <Directory "D:/xampp/htdocs/typecho">保存文件并在XAMPP控制面板中重新启动Apache服务3.4 Typecho 站点 URL 配置当您通过任何域名或 IP 访问博客时，需要确保 Typecho 知道自己的正确地址，否则样式和链接可能无法正常工作：通过http://localhost/admin/访问博客后台在左侧菜单中选择"设置"→"基本设置"找到"站点地址"选项根据您的访问方式，将其设置为对应的地址：本地访问：http://localhost公网域名访问：http://您的域名（如http://myblog.com或http://1ko0641539qv6.vicp.fun）注意: URL 末尾不要添加斜杠/，以避免路径解析问题。点击页面底部的"保存设置"按钮刷新博客首页，此时所有样式和功能应正常显示四、实现公网访问的多种方案本章介绍几种将您的本地服务器发布到公网的方法，您可以根据自身条件选择最适合的方案。如需了解各种内网穿透工具的详细对比，请参阅我们的专题文章《内网穿透工具全面对比：选择最适合您的方案》。4.1 方案一：花生壳内网穿透（适合动态IP）内网穿透允许外部网络通过特定域名访问您本地的 Web 服务：注册并登录花生壳账号，下载安装花生壳客户端在客户端或官网控制台中添加新的映射配置映射参数：应用类型: 选择HTTP或网站外网域名: 使用花生壳提供的免费域名（如1ko0641539qv6.vicp.fun）内网主机: 填写运行XAMPP的计算机内网IP地址内网端口: 80（Apache默认端口）完成映射后，通过花生壳提供的域名访问您的网站登录Typecho后台，将站点地址更新为花生壳域名（参见3.4节）4.2 方案二：固定 IP 直接访问（适合有固定公网 IP）如果您的宽带有固定公网IP（如部分商业宽带或企业专线），可以直接通过IP访问：查询您的公网IP（可通过搜索"我的IP"获取）在路由器上设置端口转发：外部端口：80（HTTP标准端口）内部IP：您运行XAMPP的电脑内网IP内部端口：80配置防火墙，允许80端口的入站连接通过http://您的公网IP直接访问您的网站登录Typecho后台，将站点地址更新为您的公网IP地址（参见3.4节）4.3 方案三：使用自己的域名4.3.1 标准方法（需要路由器端口转发）如果您已经拥有自己的域名，希望用它替代花生壳提供的域名，可以采用DDNS（动态域名解析）技术：购买域名：如果还没有域名，可以在阿里云、腾讯云、Namecheap等注册商购买设置DNS解析：登录域名提供商的控制面板添加一条A记录，指向您的公网IP（如果是动态IP，先填写当前IP）如果希望使用二级域名，可以添加如blog.yourdomain.com的记录配置DDNS客户端（针对动态IP）：方法一：使用域名提供商的DDNS服务阿里云提供"云解析DNS"的DDNS功能腾讯云提供"DNSPod"服务按照提供商说明配置自动更新客户端方法二：使用第三方DDNS服务注册No-IP、DynDNS等DDNS服务提供商下载并安装其客户端软件配置客户端，绑定您的域名和当前IP客户端会定期检测IP变化并自动更新DNS记录端口转发设置：在路由器上设置80端口转发到运行XAMPP的电脑（同4.2节）更新Typecho配置：登录Typecho后台将站点地址更新为您的自定义域名，如http://blog.yourdomain.com或http://yourdomain.com提示：如果您使用自己的域名，建议同时配置HTTPS证书以提高安全性。可以使用Let's Encrypt免费证书，并在Apache中配置SSL。4.3.2 无法设置端口转发时的替代方案如果您无法访问路由器或光猫后台设置（如运营商锁定了管理权限），仍有以下几种方式可以使用自己的域名：结合花生壳内网穿透使用自定义域名：先按照4.1节设置花生壳内网穿透在您的域名DNS设置中添加CNAME记录，指向花生壳提供的域名记录类型：CNAME主机记录：www 或 blog（您想要的二级域名前缀）记录值：您的花生壳域名（如1ko0641539qv6.vicp.fun）等待DNS生效（通常需要几分钟到几小时）此后可通过您自己的域名访问网站使用非标准端口：修改XAMPP中Apache的监听端口为非80端口（如8080）编辑httpd.conf文件，找到Listen 80并改为Listen 8080重启Apache服务在花生壳中映射此非标准端口通过http://您的域名:8080访问网站使用Cloudflare Tunnel（高级方案）：注册Cloudflare账户并将您的域名添加到Cloudflare下载并安装cloudflared客户端创建一个隧道并配置本地服务此方案完全绕过了路由器端口转发的需求，且提供免费SSL使用ngrok等第三方隧道服务：注册ngrok账户（https://ngrok.com）下载并安装ngrok客户端使用命令ngrok http 80创建临时隧道升级到付费账户可绑定自定义域名类似服务还有Telebit、Pagekite等重要提示：对于无法设置端口转发的情况，方案1（花生壳+CNAME）是最简单可行的解决方案，既可以使用自己的域名，又不需要修改路由器设置。4.4 方案四：使用 frp 开源内网穿透（高性能方案）如果您对内网穿透性能有更高要求，或者希望完全掌控穿透过程，可以考虑使用开源的frp工具。frp提供了比花生壳更高的性能和更灵活的配置选项，但需要您拥有一台具有公网IP的服务器。关于 frp 的详细配置与实践，请参阅[post cid="855" /]提示：自 v0.51.0 起，frp 的配置文件格式由 INI 切换为 TOML；请确保服务端与客户端版本匹配，配置语法请参考上文链接。4.5 方案五：反向代理与 CDN 加速（高级方案）对于需要更好性能和稳定性的用户，可以考虑结合CDN服务：注册Cloudflare等CDN服务将您的域名DNS服务器指向CDN提供商在CDN控制面板中设置源站为您的公网IP或花生壳域名启用CDN的"始终在线"功能，即使您的家庭服务器暂时离线，网站仍可访问利用CDN提供的免费SSL证书启用HTTPS五、总结与展望本指南提供了将 Windows 电脑变成个人网站服务器的完整方案：家用电脑作为服务器：零成本入门方案，适合个人学习和小型项目优点：无需额外费用，完全掌控服务器环境缺点：依赖家庭宽带和电脑稳定性，需要电脑保持开机状态内网穿透技术：多种方案可供选择，从简单易用的花生壳到高性能的frp根据您的技术水平和性能需求选择合适的内网穿透工具考虑带宽、稳定性和安全性等因素通过本指南的实践，您不仅可以建立自己的博客网站，还能深入理解 Web 服务器、数据库、网络和内容管理系统的工作原理。这些知识和技能将为您未来的Web开发项目奠定坚实基础。后续可考虑的优化方向包括：CDN加速配置、网站安全加固、数据备份策略、SEO优化以及主题与插件的定制开发等。附录 A：常见问题与排查MySQL 无法启动（XAMPP）可能原因：3306端口被占用、数据目录损坏、权限不足快速排查：在 XAMPP 控制面板点击 MySQL 右侧“Logs”查看错误日志使用netstat -ano | findstr 3306检查端口占用并结束冲突进程或修改端口确认D:\xampp\mysql\data权限正常；如崩溃恢复失败，可备份后重建数据目录Apache 无法启动可能原因：80或443端口被占用（常见：IIS、占用80的其他软件）快速排查：使用netstat -ano | findstr :80与:443定位占用进程在httpd.conf中临时改为Listen 8080验证；或关闭冲突程序phpMyAdmin 登录失败（本地默认）解决：XAMPP 默认用户为root、默认无密码；若已设置密码，请同步更新config.inc.php站点访问样式错乱可能原因：Typecho“站点地址”未与实际访问地址一致解决：参见正文“3.4 Typecho站点URL配置”，更新为实际访问域名或IPWindows 防火墙拦截处理：在“高级防火墙”中为 Apache（httpd.exe）与 MySQL（mysqld.exe）添加入站规则；或短暂关闭进行验证数据安全与备份建议：开启定期数据库备份（导出SQL）与站点目录备份；重要调整前先备份配置文件
使用frp搭建高性能内网穿透服务（结合XAMPP和宝塔面板）
前言这篇教程是用XAMPP已经在本地搭建好了Typecho博客网站，除了用花生壳，也可以用更好的frp做内网穿透，此篇教程就是使用frp搭建高性能内网穿透服务。一、准备工作1.1 所需资源一台具有公网IP的服务器（已安装宝塔面板）域名（可选，用于通过域名访问服务）本地环境（运行Web服务的计算机）1.2 了解frp工作原理frp分为服务端（frps）和客户端（frpc）两部分：服务端（frps）：部署在公网服务器上客户端（frpc）：部署在本地需要穿透的设备上1.3 域名与DNS解析（指向同一IP）将要对外提供访问的域名（如：www.example.com）添加一条 A 记录，指向 frps 所在服务器的公网IP（即宝塔所在服务器IP）。同一台服务器可承载多个域名，均需指向同一公网IP。常用验证方式：在本地终端执行：nslookup www.example.com 或 ping www.example.com，确认解析到的IP与服务器公网IP一致。解析生效后再进行后续的Nginx配置与证书申请。二、服务器端配置2.1 安装与版本说明首选方式（推荐）：在宝塔软件商店中安装并开启「frp管理器 1.1」。该管理器可一键完成服务端安装、版本选择、配置与启动，适合快速部署与可视化管理。可选方式（手动）：从 GitHub 发布页下载对应平台的 frp 二进制包，解压并通过 systemd 方式托管运行（适合有Linux基础的用户）。重要版本提示：自 v0.51.0 起，frp 配置文件已由 INI 切换为 TOML 格式。请确保服务端与客户端均使用 TOML，并注意版本匹配。2.2 配置frps服务端进行服务配置，这些是默认的端口，需要在防火墙中打开进行WEB服务配置，设置好用户名和密码，此默认端口也要打开进行token设置最后，开启服务，并下载此客户端frp注意事项：kcpBindPort 不应与 bindPort 相同，建议分配为不同端口（如 bindPort=15443，kcpBindPort=15444）。vhostHTTPPort/vhostHTTPSPort 为 frps 内部的虚拟主机端口，通常通过 Nginx 80/443 反向代理至此端口对外提供服务。auth.token 为客户端鉴权必须一致的密钥，修改后需重启 frps 并同步更新客户端。2.4 配置防火墙确保服务器防火墙开放了必要的端口：# 根据frps.toml中的配置开放以下端口 firewall-cmd --permanent --add-port=15443/tcp # bindPort firewall-cmd --permanent --add-port=15444/udp # kcpBindPort firewall-cmd --permanent --add-port=18080/tcp # vhostHTTPPort firewall-cmd --permanent --add-port=18443/tcp # vhostHTTPSPort firewall-cmd --permanent --add-port=16337/tcp # tcpmuxHTTPConnectPort firewall-cmd --permanent --add-port=7001/tcp # webServer端口 firewall-cmd --permanent --add-port=80/tcp # HTTP标准端口 firewall-cmd --reload三、客户端配置3.1 下载frp客户端在您本地计算机上，下载与服务器端相同版本的frp：可以下载宝塔中给出的0.53.2版本Windows用户：从GitHub下载对应的64位 X86Windows版本，本教程为第一个下载后解压到一个方便的位置，如D:\frp3.2 配置frpc客户端（TOML格式）在frp文件夹中创建/编辑frpc.toml文件（注意：不是frpc.ini），内容如下：# frpc.toml # 基本配置 - 连接到服务器 serverAddr = "服务器公网IP" serverPort = 15443 # 必须与frps.toml中的bindPort一致 # 认证token，必须与服务器端一致 auth.token = "与服务器端相同的令牌" # 代理规则配置 proxies name = "web-site" # 名称可自定义 type = "http" # 协议类型 localIP = "127.0.0.1" # 本地服务IP localPort = 80 # 本地服务端口 customDomains = ["www.example.com"] # 您的域名（需已解析到与frps同一IP）提示：customDomains 中的域名需与 1.3 小节配置的 A 记录一致，且已生效。3.3 启动frpc客户端创建一个批处理文件start_frpc.bat：@echo off cd /d %~dp0 frpc.exe -c frpc.toml pause双击此文件运行客户端。成功时，您会看到类似以下输出，即链接成功：[I] login to server success, get run id [xxxxxxxxxxxx] [I] proxy added: [web-site] [I] [web-site] start proxy success3.4 frpc.exe文件自动消失处理办法（可选）这是由于windows安全中心自动清除威胁文件导致的。在病毒威胁防护设置 -> 管理设置 -> 添加排除项 四、宝塔面板配置反向代理4.1 创建站点登录宝塔面板点击左侧菜单中的【网站】点击【添加站点】填写以下信息：域名：填入您要使用的域名（如www.example.com）FTP：选择【不创建】数据库：选择【不创建】PHP版本：选择【纯静态】点击【提交】4.2 修改Nginx配置在网站列表中，找到刚创建的站点，点击【设置】选择左侧菜单的【配置文件】将现有内容完全替换为以下配置（注意替换域名）：server { listen 80; listen [::]:80; server_name www.example.com; # 替换为您的域名 index index.php index.html index.htm default.php default.htm default.html; root /www/wwwroot/www.example.com; # 保持不变 #CERT-APPLY-CHECK--START # 用于SSL证书申请时的文件验证相关配置 -- 请勿删除 include /www/server/panel/vhost/nginx/well-known/www.example.com.conf; #CERT-APPLY-CHECK--END #SSL-START SSL相关配置，请勿删除或修改下一行带注释的404规则 #error_page 404/404.html; #SSL-END #ERROR-PAGE-START 错误页配置，可以注释、删除或修改 error_page 404 /404.html; #error_page 502 /502.html; #ERROR-PAGE-END # 核心反向代理配置 - 将请求转发到frps location / { proxy_pass http://127.0.0.1:18080; # 对应frps.toml中的vhostHTTPPort proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效 include /www/server/panel/vhost/rewrite/www.example.com.conf; #REWRITE-END #禁止访问的文件或目录 location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md) { return 404; } #一键申请SSL证书验证目录相关设置 (保持不变) location ~ \.well-known{ allow all; } #禁止在证书验证目录放入敏感文件 if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) { return 403; } access_log /www/wwwlogs/www.example.com.log; error_log /www/wwwlogs/www.example.com.error.log; #Monitor-Config-Start 网站监控报表日志发送配置 access_log syslog:server=unix:/tmp/bt-monitor.sock,nohostname,tag=6__access monitor; error_log syslog:server=unix:/tmp/bt-monitor.sock,nohostname,tag=6__error; #Monitor-Config-End }点击【保存】回到软件商店，找到【Nginx】，点击【设置】，再点击【重载配置】4.3 配置SSL证书（可选）在站点设置中，点击【SSL】选择【Let's Encrypt】选择验证方式为【文件验证】勾选您的域名，点击【申请】证书申请成功后会自动配置HTTPS（可选择强制HTTPS或保留HTTP）4.4 域名与访问验证（必做）确认域名解析：nslookup www.example.com 或在宝塔面板/域名商DNS控制台确认 A 记录已指向 frps 所在服务器IP。通道自检：先访问 http://www.example.com:18080/（直接到达 frps 的 vhostHTTPPort）。若能看到站点内容，说明 frp 穿透链路正常。入口自检：再访问 http://www.example.com/（通过Nginx的80端口反代转发至 frps）。若能正常显示，说明反向代理配置生效。如 18080 可访问而 80 不可访问，多半为 Nginx 站点未正确配置为反代或未重载配置；如两者均不可访问，优先检查 frpc 连接状态与 auth.token 一致性。4.5 访问网站五、常见问题排查5.1 frpc报错："error unmarshaling JSON"问题原因：frp从v0.51.0开始，配置文件格式从INI改为TOML。解决方法：确保使用正确的配置文件（.toml而不是.ini）检查配置语法是否符合TOML格式规范5.2 frpc报错："token in login doesn't match token from configuration"问题原因：客户端与服务器的token不一致。解决方法：仔细核对服务端和客户端配置中的token是否完全一致重启服务器端frps服务：systemctl restart frps重新启动客户端5.3 网站显示"The page you requested was not found"问题原因1：frpc客户端未运行或已断开。解决方法：检查客户端命令行窗口是否仍在运行重新启动frpc客户端检查是否有成功连接的日志信息问题原因2：域名与frpc配置不匹配。解决方法：确保frpc.toml中的customDomains与实际访问域名完全一致检查大小写和子域名5.4 访问域名时显示404或"找不到网页"问题原因：宝塔面板的Nginx配置未正确设置反向代理。解决方法：检查Nginx配置文件中是否包含正确的location / {...}反向代理块确保proxy_pass指向的端口与frps.toml中的vhostHTTPPort一致重载Nginx配置：在宝塔面板中点击【软件商店】→【Nginx】→【设置】→【重载配置】5.5 网站可以访问但样式错乱问题原因：后端网站不知道自己通过什么域名被访问。解决方法：确保Nginx配置中包含以下头信息传递：proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;六、高级应用（可选）6.1 将frpc设置为Windows服务使用NSSM工具将frpc设置为自启动服务：下载NSSM解压后，以管理员身份运行命令提示符执行以下命令：cd 路径到nssm解压目录\win64 nssm install frpc在弹出的窗口中：Path：选择frpc.exe的路径Startup directory：选择frpc所在目录Arguments：填写-c frpc.toml点击"Install service"启动服务：nssm start frpc6.2 多域名与子域名配置在frpc.toml中可以配置多个域名或子域名：# 主站点 proxies name = "web-main" type = "http" localIP = "127.0.0.1" localPort = 80 customDomains = ["www.example.com"] # 管理后台（指向同一本地端口，但使用不同域名区分） proxies name = "web-admin" type = "http" localIP = "127.0.0.1" localPort = 80 customDomains = ["admin.example.com"]结语通过以上配置，您已成功搭建了一个完整的内网穿透服务，可以安全地将本地网站发布到公网。宝塔面板的图形化界面简化了服务器端的配置过程，同时保留了申请SSL证书等功能。与商业内网穿透服务相比，自建frp服务不仅没有带宽限制，还提供了更高的定制灵活性和安全性。虽然初始配置略显复杂，但完成后您将拥有一个稳定可靠的内网穿透解决方案，完全由自己掌控。记住：frp客户端必须保持运行，才能确保内网穿透服务的持续可用。建议将客户端配置为系统服务，实现开机自启动。
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]
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监控图表了。
分享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能为您的博客增添更多精彩内容！有什么问题可在评论区留言，觉得有趣也可以留言！
解决Typecho报错 - Unsupported operand types: int - string
一场突如其来的网站崩溃今天做完插件，网站访客量激增。直接把我的2h2g负载拉满了，还好网站能打开，看到了点报错内容：Fatal error: Uncaught TypeError: Unsupported operand types: int - string in /www/wwwroot/blog.ybyq.wang/usr/themes/handsome/functions.php:113 Stack trace: #0 /www/wwwroot/blog.ybyq.wang/usr/themes/handsome/sidebar.php(25): online_users() #1 /www/wwwroot/blog.ybyq.wang/var/Widget/Themes/Handsome/index.php(113): require_once('/www/wwwroot/bl...') #2 /www/wwwroot/blog.ybyq.wang/var/Widget/Archive.php(1589): require('/www/wwwroot/bl...') #3 /www/wwwroot/blog.ybyq.wang/var/Widget/Archive.php(449): Widget_Archive->render() #4 /www/wwwroot/blog.ybyq.wang/index.php(15): Widget_Archive->archive() #5 {main} thrown in /www/wwwroot/blog.ybyq.wang/usr/themes/handsome/functions.php on line 113错误解读：这个错误信息告诉我们：在handsome主题的functions.php文件第113行，代码试图用一个整数(int)减去一个字符串(string)，这在PHP中是不被允许的操作。错误定位：顺藤摸瓜找到源头分析错误日志提供的线索：关键信息错误文件：handsome主题的functions.php错误位置：第113行错误类型：TypeError，类型不匹配调用链路：sidebar.php(25) → online_users()堆栈信息清晰地展示了错误的传播路径：侧边栏调用online_users()函数时触发了错误。这个函数用于统计当前在线的访客人数。问题定位：在线人数统计功能在特定情况下产生了类型不匹配，导致整个页面渲染失败。深入分析：代码脆弱性查看online_users()函数的核心逻辑：function online_users() { $filename = 'online.txt'; // 存储访客信息的文件 $onlinetime = 30; // 30秒在线有效期 $nowtime = $_SERVER['REQUEST_TIME']; // 当前时间戳(整数) $online = file($filename); // 读取文件内容为数组 $nowonline = array(); // 处理每条访客记录 foreach ($online as $line) { $row = explode('|', $line); // 分割用户ID和时间戳 $sesstime = trim($row[1]); // 获取时间戳(字符串!) // 问题代码就在这里 if (($nowtime - $sesstime) <= $onlinetime) { $nowonline[$row[0]] = $sesstime; } } return count($nowonline); }核心问题出在$nowtime - $sesstime这一运算：$nowtime是一个整数时间戳$sesstime是从文件读取的字符串警告：在线运行环境中，online.txt文件可能出现各种异常情况：存在空行某行格式错误，缺少分隔符|时间戳部分包含非数字字符当这些情况发生时，trim($row[1])可能返回空字符串或非数值内容，导致PHP在执行整数 - 字符串运算时抛出TypeError，整个程序终止运行。解决之道：防御性编程防御性编程思想：面对外部数据的不确定性，我们不能寄希望于数据永远完美，而应让代码能够优雅地处理各种异常情况。针对这个问题，我们需要两个关键改进：存在性检查：在使用数组元素前，先确认它们是否存在类型安全转换：在执行数学运算前，将可能的字符串显式转换为整数修改后的代码：function online_users() { $filename = 'online.txt'; $onlinetime = 30; $nowtime = $_SERVER['REQUEST_TIME']; // 防御步骤1：检查文件是否存在 $online = file_exists($filename) ? file($filename) : array(); $nowonline = array(); foreach ($online as $line) { $row = explode('|', $line); // 防御步骤2：检查数组索引是否存在 if (isset($row[1])) { $sesstime = trim($row[1]); // 防御步骤3：类型安全转换 if (isset($row[0]) && ($nowtime - (int)$sesstime) <= $onlinetime) { $nowonline[trim($row[0])] = $sesstime; } } } return count($nowonline); }修复结果：应用这个修复后，网站立即恢复正常运行，不再因为online.txt文件中的格式问题而崩溃。防御性编程的启示核心原则：永远不要完全信任外部数据，即使是由自己的程序生成的数据。防御性编程的几个实用技巧：始终验证输入：无论数据来源多么可靠，都要进行必要的验证类型安全转换：在进行类型敏感操作前，显式转换数据类型存在性检查：使用isset()、empty()等函数确保数据存在且有效优雅降级：当遇到异常情况时，提供合理的默认行为，而不是直接崩溃编程哲学：通过这些小小的编码习惯，我们可以将程序从"脆弱易碎的玻璃"变成"柔韧有力的竹子"，在面对各种意外情况时依然能够稳健运行。