
先说结论:这个博客现在长这样了,左边是「神经虚空」,右边是「熔岩玻璃」,你在页面右上角点一下就能换。

神经虚空,深色模式。首页那句大字是我自己的口头禅,后面会说。
这篇是个流水账式的总结,记一下这次对博客主题 Joe 动的手术:换了什么、为什么换、踩了哪些坑,还有哪些东西我自己都没测到。内容比较多,下面有目录,不想看的可以直接跳。
- 起因:它太老气了,还慢
- 先画三个样板,再选两个做进去
- 神经虚空:便当盒布局,背景里有张会动的网
- 熔岩玻璃:毛玻璃导航,宋体大字,卡片左一下右一下
- 内页也得跟上
- 白屏那几秒,我真的受够了
- 还得改:加载遮罩要第一时间出来
- 前端库升级:一堆老古董终于下岗了
- 适配 PHP 8.5:以及我自己接手了八个插件
- 去外联:能不连别人家,就不连
- SEO:给搜索引擎看的那一面
- 安全:该关的门都关一遍
- 其他小改动
- 改动一览
- 这次是怎么干的:让一群 AI 同时干活
- 我没测到的地方
- 上线前我要过一遍的清单
- 接下来还想折腾的
- 最后
起因:它太老气了,还慢
页面老气,一眼就是好几年前的味道。首屏慢,文章列表是 JS 加载完再一条条画出来的,打开首页先白一会儿,再蹦出东西。最要命的是没特色,换个名字换个头像,跟别人家的站长得差不多。
我想要的东西其实很朴素:黑红配色,深色浅色都要有,看着大气一点,最好带点科技感、未来感,有那么一点 AI 的味道,布局上能有个亮点。听着像甲方,但甲方是我自己,乙方也是我自己,所以没人能打我。
先画三个样板,再选两个做进去
我没有直接上手改,先出了三个首页样板,单独的 HTML 文件,用来看感觉:
- 神经虚空
- 红色信号
- 熔岩玻璃
看了半天,A(神经虚空)和 C(熔岩玻璃)我都舍不得扔。扔掉一个吧,心疼;两个都留,那就两个都做进主题,让访客自己选。
于是现在右上角有个「皮肤」按钮,点开能选神经虚空、熔岩玻璃,或者「随机」。第一次来的人默认随机一个,选完会记住,用 cookie 存一年。明暗模式也是一样的思路,右边挨着有个小太阳小月亮的按钮。

右上角的皮肤切换,左边是神经虚空(浅色),右边是熔岩玻璃(深色)。
首页大字我用的是:
世界上只有一个问题,那就是时间问题!
所有答案,都在时间里。
这句话是我的口头禅。写代码拖延了,说时间问题;服务器没钱续费了,说时间问题;文章一直没写完,还是时间问题。反正万能。
神经虚空:便当盒布局,背景里有张会动的网
第一套皮肤叫神经虚空,英文名 void。布局上用的是 bento 网格,就是那种日式便当盒一样、大小不一的格子拼在一起。置顶大图占一大块,推荐的小图放右边,下面一排热榜卡片,再往下才是文章列表和侧栏。
背景是个 canvas 画的神经网络,一堆小点点连着线,慢慢飘。它纯属装饰,不干正事,所以我没让它太闹腾。

同一套皮肤,浅色模式。红色换成了稍微深一点的酒红,不然白底上太扎眼。

往下滚一屏,大图、推荐、热榜一格一格摆好。
浅色模式不是随便把背景改白了事。每个颜色我都要对一遍,深色底上好看的亮红,搬到白底上就刺眼,得换一套。
熔岩玻璃:毛玻璃导航,宋体大字,卡片左一下右一下
第二套叫熔岩玻璃,英文名 ember。如果说神经虚空是冷冷的科技风,这套就是暖一点、有质感一点,背景像烧红的炭火在玻璃后面透光。
导航是悬浮在页面上的一条玻璃,标题用宋体大字,笔画粗粗的,跟满屏的无衬线字体不一样,看着一下子就有杂志的味道。文章列表是左右交错的卡片,一条图在左,下一条图在右,眼睛不会一直沿着同一条线往下溜。热榜做成一条横着的卡带,可以左右看。

熔岩玻璃,深色。

熔岩玻璃,浅色。这张我自己挺满意,暖粉色的底。

