验证码不显示主因是输出缓冲区被污染,需在renderImageByGD()开头加ob_clean()兜底;必须满足GD启用、路由配置正确、URL可访问三条件;修复后需清OPcache、Composer autoload及浏览器缓存。

验证码不显示,90%不是配置问题,而是输出缓冲区被污染了——图像生成前已有任何输出(空格、BOM、echo、错误提示),header() 就会失效,浏览器收到的就不是 PNG 流,而是带 HTML 或乱码的响应。
为什么本地能显示、线上就不行?
常见原因是文件编码或部署环境差异:
- 本地编辑器保存时自动加了 UTF-8 BOM 头,线上 PHP 解析到 BOM 就算“已输出”,
header()调用直接失败 - 线上开启了
display_errors = On,PHP 错误(比如 Notice)在CaptchaAction执行前就被输出到缓冲区 - 某些中间件(如 Nginx 的 fastcgi_buffering、CDN 缓存)提前截断了二进制响应
-
ob_start()在别处被调用但没配对ob_end_flush(),导致缓冲区残留
必须检查的三个硬性条件
缺一不可,否则连执行到图像生成阶段的机会都没有:
-
GD扩展已启用:运行php -m | grep gd,确认输出含gd;若无,需安装并重启 PHP -
CaptchaAction路由可访问:确保控制器的actions()方法返回了'captcha' => ['class' => 'yii\captcha\CaptchaAction'],且没被AccessControl规则拦截(例如'actions' => ['login']里漏掉了'captcha') - 请求 URL 可直接访问:在浏览器打开
/index.php?r=site/captcha(按实际路由调整),不应返回 HTML 页面或 404,而应是空白页或损坏图像——这说明至少路由通了
最有效的修复:在 renderImageByGD() 开头加 ob_clean()
这是绕过所有前置输出污染的兜底方案,修改位置固定:
打开 vendor/yiisoft/yii2/captcha/CaptchaAction.php,定位到 protected function renderImageByGD($code) 方法,在 ob_start() 调用之前插入:
if (function_exists('ob_clean')) {
@ob_clean();
}
注意不要加在方法末尾或 imagepng() 后面——那时 header 已发,再 clean 没用。必须在 ob_start() 前,且最好在函数开头第一行。
如果项目用了 himiklab/yii2-captcha-component,同样找它的 CaptchaAction.php,改法一致。
容易被忽略的细节
改完代码不生效?可能卡在这几个地方:
- Composer 自动加载未更新:执行
composer dump-autoload,尤其当你把CaptchaAction重写到自定义命名空间后 - OPcache 缓存了旧字节码:重启 PHP-FPM 或清空 OPcache(
opcache_reset()) - 浏览器缓存了失败响应:强制刷新(Ctrl+F5)或用隐身窗口测试,避免 304 或缓存的空响应干扰判断
- 日志没开:在
config/web.php中确保'log' => ['targets' => [...]]启用,查看runtime/logs/app.log是否有 GD 相关警告
真正麻烦的从来不是加一行 ob_clean(),而是得先确认它到底有没有被执行——有时候你改了源码,却调用的是另一个路径下的同名类。


















