服务器进入稳定运行阶段，近期改变如下： 7-17：使用Kimi K3完善主页，修复了跨站访问的问题，网站内集成了介绍内容。 7-28：恢复了被K3删除的友链内容。 更新了设备&宅物页面的物品，新增了两个娃娃一张专辑。 完成了域名的续费，保障服务稳定性。 将主页迁移到CloudFlare Pages托管，缓解主服务器宽带压力。 完成家里云搭建，基于FRP透传，可作为服务器的备份站点，亦或者作为MC服务器运行。
摘要大一下学期学业不佳，依赖AI致高数、物理挂科，建模比赛敷衍。爱好方面用Typecho重建博客，整合pjax等功能并借AI实现首页设计。生活作息紊乱，不吃早餐而依赖夜宵。宿舍关系和谐，线上社交有得有失，已退出学生会。学期结束虽有遗憾，亦收获个人项目成果，盼补考通过。正文 从3月10日拖着行李箱再次踏入校门，到7月10日放下笔走出期末考场，大一下的时光满打满算不过短短四个月。一百二十多天，说长不长，说短不短，刚好够一场从初春到盛夏的过渡，刚好够我从寒假里那个信誓旦旦要"这学期一定好好学习"的幻想中，慢慢滑回现实。 暑假眼看着就要来了，与其让它悄无声息地溜走，不如坐下来，对这磕磕绊绊的四个月做个简单总结吧。 学业方面 如果要用一个词概括，那只能是"马马虎虎"——甚至这个词本身都带着几分自我美化的成分。 这学期伊始，我并非没有立下过flag。然而现实是，三月的春风还没吹暖图书馆的座位，我的学习热情就已经先于气温降了下来。每天睁开眼，第一件事不是翻开课本，而是摸向手机，在各类消息和推送中消磨掉清晨最清醒的半小时。等到终于磨蹭着坐到书桌前，面对着高数课本上密密麻麻的公式，或是数据结构里那些让人头晕的指针和递归，动力就像沙漏里的细沙，无声无息地漏光了。 于是，"AI续命"成了这学期的主旋律。高数的课后作业？丢给AI，抄完答案，似懂非懂地点个头。数据结构的算法题？让AI写个框架，自己改改变量名交上去。Web前端的课程设计？直接让AI生成一套模板，修修补补就算完工。你说这些课程重要吗？当然重要——高数是理工科的地基，数据结构是编程的核心，Web前端是实打实的吃饭手艺。但你说我认没认真学呢？答案显然是否定的。那些用AI糊弄过去的知识点，在期末考场上全都变成了回旋镖，精准地扎了回来。 于是乎，我的数据结构和物理不出意外地挂掉了。看到成绩单的那一刻，不是意外，更像是迟到的审判——这学期偷的每一个懒，都在那张成绩单上找到了对应的位置。 至于跟队友参加的统计建模比赛，过程也是有气无力。赛前大家聚在一起，豪言壮语说要拿奖；赛中的无数个夜晚，群聊里充斥着"明天再搞吧"的相互安慰；最后截止日的前两天，全队进入经典抱佛脚模式，对着数据手忙脚乱地跑模型、凑论文。结果可想而知，投出去的作品像一颗小石子丢进大海，连点水花都未曾溅起。现在回想起来，那场比赛与其说是比赛，不如说是我们几个人集体拖延症发作的临床观察记录。 爱好方面 如果说学业是一潭死水，那爱好至少还泛着几圈涟漪——虽然这些涟漪很大程度上也是靠AI吹起来的。 这学期我做了一件酝酿已久的事：用Typecho取代了Astro，彻底重做了我的博客网站。迁移的过程比想象中折腾，数据导出、格式转换、主题适配，前后断断续续搞了两周。但当我终于在浏览器里输入新地址，看到熟悉的文章列表以全新的面貌呈现时，那种成就感是真实的。 紧接着，我开始借助AI对博客进行深度优化。最花心思的是pjax无刷新技术的整合——为了实现页面切换时的平滑过渡，我翻了不少文档，让AI帮我逐行调试代码，最终搞定了这套方案。后来又陆续整合了Live2d看板娘和在线音乐播放器，现在打开博客，能看到可爱的看板娘在角落晃悠，背景音乐缓缓流淌，至少视觉上算是有了点"个人站点"的样子，而不是千篇一律的模板站。 期间还心血来潮部署了一次邮件服务，想着以后可以给博客加上订阅功能，或者搞个联系方式。结果配置了半天SMTP、DKIM、SPF记录，好不容易能收信了，却因为服务器被阿里云制裁而无法发信，最后不了了之，成了又一个"烂尾工程"。 另外一件值得记录的事，是借助AI把自己手工设计的网站首页变成了现实。之前画了一堆草图，配色、布局、交互逻辑都在脑子里转了很久，但苦于前端能力有限，一直停留在纸上。这学期干脆把设计稿丢给AI，一边描述需求一边迭代，硬是把那个幻想中的首页做了出来。现在博客站和主页分开挂在两个域名下，一个负责内容输出，一个负责门面展示，管理起来清爽多了。 内容方面，这学期也没少往博客上灌水。翻出了高中时期写的一些旧文章，修修改改搬了上去；偶尔折腾了什么新东西，也习惯性地记上一笔。虽然更新频率飘忽不定，质量也参差不齐，但至少这个小小的自留地还在运转，没有彻底荒芜。 生活方面 作息方面，我倒是保持着一种诡异的"良好"——良好到几乎每天都能在凌晨两点准时躺下，早上七点准时被闹钟惊醒，然后以一个鲤鱼打挺的姿势冲向早八教室。听起来很健康对吧？但问题在于，这套作息的代价是几乎不吃早餐，以及顿顿不离夜宵。 早餐？不存在的。早八的课都踩点进教室了，哪还有时间光顾食堂。于是空腹撑到中午，午饭往往又因为各种事由草草了事。等到夜幕降临，肚子里的空虚感开始作祟，夜宵就成了唯一的救赎……凌晨的宿舍楼道里，我的外卖袋子和垃圾桶里的包装纸见证了太多罪恶的深夜。这两者形成了奇妙的强相关：不吃早餐导致白天能量不足，白天能量不足导致晚上报复性进食，晚上吃太饱又影响第二天早起，恶性循环，闭环了。 偶尔也会出现三餐都敷衍的情况——忙起来就随便扒两口，或者干脆用零食应付。按理说这种饮食节奏，肠胃早该造反了。但神奇的是，我的消化系统居然扛住了，一学期下来基本无大碍，不知道是该感谢年轻的身体，还是该担心它在默默记小账，等年纪大了再一起清算。 社交方面 宿舍关系意外地稳定。四人寝室，没有争吵，没有冷战，没有狗血剧情，大家各忙各的，偶尔一起吐槽课程，维持着一种恰到好处的和谐融洽。这种"不咸不淡"的室友关系，大概已经是大学宿舍生活的最优解了。 线上社交则复杂一些。这学期继续维持着几个很不错的社交关系，有能深夜聊技术的朋友，有一起追番玩游戏的同好，也有偶尔互相倾倒情绪垃圾的知己。大家散落在不同的城市，靠着网线维系，却意外地稳固。当然，也有遗憾——断交了一个曾经很聊得来的朋友，最后因为控诉我的冷落而不再联系。虽然少了一个人分享日常，但总体算下来，我的社交账本依旧是健康的，甚至可以说是盈余的。 学生会那边，经过一年的干部生活，我终于认清了现实。那些繁琐的表格、无意义的会议、形式主义的活计，消耗掉了我不少课余时间，换来的却只是简历上一行可有可无的字。换届的时候，我没有递交留任申请，走得干干净净。 技能方面 驾照依旧没有去学。 总结 唉唉，不管怎么说，学期已经结束了。 好好学的，没好好学的，认真做的，糊弄过去的，开心的，遗憾的，全都化作那张薄薄的成绩单，和宿舍楼下逐渐空旷的停车场，无声地召唤着暑假的来临。 这四个月像一场模糊的梦境，醒来时手里攥着的，除了几个挂科的学分，还有那个在深夜亮着屏幕、一点点搭建起来的博客；除了建模比赛的落败，还有几个未曾走散的线上好友；除了无数个靠AI续命的作业夜晚，还有对自己"到底想要什么"的零星思考。 暑假来了，先休息几天，再努力尝试新的东西。 希望下学期，至少，先把补考过了吧。
借由 AI 辅助，近期进行了重大变更，细节如下： 新增 主页 站，与本博客站进行区分，本博客已迁移到 blog域； 图片来源：https://www.pixiv.net/artworks/91469670 新增一个简短版的个人介绍，请从 个人主页 访问； 将友链迁移到 个人主页 了； 贴合个人主页的主题颜色，对本站的主题色进行了变更。
摘要作者为优化Typecho博客的Markdown支持，尝试EditorMD插件遭遇PHP 8受保护方法调用错误及PJAX兼容性问题。经修复权限、尝试双插件并存后，因体验割裂最终决定整合EditorMD编辑器与EnhancedMarkdown的Parsedown解析器，开发新插件EnhancedMarkdownPlus，解决命名冲突实现前后端统一渲染，并总结AI辅助开发的经验教训。 前言 作为一个追求极致阅读和交互体验的独立博客站长，我使用了一款极简优雅的主题 Jasmine_Plus。在偶然跟别人的一次聊天，我就遇到了这个情况： 我：博客自带编辑器就是方便，走到哪写到哪（截图） 对面：没有语法着色吗（）我现在已经离不开语法着色了 我：这个没有，但是有预览（ 我：（说的也对，我好像可以弄一个了） …… 为了优化博客对 Markdown 语法的支持，我一开始上网搜索，选用了 EditorMD，但是装上插件以后很快就遇到了麻烦。借此又开启了这次折腾，以下是完整记录。 初遇 Bug 满怀期待部署完插件，上编辑器看看？一切正常，运行良好。 然后，刷新一下首页看看呢？ 报错了。进文章看看？也是空空如也。 初步定位 在AI的帮助下，我顺着追踪到了 Editor.MD 插件的主控文件 Plugin.php。在插件的 content 和 excerpt 过滤器中，代码试图去渲染文章内容： Php | 复制 \(text = \)conent->isMarkdown ? \(conent->markdown(\)text) : \(conent->autoP(\)text); 问题就出在这里：markdown() 和 autoP() 是 Widget\Base\Contents 内部的 protected（受保护）方法。 AI 给出如下解释： - 在旧版本 PHP 或老旧 Typecho 中，这种外部直接调用可能通过某些魔法方法或宽松的可见性规则被放行。 - 但在现代 PHP 8.x + Typecho 1.2+ 中，外部直接访问受保护方法会触发 Typecho\Widget 的 __call() 魔术方法。由于权限不足，该魔术方法最终返回了 null。 - 紧接着，这个 null 被传递给了 Typecho 核心的 Contents.php 进行后续处理（例如 explode() 等字符串操作），从而引发了 Passing null to parameter of type string is deprecated 的警告。 解法一：修复 Content 询问朋友资助的Opus4.6后，AI给出如下解法： 既然是受保护方法，我们只需要利用 PHP 的 Closure::bind，在运行时将闭包的作用域临时绑定到当前对象类中，就能合法地调用这些受保护方法。 同时，考虑到不同 Typecho 版本核心可能发生改变，我还增加了一层回退机制——如果类中不存在这两个方法，直接调用底层的 Utils\Markdown 和 Utils\AutoP 静态工具类。 修复后的 content 方法核心逻辑如下： Php | 复制 \(markdown = method_exists(\)conent, 'markdown') ? \Closure::bind(function(\(t) { return \)this->markdown(\(t); }, \)conent, get_class($conent)) : function(\(t) { return class_exists('\\Utils\\Markdown') ? \Utils\Markdown::convert(\)t) : $t; }; \(autoP = method_exists(\)conent, 'autoP') ? \Closure::bind(function(\(t) { return \)this->autoP(\(t); }, \)conent, get_class($conent)) : function($t) { if (class_exists('\\Utils\\AutoP')) { $parser = new \Utils\AutoP(); return \(parser->parse(\)t); } return $t; }; \(text = \)conent->isMarkdown ? \(markdown(\)text) : \(autoP(\)text); 经过此番修改，至少，首页的 Deprecated 报错消失了。 然而，还没高兴半分钟，当我从首页点击某篇文章的标题跳转进详情页时，傻眼了：文章内容区依然一片雪白，一个字都没有显示。 再战 Bug 上面的事情后，朋友资助的Opus4.6额度已经见底，不足以支撑下一步，于是切换 Gemini3.5Flash，让AI再次审查渲染后的 DOM 结构，我发现详情页的文章区域其实有内容，但它们被包裹在了一个隐藏的 textarea 中： Html | 复制 # 这里是文章的原始 Markdown 源码... 原来，EditorMD 插件默认开启了“前端解析渲染”。它不依赖服务器将 Markdown 转成 HTML，而是直接把 原始Markdown 丢进一个隐藏的文本框，然后在页面底部（Footer区域）注入一段 JavaScript 脚本，在页面加载完毕后调用 editormd.markdownToHTML() 动态在客户端渲染出最终的 HTML 结构。 这套逻辑在传统的“整页刷新”博客上跑得挺好，但在我的二改主题上踢到了铁板。 为了保持页面完整性与不同页面之间的加载连贯性，我引入了 PJAX 技术来实现无刷新页面跳转。 在这种加载模式下： 当用户点击文章链接时，PJAX 只会通过 AJAX 异步获取目标页面的内容，并替换当前页面中指定的容器（如 #middle 和 #right）。 但，关键在于：页面底部的 Script 脚本在 PJAX 局部加载时是不会被重新执行的！ 结果就是：用户通过 PJAX 点击进文章页，隐藏的 textarea 被成功加载了，但负责把 Markdown 转换成 HTML 的 JS 渲染引擎根本没有被触发，页面依旧是一片空白。 解法二：更换插件 折腾至此，我就要准备换插件了，我听说另外一个插件EnhancedMarkdown也能比较好的解决这个问题，又不是我就安装了上去。 但是启用之后我才发现，这个插件只会接管网站显示时候的Markdown渲染，对于后台编辑器没有任何支持。 这个解法不合适。 解法三：双插件并存 既然上文提到，EitorMD默认开启接管前端渲染，那我把前端渲染关掉了不就行了？ 然后我就随手一关，编辑页面正常了，经过方案一修复的东西让现在首页也完全正常了。 但是回头一想：除了编辑器以外，啥也没更改，那我做 Markdown 的支持做好了吗？不过是在编辑器里如此罢了，实则并没有完全支持。 于是我配合关闭了前端显示的 Editor.MD ，将首页的 MD 渲染器由 EnhancedMarkdown 来接管。然后我又用了几个测试文档来测试功能，一切正常，没有报错，于是这件事告一段落。 三见 Bug 就这样用了几天，看着编辑器支持的部分语法，放到网站就不支持了，而某些编辑器不支持的，网站上又能正常显示，这种体验非常割裂 于是我就想，与其扭捏半天各取一半，占用双倍空间，不如借助 AI，自己完成嫁接整合，各取一半成为一个完整的插件，岂不美哉？ 解法四：另起炉灶 借助 AI 帮助我选用用合适的架构，询问 AI，AI分析如下： 候选方案 运行端 技术栈要求 依赖管理与引入方式 社区活跃度与状态 Parsedown / ParsedownExtra 服务端 PHP 7.4+ 零依赖。可单文件引入，或通过 Typecho 自定义 Autoloader 加载。 极高稳定性，已进入维护期（不再频繁更新，但非常稳定）。 League/CommonMark 服务端 PHP 8.0+ 强依赖 Composer。需将 vendors 打包在插件内，或在服务器执行 composer install。 极高，PHP 基金会及主流框架（Laravel）的标准 CommonMark 实现。 markdown-it 客户端 JavaScript 浏览器环境引入 JS 文件（通过 CDN 或本地静态文件）。 极高，JS/Node 领域的行业标准。 markdown-it(Node.js/Python) 服务端 Node.js / Python 需要服务器安装 Node.js/Python 环境，PHP 通过 exec() 或本地 HTTP API 进行进程间通信。 活跃，但为 PHP 架构引入了异构栈。 EditorMD(marked.js) 客户端 / 混合端 PHP + JS Typecho 插件接管后台编辑器，前台可选加载 JS 资源进行解析。 经典解析插件，目前处于维护状态。 注意： 1.Typecho 现存方案：当前项目有了 EnhancedMarkdown 插件，其本质就是基于 Parsedown + ParsedownExtra 进行的深度定制（重写了 Parser::blockFencedCodeComplete 以支持 Mermaid、代码高亮、复制按钮，扩展了 containsMarkdownSyntax 保护纯 HTML 标签，实现了 preprocessMath 保护数学公式）。 League\CommonMark：虽然配置极度灵活，但不支持原生“拼音/中文 Slug 生成”，如需实现 TOC 锚点去重和中文拼音定位，需要编写大量自定义扩展（Extension/Renderer）。 客户端 JS (markdown-it)：完全把渲染工作交给了浏览器，功能极其强大（插件多），但会导致 RSS 订阅源、API 接口（XMLRPC）、主题中的“字数统计”和“摘要截取”功能无法获取正确的 HTML 文本，因为数据库里存的是原始 Markdown，而服务器不再解析它。 工作区现存的 EditorMD 插件：在 EditorMD 插件中，虽然在设置里提供了“接管前台Markdown解析”选项（利用 marked.js 进行前端渲染），但在当前 codebase 中该行为已被定制和修改： —— 也就是说，由于当前启用的 Jasmine 主题是一个 PJAX 主题，客户端渲染模式会在 PJAX 局部刷新时失效（因为页脚脚本不再重新执行），因此该插件目前在前端已经退化为直接调用服务端的 \Utils\Markdown::convert，前端解析事实上处于旁路/失效状态。 阅读完这一切，我决定使用 EnhancedMrkdown 插件的 Parsedown / ParsedownExtra 作为解析器，将 EditorMD 的对照式编辑器集成到 EnhancedMrkdown 中，事已至此，开工吧。 拦路 Bug 出现 让 AI 合并了一次插件，再次尝试部署，页面顶部弹出一行冰冷的字：“无法启用插件”。 让 AI 继续排查原因，汇总如下： Typecho 插件 EnhancedMarkdownPlus 在启用时因 PHP 类找不到而失败。排查发现，插件配置表单的 config() 方法中使用了 new Form\Element\Layout(...) 来插入 标题。由于文件头部存在 use Typecho\Widget\Helper\Form，PHP 会将 Form\Element\Layout 解析为 Typecho\Widget\Helper\Form\Element\Layout。然而，Typecho 核心源码的 var/Typecho/Widget/Helper/Form/Element/ 目录下并不存在 Layout.php，该目录只有 Text、Radio、Textarea、Select、Fake 等表单组件类。Layout 实际上是 Typecho\Widget\Helper\Layout，属于底层的 HTML 标签封装类，而非表单元素。因此，实例化一个命名空间错误的“幽灵类”触发了致命的 Class Not Found 错误，导致插件无法激活。 杜绝 Bug 考虑到现有的对话上下文污染，为了防止和已有插件产生命名冲突或资源覆盖报错，我采取了最稳妥的策略——让 AI 彻底重写。 将所有 PHP 文件的命名空间完全修改为 TypechoPlugin\EnhancedMarkdownPlus。 将后台读取配置的键值全部替换为 EnhancedMarkdownPlus，使得新插件的激活完全独立，不留一丝旧版插件的影子。 将所有需要的文件及静态库彻底整理进新目录，统一在 EnhancedMarkdownPlus 的 ASSETS_SUBDIR 下进行管理。 再次打开后台，输入一段 Markdown，后台的分屏预览瞬间亮起，所有公式、图表、高亮和容器无缝呈现，效果与前台丝毫不差！ 这次重写，一遍过，干脆利落，没有任何拖泥带水。 总结 “修一个 Bug”往往只是开始，不是结束：修复了 protected 方法调用问题，却发现 PJAX 不兼容；解决了 PJAX，又发现双插件体验割裂。技术债的连锁反应要求我们从全局视角审视方案。 AI 是加速器，不是替代器。 本次折腾中，AI 在方案对比、代码生成、Bug 排查上提供了巨大帮助，但也引入了命名空间错误。AI 适合快速原型和思路拓展，核心架构决策和关键代码审查必须人工把关。 兼容性优先于炫技： League\CommonMark 功能强大但配置复杂，markdown-it 生态丰富但破坏服务端一致性。最终选择 Parsedown 不是因为最先进，而是因为最契合现有技术栈、最稳定、最可控。 彻底重写有时比重构更省时间：当旧代码存在命名冲突、历史包袱、AI 引入的隐藏错误时，果断另起炉灶，用清晰的边界和独立的命名空间重新组织，反而降低了长期维护成本。 至此，折腾告一段落。现在，前台文章由 Parsedown 引擎在后端一次性高效解析输出，配合 Prism.js 和 KaTeX 提供完美的语法高亮和公式渲染；后台则由 Editor.md 的对照编辑器接管，提供完全还原前台样式的双栏实时预览。文章跳转在 PJAX 的加持下行云流水，整体体验非常丝滑。 相关折腾成果已发布至Github - EnhancedMarkdown_Plus，如有需要自行取用！
摘要作者记录为 Typecho 博客配置 AI 摘要插件 AIContentSummary 的踩坑与重构过程。发现插件存在时序冲突（finishPublish 钩子执行时机导致无法生成摘要）、同步请求卡死及温度参数硬编码与 KIMI API 不兼容等问题。通过改用 write 钩子拦截内容变更、增加温度设置与单篇文章控制开关等方式改良插件，最终成果已开源至 GitHub。 前言 在独立博客的排版与呈现中，一篇精炼的摘要能极大提升读者的阅读体验。但是鉴于本人比较懒，所以就让打算让AI代劳。最近我尝试为 Typecho 博客配置一款名为 AIContentSummary的 AI 摘要插件，本以为填个 API Key 就能用了，结果在 AI 的帮助下还是折腾了好久，过程中踩了不少坑。 这里把这次 debug 和重构的完整过程记录下来。 一、初遇 Bug 配置好 API 地址和密钥后，我满怀期待地点击“发布”，却只看见一个Server Error，回到文章列表，发现文章发布了，但 AI 摘要字段依然是空的。即使我去修改已经存在的文章并重新发布，摘要也没有任何变化。 为了找出原因，我借助 AI，扒开了插件的源码 Plugin.php，顺着文章保存与发布的生命周期理了一遍逻辑： 时序冲突 在 Plugin.php 中，生成逻辑挂载在 finishPublish 钩子上，通过 ensureSummary() 方法来实现。而该方法内部有一行安全检查： Php | 复制 if (\(exists && trim((string) \)exists['str_value']) !== '') { return null; } 看似合情合理──如果文章已经有了摘要，就不重复生成。 然而，在 Typecho 写入文章的事务中，自定义字段的保存方法 applyFields() 在 finishPublish 钩子被触发之前就已经执行了。当用户点击“发布”时，表单提交了空的（或旧的）自定义字段，并写入了数据库。到了执行 finishPublish 时，数据库里其实已经存在了这个字段，导致上述检查永远返回 null，自动更新和生成机制直接失效！ 同步请求 由于 API 请求是同步发起的，如果接口响应缓慢或网络超时，整个发布文章的页面就会卡死；更严重的是，原代码中没有任何 try-catch 保护，一旦 API 服务异常或者鉴权失败抛出错误，会导致整个 Typecho 页面崩掉，这就是之前“Server Error”的根源所在。 二、排查诊断 为了在不崩掉页面的前提下定位错误，AI辅助我做了两件事： 在 finishPublish 中包裹了 try-catch，并使用 Typecho 自带的 Notice 机制，如果生成失败，就在页面重定向后以红条警告展示具体报错，而不是直接白屏。 在博客根目录下输出了一个临时的 debug.log，打印钩子触发的参数状态。 这一测，一下子就发现了问题： 模型参数限制 在开启了配置后，重新发布，日志里爆出了真正的接口报错： Text | 复制 async ensureSummary failed. Error: invalid temperature: only 1 is allowed for this model 原来插件在调用大模型接口时，将温度值（temperature）硬编码成了 0。而我配置的 KIMI 的 API 为了输出稳定性，强制要求温度值必须为 1。 三、AI 辅助下的插件改良 既然定位到了所有的痛点，那就开始解决！我给这个插件做了一次改良升级： 生成逻辑 拦截 write 钩子 ── 精准判定是否需要重绘 我注册了 Widget\Contents\Post\Edit 下的 write 过滤器。在文章真正写入数据库前： 比对旧正文与新正文，确认文章内容是否真的改变了。 比对旧摘要与表单提交的摘要。如果两者相同，说明作者并没有手动编辑过摘要，此时如果正文变了，就可以安全地将 $shouldRegenerate 状态标记为 true。 如果用户手动修改了摘要输入框，则主动放弃自动生成。 参数调优 增加温度（Temperature）设置：在插件配置后台中，新增了 temperature 字段，并将其默认值设定为 1.0，从而避免因为某些模型 API 参数限制报错。 按需独立控制：在文章编辑页新增了一个专门的 “自动 AI 总结” (启用/禁用) 单选按钮，让写作者在撰写单篇文章时，能根据需要灵活决定是否启用 AI 摘要生成。 保留日志输出：所有的调试日志全部在同级目录的 log/debug.log 下，方便用户检查错误原因。 总结 经过以上折腾，AI 总结已经能完美地运行在我的博客上，文章发布与更新流畅自如，AI 摘要如丝般顺滑地自动运作，折腾完毕，收工！ 相关折腾成果已发布至Github - AIContentSummary_Plus，如有需要自行取用！
借助AI，近期更新极大的优化了网站的功能和稳定性，具体如下： 优化了PJAX的加载逻辑，极大的减少了控制台报错的概率，相应的提升了网站稳定性； 网站现在可以记忆阅读位置了，点击上一页会自动回到上一次浏览的位置； 更换Markdowm解析引擎，使用自己修改的，基于插件 EnhancedMarkdown 和 EditorMD 的二次修改的 EnhancedMarkdownPlus 插件作为网站的MD支持； 网站基于AIContentSummary二次修改了插件，为博客增加AI摘要功能。
友链新增七辰和诺雅狐； 网站新增了对PJAX的支持，以节省流量开支； 友链界面排版优化； 新增“罗伊的技能点”页面，展示我的技能； 删除单独的“罗伊在哪里”页面； 使用了新的网站logo； 优化“关于罗伊”： 新增“个人档案”； 补充“我的游戏人生”，整理了玩过的许多游戏； 爱好与兴趣合并为“兴趣关键词”； 新增已删除单独的“罗伊在哪里”页面，作为介绍的一部分内容展示。
摘要Cloudflare Tunnel 技术指南详解通过 cloudflared 建立加密隧道暴露本地服务的两种管理模式（本地配置与远程 Token）、跨平台安装、核心命令与配置语法、部署流程及安全实践，支持 HTTP/SSH/TCP 等多协议代理与故障排查。一、概述 Cloudflare Tunnel（原 Argo Tunnel）通过 cloudflared 守护进程在你的本地服务器与 Cloudflare 边缘网络之间建立出站加密连接，无需公网 IP、无需开放防火墙端口，即可将本地服务安全暴露到互联网。 两种管理模式 模式 特点 适用场景 本地管理（Local Management） 配置存储在本地 config.yml，使用 cert.pem 和 JSON 凭证 需要版本控制、基础设施即代码 远程管理（Remote Management） 配置存储在 Cloudflare Dashboard，本地仅使用 Token 快速部署、多副本 HA、团队共享 二、安装 macOS Bash | 复制 brew install cloudflared Linux（Debian/Ubuntu） Bash | 复制 curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb sudo dpkg -i cloudflared.deb Windows Powershell | 复制 winget install --id Cloudflare.cloudflared # 或 choco install cloudflared Docker Bash | 复制 docker run -d --name cloudflared --restart unless-stopped \ cloudflare/cloudflared:latest tunnel --no-autoupdate run --token <YOUR_TOKEN> 三、核心命令详解 3.1 认证与账户 命令 功能 cloudflared tunnel login 打开浏览器授权，将本地 cloudflared 与 Cloudflare 账户关联，生成 ~/.cloudflared/cert.pem cloudflared tunnel token <NAME/UUID> 获取指定隧道的 Token（用于远程管理或 Docker 部署） 3.2 Tunnel 生命周期管理 命令 功能 cloudflared tunnel create <NAME> 创建命名隧道，输出 Tunnel ID（UUID）和凭证文件路径 cloudflared tunnel list 列出所有活跃隧道（加 -d 可包含已删除隧道） cloudflared tunnel info <NAME/UUID> 查看隧道详情，包括活跃连接器（replicas）状态 cloudflared tunnel delete <NAME/UUID> 删除隧道（活跃连接时拒绝，加 -f 强制删除） cloudflared tunnel cleanup <NAME/UUID> 清理隧道的残留连接（用于非优雅退出后的修复） cloudflared tunnel cleanup --connector-id <ID> <NAME/UUID> 断开并删除指定 replica 连接器 3.3 路由配置 命令 功能 cloudflared tunnel route dns <NAME/UUID> <hostname> 自动在 Cloudflare DNS 创建 CNAME 记录，将子域名指向隧道 cloudflared tunnel route lb <NAME/UUID> <hostname> <pool> 将隧道添加为负载均衡池端点（自动创建 LB 和 Pool） 3.4 运行与调试 命令 功能 cloudflared tunnel run <NAME/UUID> 运行指定隧道（前台运行，Ctrl+C 退出） cloudflared tunnel --config <path> run <NAME/UUID> 使用自定义配置文件运行隧道 cloudflared tunnel --url http://localhost:PORT 启动 Quick Tunnel（临时隧道，无需账户，生成 *.trycloudflare.com 域名） cloudflared tunnel diag 运行连通性预检查（2026.5.2+ 版本内置） cloudflared tail <UUID> 实时流式查看隧道日志 cloudflared tunnel ingress validate 验证配置文件语法 cloudflared tunnel ingress rule <URL> 测试指定 URL 匹配哪条 ingress 规则 3.5 系统服务 命令 功能 cloudflared service install 安装为系统服务（systemd/launchd/Windows Service），自动读取 /etc/cloudflared/config.yml cloudflared service install <TOKEN> 使用 Token 安装远程管理服务 cloudflared update 检查并更新 cloudflared 二进制（仅适用于 GitHub 安装方式） cloudflared version 显示版本号和构建日期 cloudflared help 显示所有顶级命令 四、配置文件详解（config.yml） 4.1 基础结构 Yaml | 复制 tunnel: <TUNNEL_ID或NAME> credentials-file: /etc/cloudflared/<TUNNEL_ID>.json # 可选：指定传输协议（quic 或 http2） protocol: quic ingress: - hostname: app.example.com service: http://localhost:8080 originRequest: connectTimeout: 30s noTLSVerify: false - hostname: ssh.example.com service: ssh://localhost:22 - hostname: rdp.example.com service: rdp://localhost:3389 - hostname: tcp.example.com service: tcp://localhost:25565 # 必须：兜底规则，拒绝未匹配请求 - service: http_status:404 4.2 Ingress 服务类型 类型 示例 说明 HTTP http://localhost:80 普通 HTTP 服务 HTTPS https://localhost:443 本地 HTTPS 服务（配合 noTLSVerify: true 可跳过证书验证） SSH ssh://localhost:22 SSH 服务（需配合 cloudflared access ssh 使用） RDP rdp://localhost:3389 远程桌面 TCP tcp://localhost:25565 任意 TCP 服务 UNIX Socket unix:/var/run/app.sock Unix 域套接字 状态码 http_status:404 返回指定 HTTP 状态码（常用作兜底） 4.3 originRequest 常用配置 Yaml | 复制 originRequest: connectTimeout: 30s # 连接超时 tlsTimeout: 10s # TLS 握手超时 keepAliveTimeout: 30s # 长连接超时 noTLSVerify: true # 跳过本地 TLS 证书验证（自签名证书时使用） httpHostHeader: localhost # 自定义 Host 头 originServerName: localhost # SNI 名称 disableChunkedEncoding: true # 禁用分块传输 五、本地管理模式完整流程 Bash | 复制 # 1. 登录授权 cloudflared tunnel login # 2. 创建隧道 cloudflared tunnel create my-tunnel # 输出：Tunnel ID 和凭证文件路径 ~/.cloudflared/<UUID>.json # 3. 配置 DNS 路由 cloudflared tunnel route dns my-tunnel app.example.com cloudflared tunnel route dns my-tunnel api.example.com # 4. 创建配置文件 sudo mkdir -p /etc/cloudflared sudo cp ~/.cloudflared/<UUID>.json /etc/cloudflared/ sudo tee /etc/cloudflared/config.yml <<EOF tunnel: <UUID> credentials-file: /etc/cloudflared/<UUID>.json ingress: - hostname: app.example.com service: http://localhost:3000 - hostname: api.example.com service: http://localhost:8080 - service: http_status:404 EOF # 5. 验证配置 cloudflared tunnel ingress validate # 6. 安装系统服务 sudo cloudflared service install sudo systemctl enable --now cloudflared # 7. 查看状态 sudo systemctl status cloudflared sudo journalctl -u cloudflared -f 六、远程管理模式（Dashboard / Token） 6.1 通过 Cloudflare Dashboard 创建 登录 Cloudflare Zero Trust Dashboard 进入 Networks → Tunnels → Create a tunnel 选择 Cloudflared，命名后保存 复制安装命令（包含 Token）： Bash | 复制 sudo cloudflared service install eyJhbGci... 在服务器执行该命令，自动安装 systemd 服务并连接 6.2 使用 Token 手动运行 Bash | 复制 # 前台运行 cloudflared tunnel run --token <TOKEN> # Docker 运行 docker run -d --name cloudflared --restart unless-stopped \ cloudflare/cloudflared:latest tunnel --no-autoupdate run --token <TOKEN> 6.3 本地管理转远程管理 Bash | 复制 # 获取现有隧道的 Token cloudflared tunnel token <TUNNEL_NAME> # 然后用 Token 启动，无需本地凭证文件 七、Quick Tunnel（临时隧道） 无需 Cloudflare 账户，快速暴露本地服务： Bash | 复制 cloudflared tunnel --url http://localhost:8080 输出示例： | 复制 Your quick tunnel is: https://xyz789.trycloudflare.com 域名随机生成，有效期为当前会话 适合临时演示、测试 webhook 八、Wrangler CLI 管理（2026 新增） Cloudflare 在 2026 年 3 月将 Tunnel 管理集成到 Wrangler： Bash | 复制 # 创建远程管理隧道 wrangler tunnel create my-tunnel # 列出隧道 wrangler tunnel list # 查看详情 wrangler tunnel info my-tunnel # 运行隧道 wrangler tunnel run my-tunnel # 快速临时隧道 wrangler tunnel quick-start # 删除隧道 wrangler tunnel delete my-tunnel Wrangler 会自动下载和管理 cloudflared 二进制。目前该功能为实验性。citeweb_search:1#6web_search:1#4 九、诊断与故障排查 9.1 连通性预检查（2026.5.2+） 从 cloudflared 2026.5.2 版本开始，每次启动自动执行： Bash | 复制 cloudflared tunnel run my-tunnel # 或手动诊断 cloudflared tunnel diag 检查项： DNS 解析：region1.v2.argotunnel.com / region2.v2.argotunnel.com 传输连通性：出站 UDP/QUIC 和 TCP/HTTP2 的 7844 端口 管理 API：出站 TCP/443 到 api.cloudflare.com 结果状态： ✅ Pass：正常 ⚠️ Warn：非阻塞问题（如管理 API 不可达，仅影响自动更新） ❌ Fail：阻塞问题，会提前退出并给出修复建议citeweb_search:1#2web_search:1#9 9.2 常见问题排查 现象 排查命令 服务无法启动 sudo systemctl status cloudflared 查看实时日志 sudo journalctl -u cloudflared -f 配置语法错误 cloudflared tunnel ingress validate 规则匹配测试 cloudflared tunnel ingress rule https://app.example.com 网络连通性 curl -I https://api.cloudflare.com DNS 解析 dig app.example.com 端口占用 netstat -tulpn \| grep cloudflared 9.3 清理残留连接 如果隧道因异常退出导致无法删除或重新运行： Bash | 复制 cloudflared tunnel cleanup my-tunnel 十、多服务与多设备最佳实践 10.1 单隧道多服务 一个隧道可代理多个本地服务，通过不同子域名区分： Yaml | 复制 ingress: - hostname: blog.example.com service: http://localhost:3000 - hostname: api.example.com service: http://localhost:8080 - hostname: ssh.example.com service: ssh://localhost:22 - service: http_status:404 10.2 多设备独立隧道 每台设备使用独立隧道，避免路由冲突： Bash | 复制 # 设备 A cloudflared tunnel create device-a cloudflared tunnel route dns device-a a.example.com # 设备 B cloudflared tunnel create device-b cloudflared tunnel route dns device-b b.example.com 10.3 Docker Compose 部署 Yaml | 复制 services: cloudflared: image: cloudflare/cloudflared:latest container_name: cloudflared restart: unless-stopped command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN} network_mode: host # 关键：允许访问宿主机 localhost environment: - TUNNEL_TOKEN=${TUNNEL_TOKEN} 十一、安全建议 凭证保护：cert.pem 和 <UUID>.json 文件权限设为 600，不要提交到 Git 最小权限：生产环境使用远程管理 Token，避免在服务器上保留 cert.pem TLS 验证：本地服务使用自签名证书时，仅在 originRequest 中设置 noTLSVerify: true，并确保流量经过 Cloudflare 加密 兜底规则：ingress 最后务必添加 service: http_status:404，防止未授权域名访问 自动更新：生产环境建议开启自动更新，或定期执行 cloudflared update 如需针对特定场景（如 SSH over Tunnel、负载均衡、或 Kubernetes 部署）的深入配置，可以进一步展开说明。
新增了播放器支持，源项目Github； 添加了位于左下角的 Live2D 模型，源项目地址Github； 对网站进行测速，使用cf配置优化了网站加载速度。
摘要本文详细讲解如何在 Typecho Jasmine 主题首页集成 Meting APlayer 音乐播放器，包括插件安装、模板修改、初始化脚本编写、侧边栏控制按钮添加及移动端适配等步骤，提供多种部署方案。 前言 Jasmine主题简约而又强大，配合Meting Aplayer 可以得到相当不错的体验，但是仅限于文章页面，我希望能在保留两者的强大功能的情况下，将播放器深度集成进网站，在查找源码询问ai以后一番折腾便有了如下教程，感谢ai 正文：在 Jasmine 主题首页集成 Meting APlayer 音乐播放器 前置说明 在实际部署中，三个项目的目录结构如下（并不在同一层级）： | 复制 Typecho 根目录/ ├── usr/ │ ├── plugins/ │ │ └── Meting/ ← Meting Aplayer 插件（重命名后放在这里） │ │ ├── Plugin.php │ │ ├── Action.php │ │ ├── assets/ │ │ ├── driver/ │ │ └── include/ │ └── themes/ │ └── Jasmine/ ← Jasmine 主题 │ ├── index.php │ ├── template-parts/ │ │ ├── header.php │ │ ├── footer.php │ │ └── ... │ └── assets/ └── index.php Meting-master/ 是 Meting-api 的 Node.js 源码仓库，仅用于参考或二次开发，不参与部署。实际运行时使用的是插件内 include/Meting.php（PHP 版本）。 第一步：安装 Meting Aplayer 插件 将 Meting Aplayer/ 文件夹上传至 Typecho 的 usr/plugins/ 目录 将文件夹重命名为 Meting（这是必须的，插件内部通过 pluginUrl.'/Meting/assets' 引用资源） 进入 Typecho 后台 → 控制台 → 插件，启用 "APlayer-Typecho | Meting" 确认服务器已安装 PHP 的 curl 和 openssl 扩展 插件关键配置项（后台可调） 配置项 默认值 说明 theme #ad7a86 播放器主题色 height 340px 播放列表最大高度 autoplay false 是否自动播放 order list 播放顺序（list / random） preload auto 音频预加载策略 bitrate 192 音质（128 / 192 / 320） api 自动生成 云解析地址，一般无需修改 salt 自动生成 API 接口保护盐值，一般无需修改 第二步：理解播放器的工作机制 插件激活后会自动注入以下前端资源到所有页面： header 钩子：注入 APlayer.min.css、APlayer.min.js，以及全局变量 meting_api footer 钩子：注入 Meting.min.js（负责发现 DOM 中的 .aplayer 元素并初始化播放器） content 钩子：解析文章/页面内容中的 短代码，将其渲染为 DOM 元素 因此，只要页面中存在带有正确 data-* 属性的 .aplayer 元素，Meting.min.js 就会自动将其初始化为播放器。 第三步：在 Jasmine 首页添加播放器 Jasmine 主题的首页模板是 index.php，它按顺序加载 header.php、left.php、navbar.php、文章列表、right.php、footer.php。 有两种方式将播放器嵌入首页： 方式 A：直接修改 index.php 模板（推荐） 编辑 Jasmine/index.php，在文章列表区域的上方或下方插入一个 .aplayer 容器。 注意：插入位置取决于那个区域是主题的哪个组件在负责，例如右边就查找包含right字段的文件，根据名称可定位具体位置，在对应的位置插入这一行即可 插入位置示例（在文章列表上方，导航栏下方）： Php | 复制 <?php $this->need('template-parts/navbar.php'); ?> <?php if ($this->have()): ?> 然后在 Jasmine/template-parts/footer.php 中（$this->footer() 之前），添加初始化脚本： Html | 复制 将 你的歌单ID 替换为实际的歌单 ID（如网易云歌单 436843836）。平台和类型也可改为 tencent、kugou 等。 不显示播放列表：如需保留播放器面板但隐藏列表，可使用 CSS：在 Typecho 后台 → 外观 → 设置 → 自定义头部 HTML 中添加 方式 B：通过主题 customFooter 配置注入（无需改模板文件） Jasmine 主题在后台提供了 customFooter 配置项，内容会被注入到 footer.php 第 12 行 $this->options->customFooter() 处。 在 Typecho 后台 → 外观 → 设置 → 自定义底部 HTML 中填入： Html | 复制 注意：这种方式下播放器会出现在页面 footer 区域，而非首页文章列表上方。如果只希望在首页显示，需要在脚本开头加判断： Javascript | 复制 if (window.location.pathname !== '/' && window.location.pathname !== '/index.php') return; 方式 C：仅在首页文章内容中使用短代码（最简单但需创建特殊文章） 如果你有一个专门的"音乐"页面，可以在文章中直接写： | 复制 但这种方式不能直接用于首页文章列表区域，因为 index.php 渲染的是文章卡片摘要，不会展开文章正文中的短代码。此方式仅适用于单篇文章/页面的正文。 第四步：主题调整（可选） 图标大小调整（可选）。直接编辑 Jasmine/assets/main/main.css，修改以下已有规则的数值： Css | 复制 /* 第 60-65 行，左侧栏容器尺寸 */ #left .nav-link { display: block; width: 56px; /* 原 50px */ height: 56px; /* 原 50px */ color: rgba(var(--link-color-rgba)); } /* 第 67-75 行，左侧栏图标大小 */ #left .nav-link i { font-size: 28px; /* 原 24px */ padding-top: .75rem !important; padding-bottom: .75rem !important; padding-right: 1rem !important; padding-left: 1rem !important; border-radius: var(--bs-border-radius) !important; transition: all .15s ease-in-out; } 手机端顶部导航栏和抽屉菜单的图标在 navbar.php 中通过 Bootstrap 的 fs-5 类控制大小，直接在 navbar.php 中将 fs-5 改为 fs-4 即可，或在 main.css 末尾追加： Css | 复制 /* 手机端导航图标（覆盖顶部栏、抽屉菜单、底部快捷入口） */ .navbar .nav-link i, .offcanvas a i { font-size: 24px; } 注意：抽屉菜单底部的页面图标（首页、归档、友链等）是裸 标签，不带 .nav-link 类，所以用 .offcanvas a i 统一覆盖。 第五步：音乐控制按钮侧边显示与移动端适配 Jasmine 主题的左侧栏底部（template-parts/left.php）有"切换模式"和"返回顶部"两个按钮。在它们上方添加一个音乐播放/暂停按钮，使用 Tabler Icons 的 ti-music（播放中）和 ti-music-off（已暂停）。 5.1 修改 template-parts/left.php 在底部 中，"切换模式"按钮之前插入以下 ： Php | 复制 播放音乐 <a class="nav-link p-0 d-flex align-items-center justify-content-center" id="bd-theme" 按钮默认隐藏（style="display:none!important"），等播放器初始化成功后再显示。 5.2 修改播放器初始化脚本 将第三步中的初始化脚本替换为以下版本（同时负责初始化播放器和绑定按钮事件）： Html | 复制 5.3 手机端适配 左侧栏在手机端被 d-none d-lg-block 完全隐藏，因此需要在 navbar.php 的手机端按钮区域中也添加一个音乐控制按钮。 编辑 Jasmine/template-parts/navbar.php，找到第 44 行的 ，在主题切换按钮和汉堡菜单按钮之间插入： Html | 复制 然后在播放器初始化脚本中，增加对手机端按钮的绑定。在 ap.on('pause', ...) 回调之后添加： Javascript | 复制 // 手机端按钮绑定 var btnMobile = document.getElementById('music-toggle-mobile'); var iconMobile = document.getElementById('music-toggle-icon-mobile'); if (btnMobile) { btnMobile.style.display = ''; btnMobile.addEventListener('click', function() { ap.toggle(); }); } ap.on('play', function() { if (iconMobile) { iconMobile.classList.remove('ti-music-off'); iconMobile.classList.add('ti-music'); } }); ap.on('pause', function() { if (iconMobile) { iconMobile.classList.remove('ti-music'); iconMobile.classList.add('ti-music-off'); } }); 5.4 工作原理 播放器初始化成功后，脚本将 APlayer 实例挂载到 window.homeAPlayer 同时将 #music-toggle-wrap 的 display 设为空，使按钮可见 点击按钮调用 ap.toggle() 切换播放/暂停 通过 APlayer 的 play 和 pause 事件同步切换图标：播放中显示 ti-music，暂停时显示 ti-music-off 鼠标悬停在按钮上时，左侧弹出的提示文字也会动态更新（"播放音乐" / "暂停音乐"） 第六步：PJAX 兼容（如适用） 如果 Jasmine 主题启用了 PJAX（页面无刷新切换），播放器在页面切换后不会自动销毁和重建。需要在主题的 PJAX 回调中添加： Javascript | 复制 // 页面切换前销毁播放器 if (typeof aplayers !== 'undefined') { for (var i = 0; i < aplayers.length; i++) { try { aplayers[i].destroy(); } catch(e) {} } } // 页面加载后重新初始化 if (typeof loadMeting === 'function') { loadMeting(); } 快速验证清单 Meting 插件文件夹已命名为 Meting 并放在 usr/plugins/ 下 插件已在 Typecho 后台启用 服务器有 curl 和 openssl PHP 扩展 首页源码中能看到 元素 浏览器控制台无 JS 报错 播放器能正常加载歌曲列表并播放 支持的音乐平台与资源类型 平台 code 单曲 专辑 歌单 歌手 网易云音乐 netease song album playlist artist QQ 音乐 tencent song album playlist artist 酷狗音乐 kugou song album playlist artist 示例：播放网易云歌单 | 复制 示例：播放自定义音频 | 复制