文章卡片左右交错。图在左,下一张就在右。
内页也得跟上
首页好看不算完。文章页、归档、留言、404、搜索、统计这些内页,两套皮肤、明暗两种模式,全都配了一遍,加起来是四种组合,每个页面都得过一遍。旧的那套皮肤我直接删了,留着只会让代码越来越乱。
之前 Joe 自带的夜间模式也还在,我让它跟新的明暗切换互相不打架。这俩都要往 <html> 上打标记来决定当前是明是暗,得让它们别互相覆盖。

文章页,神经虚空深色。这是截的左半边,右边侧栏里有访客的评论,我不放。

文章页,熔岩玻璃浅色。头像是预览站离线时的占位图,线上是正常头像。
手机上也看了,大字会缩,导航收成汉堡按钮,皮肤和明暗按钮还在。

手机端,左边神经虚空深色,右边熔岩玻璃浅色。
白屏那几秒,我真的受够了
前面说过,原版 Joe 的首页文章是 JS 异步渲染的:页面先出来,一堆骨架占位,JS 加载完了再去拉数据、画列表。网速一般的时候,你盯着个空壳等好几秒。
我这次改成了服务端直接把首屏文章吐出来,HTML 到了就是有内容的。再加了一个首屏的加载动画(preloader),页面还没准备好之前盖一层,准备好了淡出。
另外还有一些很细碎的活儿:Logo 和第一张 banner 图加了 eager 和 fetchpriority,意思是别懒加载,优先下载;所有图片补上宽高,免得加载完把页面顶来顶去。

