博客评论区传图这件事,折腾了一轮终于弄利索了。中间踩了不少坑,记下来,免得以后重新来一遍。


一、先交代一个背景:SSH 被人盯上了

博客跑在腾讯云 Lighthouse 上,一直开着 22 端口。某天翻控制台的登录日志,全是别人的爆破尝试,密码试个不停。我索性把公网 22 关了,结果把自己也关在了外面,远程干活只能绕道宝塔的网页终端,别扭。

后来把 SSH 换到一个高位非标端口,同时关掉密码登录、只留密钥。爆破脚本默认只扫 22,换端口之后日志就清净了。这里具体用了哪个端口就不写了——写出来等于告诉人往哪打。治标,但对个人小站够用。

后面处理评论区传图,全是在这个新通道上操作的。

二、问题:评论区图床按钮一直传不上去

iro 主题(Sakurairo)的评论区自带一个传图按钮,后台可以选图床:imgur、SM.MS、Chevereto、Lsky Pro 这几家。

Image

我一直没去动它,默认用的 imgur。直到某天想发张截图,才发现点了没反应。

查了一下后台,两个原因叠在一起:

  1. imgur_client_id 是空的——没填鉴权令牌,imgur 那边根本不收。
  2. 就算填了也没用,imgur 的接口域名在国内访问不通,被墙了。

所以这个按钮从一开始就是坏的,只是我从来没真去用而已。

三、iro 主题的上传流程,源码里长什么样

iro 是开源主题,直接翻源码比瞎猜快。

前端按钮触发后,请求打到:

POST /wp-json/sakura/v1/image/upload

字段名是 cmt_img_file。后端拿到文件后,并不直接落盘,而是根据后台 img_upload_api 的设置,把文件再转发给你选的那家图床,等图床返回一个图片 URL,再把这个 URL 拼回评论输入框。

也就是说,iro 本体只做一个"转发中介",图是存在第三方图床上的。

看了一遍四种图床的转发代码,发现只有 Lsky Pro 允许自定义上传地址(lsky_url),其余三家要么域名写死,要么国内连不上。这是个突破口。

Lsky Pro 的请求长这样:

POST {lsky_url}/api/v1/upload
Authorization: Bearer {lsky_api_key}
multipart/form-data, 字段名 file

成功时它期望的响应是:

{
  "status": true,
  "data": { "links": { "url": "https://..." } }
}

主题后端取 data->links->url 当作图片地址。

四、思路:与其找个真图床,不如自己"伪装"一个

个人博客用第三方图床,有几个绕不开的坑:免费额度、外链防盗、哪天服务商跑路图片全挂。

我自己的服务器上跑着 WordPress,磁盘有富余。与其依赖别人,不如让这些评论图片直接存到自己服务器上。

但问题是:iro 主题只认它写死的那几种图床协议,后台下拉框里没有"存到我自己服务器"这个选项。

那就绕开主题。我写一个能接收 Lsky Pro 格式请求的接口,让主题以为自己在跟一个真的 Lsky Pro 图床通信,实际图落在我自己的 WP 目录里。

具体做法:

  1. 后台把图床从 imgur 切成 Lsky Prolsky_url 填成自己的域名,lsky_api_key 填一段自设的密钥。
  2. iro 就会把文件转发到 https://<我的域名>/api/v1/upload
  3. 我在 Nginx 里加一条规则,把 /api/v1/upload 转发到 WordPress 的一个 REST 路由。
  4. 路由背后是一个 mu-plugin,负责接收文件、校验、压缩、存盘,最后按 Lsky 的格式返回 URL。

iro 那边只知道自己成功传图给了一个叫 Lsky 的东西;实际图全在自己服务器上,不受第三方图床存亡影响。

五、存储策略:不和文章媒体库混在一起

图片要存自己服务器,但我特意没让它进 WordPress 默认的 wp-content/uploads/

原因很简单:媒体库是给文章插图用的,归后台管理,有自己的一套文件命名和清理逻辑。评论图片是用户上传的、公开的、量可能不小,混在一起,后台管理媒体库的时候会很乱。

所以单独建了一个目录,比如 wp-content/comment-img/年/月/(实际命名可随意),跟媒体库彻底分开。

六、压缩:保持原尺寸,只降画质,统一转 JPEG

评论里的截图,大多是聊天记录、报错信息这类,里面有一堆文字。这类图最怕两件事:一是被压缩到模糊看不清字,二是体积太大拖慢页面。

所以定了这么几条规则:

  • 尺寸不变。不缩放,原图多大输出多大。缩放最容易把图里的字弄糊。
  • 只降画质,统一转成 JPEG,质量压到 82。
  • GIF 动图只取首帧转成 JPEG。评论区我暂时不需要动图,动图转 JPEG 反正也动不了。
  • 带透明的 PNG/WebP,转 JPEG 前先铺一层白底——JPEG 不支持透明,不铺的话透明区域会变黑。
  • 上传完立刻压,不保留原始文件,省空间。