加载动画。左边神经虚空,一个转圈的六边形加进度条;右边熔岩玻璃,一块玻璃卡片,写着「正在点燃余烬」。
还得改:加载遮罩要第一时间出来
这一节是个还没做完的活,先写下来,免得我自己忘了。
上面那个加载遮罩有个毛病:它是页面的一部分,得等页面的 HTML 和样式回来了才能出现。碰上网络不太好的时候,比如 HTTP/3 连接建立得慢,用户会对着一片空白干等,遮罩根本没机会上场,等于白做。
我现在的想法是,服务器吐出第一口数据的时候,先把遮罩吐出去。样式直接内联在 HTML 最前面,不要依赖任何外部文件,不然样式文件没回来,遮罩也是光秃秃的。再加一个兜底的超时,超过一定时间不管页面好没好都把遮罩撤掉,别把访客关在里面出不来。
这个正在改,还没上线,我先把方案定下来,确认没问题再动手。
前端库升级:一堆老古董终于下岗了
主题自带的那些前端库,很多版本停在好几年前。有的有官方登记在案的安全漏洞(就是 CVE 编号那种),有的只是老,但老到一定程度也让人睡不着。我挨个给升了:
Swiper 从 5.4.5 升到 6.8.4,5.4.5 有个编号 CVE-2021-23370 的漏洞。jQuery 从 3.5.1 升到 3.7.1。Fancybox 我干脆换成了 GLightbox 3.3.1。Prism 从 1.23 升到 1.30.0,修掉了 CVE-2024-53382。Mermaid 从 8 一路跳到 12.3.0。MathJax 换成 3.2.2,ECharts 从 5.1.1 升到 5.6.0。
后台编辑器 EditorMD 里面藏着的东西也不能放过:内置的 Marked 从 0.3.3 升到 4.3.0(0.3.3 也有漏洞),Underscore 从 1.8.2 升到 1.13.8,CodeMirror 升到 5.65.21,KaTeX 是 0.19.1。PayButton 那个插件里居然还带着 jQuery 1.10.2,也换成了 3.7.1。
WOW 和 animate 精简了一下,Clipboard 直接换成浏览器原生的写法。
这些文件有大有小。Mermaid、MathJax、ECharts 这几个大家伙,我走自己搭的一个 jsdelivr.orzlee.com 镜像,实时回源,我拿 sha256 对过,和官方的一模一样。小文件就跟着主题自己托管。要是镜像挂了,有降级的路子,不至于整页脚本跑不起来。
适配 PHP 8.5:以及我自己接手了八个插件
线上现在已经是 PHP 8.5.12 了。主题和我用的插件里,好几个原作者早就不更新,在新版本 PHP 上跑要么报一堆弃用警告,要么直接炸。
我索性全接过来自己维护:主题本体加上 8 个插件,分别是 AjaxComment、AutoSlug、Comment2Telegram、EditorMD、MarkdownParse、PayButton、Sitemap 和 waterMark。版本号我在原版本后面加了个 -orz.1,比如 Joe 现在是 7.3.7-orz.1,一看就知道是我动过的,不会跟官方版本混在一起。
这个过程里翻出来不少问题,下面这几个我印象最深。
评论一提交就 500
AjaxComment 在 Typecho 1.3.1-rc 上,评论提交直接 500。原因有两个:代码里用的 $post->pathinfo 在新版里已经没有了,还有 $options->index() 返回的是 null,后面拿着它接着往下用。这两处看着不起眼,结果就是访客点了「发送评论」,页面啪的一声挂了。
Telegram 通知,任何评论都 500
Comment2Telegram 原版更离谱:任何评论提交都会 HTTP 500。查了半天,原因是钩子只传了 1 个参数,但回调函数要求 2 个。这种参数数量对不上的调用,在 PHP 8 里是要直接报错的。也就是说它在这个版本上,压根就没好过。
目录里写七级标题,页面直接崩
MarkdownParse 的 [TOC] 功能,文章里一旦出现 7 级以上的标题就会致命错误。七级标题谁会写呢?但是就是有人手滑多敲了几个 #,一页内容就没了。
水印失败,原图没了
waterMark 原版在加水印失败的时候,会把原图弄丢。我改成先写到临时文件,校验没问题了再替换原图,最差也就是没有水印,不会没有图。
另外两个
PHPMailer 里有处 magic_quotes 的判断,PHP 8 下带附件发信会 Fatal。Joe 读设置的时候,碰上空表会抛 TypeError。都修了。
怎么确认修对了
我没有直接在线上试,那样太刺激了。我自己编译了一份 PHP 8.5.12,用 SQLite 把线上的真实数据导进来,搭了个预览环境。然后全站 55 个页面逐个请求,全部 200;另外还拿 106 组 Markdown 内容,对比改之前和改之后的输出,一模一样才算过。
去外联:能不连别人家,就不连
原版 Joe 里有很多功能是接别人家的接口:歌曲播放(APlayer)、影视、直播、壁纸。这些东西一部分有版权问题,一部分接口早就失效了,360、虎牙、maccms、vvhan、i-meto 这些,我全移除了,连带它们的接口。
另外还有几处在页面里悄悄发请求的:78.al 的公告请求、api.aim.moe、api.owo-bo.cn 的更新公告,也都删了。你的博客每次打开,没理由去问别人家服务器今天有没有公告。
Gravatar 改成了 cn.gravatar.com,默认头像用 wavatar。顶部导航原来是写死的,现在由后台设置控制(JNavMaxNum、JCustomNavs),想放几个、放什么,自己填。作者信息改成了 orzlee 和 orzlee.com,旧的默认值在渲染的时候会自动映射过来,不需要手动去数据库里改。

侧栏作者卡,左边神经虚空,右边熔岩玻璃。头像是预览站离线时的占位图。
SEO:给搜索引擎看的那一面
这块不太好看,但花了功夫。加了 canonical(告诉搜索引擎哪个才是正式地址)、OpenGraph(别人分享链接时的卡片信息)、JSON-LD 结构化数据(WebSite、BlogPosting、BreadcrumbList 三种)。搜索页设成 noindex,follow,别让站内搜索结果污染收录。keywords 和 description 做了转义和回退,文章没写摘要的时候,也不会输出一个空标签。另外还有个 sitemap 插件,生成站点地图。
安全:该关的门都关一遍
安全这块改完之后最不容易被人看出来,但真出了事就是大事,所以我宁可多翻几遍。
评论加了图片验证码,是我自己写的 SVG 验证码,不依赖 GD 库,字形做了打乱来对抗简单的识别,每个验证码一次性有效(nonce)。如果存放验证码的地方不可写,会直接拒绝评论,而不是放行,宁可不让人评论也不开后门。错误提示也区分得很细:没填就提示请输入,填错了提示验证码错误,放太久提示已过期。

评论框,昵称和邮箱默认留空,多了一格验证码。
这里还有个很烦人的小坑:验证码填错,页面却提示「发言频繁」。原因是 Typecho 核心先检查发言间隔,再跑过滤器;所以验证码还没校验,就已经因为间隔判断先报错了。我改成把验证码预检放在前面。
后台地址已经改名了,不再用默认的那个,而且新增了一个 LoginGuard 插件:登录要验证码,同一个 IP 连续失败 5 次,锁 15 分钟,默认把 XML-RPC 也封了,那个入口我自己从来不用。
零碎的还有一堆:Cookie 加了 secure 和 SameSite=Lax;Telegram 的 webhook 加上了 secret_token 校验;浏览量和点赞数改成原子计数,防止被人刷;分页和排序参数做了白名单;好几个接口加了限制和鉴权;备份下载加了 CSRF 校验;还删了 library/files.php 这个老旧的工具文件,留着它纯属给人送机会。
其他小改动
设置页那段脚本,原来有地方没内联好,导致设置页有时候是空白的,修了。顶部那个小人图标我去掉了,觉得多余;改成登录之后,头部右边会显示管理员的名字,点一下进后台。评论的错误提示也写得更准确了。

登录后头部显示管理员名字,上面是神经虚空,下面是熔岩玻璃。这是预览站里的测试账号。
改动一览
| 类别 | 改前 | 改后 |
|---|---|---|
| 皮肤 | 一套老气的默认皮肤 | 神经虚空 + 熔岩玻璃,各自明暗双模式,右上角切换 |
| 首页渲染 | JS 异步渲染,先白屏 | 服务端首屏直出,加 preloader |
| 前端库 | Swiper 5、Prism 1.23 等多个老版本,有漏洞 | 全部升级,大文件走自建镜像 |
| PHP | 多个插件在新版本上直接报错 | 主题和 8 个插件自己维护,PHP 8.5 可用 |
| 外部依赖 | 歌曲、影视、直播、壁纸,公告请求 | 全部移除 |
| SEO | 基础标签 | canonical、OpenGraph、JSON-LD、sitemap |
| 安全 | 评论无验证码,后台默认地址 | 验证码、登录防护、Cookie 与接口加固 |
这次是怎么干的:让一群 AI 同时干活
说实话,这么大的工作量,我一个人干不完。这次我用了好几个 AI 子代理并行干活,分工是设计、实现、审查、安全审计和验收。一个负责写,一个专门挑刺,一个专门审安全,最后另一个来验收。
我的体会是:AI 很能写,但它说「改好了」这件事,一个字都不能信。每次改动都要真的跑起来看,所以才有了前面说的那个预览环境,PHP 8.5 真的把页面渲染出来,而不是看着代码觉得应该没问题。
踩的坑也不少,除了前面那两个 500,还有个版本错位的:某一次主题文件和设置页脚本的版本对不上,结果设置页一片空白,排查到最后才发现是版本错位,跟数据没关系。
我没测到的地方
这一块我想老实说,免得你们以为一切都完美。
MySQL 方言那块我在本地没法测。预览环境用的是 SQLite,而归档页里有个 FROM_UNIXTIME 是 MySQL 才有的函数,我没法在预览站里真的跑一遍。上线之后我会盯着点,去归档页抽查。
GD 水印也没实测。那个 waterMark 的改动,逻辑我看着没问题,但真去传张图加水印试,我没做。
还有 EditorMD,它在浏览器里的表现没测。库是升级了,但后台编辑器点起来顺不顺手,要等我真的写篇文章才知道。
上线前我要过一遍的清单
- 首页、文章页、归档页,两种皮肤各点一遍,明暗各切一次
- 提交一条评论,看验证码、Telegram 通知、邮件提醒是不是都正常
- 归档页看有没有报错(就是前面说的 MySQL 那块)
- 传一张图,看水印好不好使
- 后台写一篇带公式、带图表的文章,看编辑器
- 浏览器控制台看一眼,有没有被拦掉或者加载失败的脚本
接下来还想折腾的
Live2D 看板娘在评估,不一定做,我担心它拖慢首屏。后台界面还想动一动,现在的后台跟前台比有点格格不入。再就是更多的皮肤,上一节说的那个红色信号,样板还在,说不定哪天心血来潮就做进去了。当然,前面说的加载遮罩要提前出,也还在改。
最后
写到这里,这个博客算是换了一副新骨头。如果你点进来看到了新皮肤,觉得哪里别扭、哪里没对齐、哪里在你的手机上炸了,欢迎直接在下面评论,我看到会改。
对了,评论框现在有验证码了,如果你填错了,别骂我,真的是为了挡垃圾评论。


评论 (0)