实测效果:一张 800×500 的 JPEG,原图 9.4KB,压完 8.7KB。压缩后视觉上基本没区别,但文件小了约 8%。真正的收益在那些没压缩过的大 PNG 截图上,效果会更明显。

七、安全:不开放注册,来的全是游客,传图权限怎么给

我一开始的想法是"只有登录用户才能传图",这样最安全。但这是个人博客,我没开放用户注册,评论的人全是游客。真设成只能登录用户传,等于所有评论者都用不了,这功能就白做了。

所以改成:任何人都能传,防御全压在接口这一层

游客上传,最怕的就是有人往服务器传 WebShell(一句话木马那种)。针对这个,做了几道检查:

  1. 扩展名黑名单.php.phtml.phar.svg 这类直接拒。
  2. MIME 白名单:只收 jpeg/png/gif/webp,用 finfo 读文件真实类型,不看上传时自称的类型。
  3. 魔数校验:再对一下文件头的二进制签名,确认确实是图片。
  4. GD 重新解码再编码。不管上传的文件里塞了什么,都先用 GD 解码成位图,再重新编码成 JPEG。重编码出来的东西只剩像素,藏在文件里的 PHP 代码、注释、EXIF 载荷,在这一步全被丢掉。
  5. Nginx 再补一刀:图片目录里禁止执行任何 PHP。就算前面哪道校验漏了,真混进来一个 .php,Nginx 也会直接拒掉。

前几道防的是"伪装成图片的恶意文件",第四道是从文件本身下手:PHP 代码过不了一轮"解码→再编码"。

我实测过:造了一个合法的 PNG,在文件尾部追加一段 <?php @eval($_POST[0]); ?>(网上说的"图片马"就是这么做的),然后传上去。接口正常返回一个 JPEG,我 grep 了一遍输出文件,里面没有任何 eval_POST<?php 残留。

另外几条限制也一起设了:

  • 单张不超过 5MB。
  • 图片长或宽任一边超过 5000 像素,直接拒绝。
  • 每个 IP 60 秒内最多传 3 张,防止有人刷爆磁盘。

八、几个部署上的小坑

Nginx 的转发

iro 固定请求 /api/v1/upload 这个路径,但 WordPress 默认不认识它。我在 Nginx 里加了一条:

# 把 /api/v1/upload 转发到 WordPress 的 REST 路由(路由名以实际为准)
location = /api/v1/upload {
    rewrite ^ /index.php?rest_route=/my-plugin/v1/comment-upload last;
}

把请求 rewrite 到 WordPress 的 REST 路由上。rewrite ... last 会重新走一遍 location 匹配,把 index.php 交给 PHP-FPM,POST 的 body 会自动跟着透传过去。这块当时担心不靠谱,实测一发请求就通了。

图片目录别让它列出来、别让它执行

# 目录名以实际为准,核心是:禁 php 执行 + 禁目录列表
location ^~ /wp-content/comment-img/ {
    location ~ \.php$ {
        deny all;   # 目录里禁止执行 PHP
    }
    location ~ /\. { deny all; }   # 禁止访问隐藏文件
    expires 30d;
    access_log off;
}

主题设置存在哪个 option

排查时还发现一个坑:iro 主题的设置并不是存成 sakura_option,而是存在 iro_options 里。一开始我按惯例去读 sakura_option,读出来全是空的,绕了一下才找到真正的位置。

九、上线后测试

接口写完,在服务器上用 curl 发了几轮真实请求验证,覆盖了正常和异常两种情况:

场景结果
正常 JPEG 上传通过,返回 URL,文件落在独立目录
透明 PNG转成白底 JPEG,尺寸不变
图片宽 5001px(超限)拒绝,返回"尺寸过大"
伪装成图片的文本拒绝
图片改名成 .php 上传拒绝(扩展名黑名单)
不带鉴权令牌拒绝
图片马(PNG 尾部塞 PHP)上传成功但输出已消毒,无代码残留

最后在浏览器里,把 iro 后台的图床从 imgur 切到 Lsky,填上 URL 和密钥,清掉之前那个 weserv.nl 图片代理前缀(那也是国外服务,留着反而添乱),在前台发了一条带图的评论——成了。

十、小结

这次折腾下来,几个经验算是记住了:

  1. iro 主题传图,本质是"转发到第三方图床",不是本地存图。想让它存自己服务器,别硬改主题,自己写个兼容协议的接口更省事,还不用怕主题升级把改动冲掉。
  2. 个人博客不开放注册,就别硬套"登录才能用"那套。游客场景下,安全靠的是接口层的文件校验和重绘消毒,而不是账号体系。
  3. 图片马的死穴是"重编码"。只要输出前强制走一遍解码→再编码,藏在文件里的任何附加载荷都活不下来。
  4. 改服务器配置前先备份,一条 nginx -t 也花不了几秒,出问题能立刻回滚。

现在评论区传图能用了,图在自己服务器上,也不用担心哪天图床跑路。

欢迎来到XiaoZou123,这里是一个电脑极客、数码爱好者网站。我平时喜欢关注数码新闻,研究计算机技术。如果你看我头像觉得我是二次元,那我其实还算不上!
最后更新于 2026-09-06