PHP生成含中文验证码的核心难点是字体路径错误、FreeType未启用或字符编码非UTF-8,需用imagettftext()配合真实.ttf文件,确保function_exists('imagettftext')为true,并用preg_match_all('/./u')正确拆分UTF-8中文字符,避免乱码与显示方块。

PHP 生成含中文的验证码图片,核心难点在字体和编码
直接用 imagefttext() 画中文却只显示方块?大概率是字体文件路径错、没启用 FreeType 或字符编码不是 UTF-8。PHP 默认不带中文字体,imagettftext()(注意是 imagettftext,不是 imagestring)必须配真实存在的 .ttf 文件,且 PHP 编译时得有 --with-freetype 支持(Ubuntu 下装 php-gd 包通常已含)。生成前务必确认:function_exists('imagettftext') 返回 true,否则整个流程会静默失败。
验证码字符串里混用英文、数字、中文,要注意随机性和去歧义
中文字符不能靠 range('一','龥') 直接取——PHP 字符串是字节流,UTF-8 下一个汉字占 3 字节,substr() 或 str_split() 会截断乱码。正确做法是先用 preg_match_all('/./u', $str, $matches) 拆成字符数组。选字时避开易混淆字:比如「O」和「零」、「l」和「一」、「0」和「〇」。建议预定义白名单:
$chars = array_merge(
range('a', 'z'), range('A', 'Z'), range(0, 9),
['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '中', '国', '人', '民']
);用 array_rand($chars, 4) 抽索引再拼接,比 mt_rand() 手动循环更稳。
GD 绘图时中文偏移、重叠、换行,本质是字体度量没对齐
imagettftext() 的 y 坐标是文字基线(baseline)位置,不是顶边。中英文混排时,英文字体和中文字体的 ascender/descender 高度不同,强行用同一 y 值会导致上下错位。解决方法:固定字号(如 24px),用 imagettfbbox() 测单字实际包围盒,取最大高度作为行高,每个字符 y 值 = 上一行 y - 行高 + 常量偏移(例如 +10)。常见坑:
立即学习“PHP免费学习笔记(深入)”;
- 字体文件路径写相对路径(如
'./simhei.ttf'),但脚本 CLI 运行时工作目录不是 Web 根目录 → 改用__DIR__ . '/simhei.ttf' - 中文字符传入前没
mb_convert_encoding($char, 'UTF-8', 'auto')→ GBK 环境下传入 GB2312 字符会崩 - 没调
header('Content-Type: image/png')就输出图像 → 浏览器当文本渲染,显示乱码
验证码 session 存储中文值,必须确保 session 编码一致
存到 $_SESSION['captcha'] 的值要是原始字符串(如 '三七中'),不是 HTML 实体或 URL 编码后的结果。检查 php.ini 中 default_charset = "UTF-8",且 session 启动前不能有任何输出(包括 BOM)。验证时用严格比较:if (mb_strtolower($_POST['code']) === mb_strtolower($_SESSION['captcha'])),避免大小写或全半角问题。若发现 session 里中文变问号,用 bin2hex($_SESSION['captcha']) 查看是否存储阶段就已损坏——大概率是 session.save_handler 用了非 UTF-8 兼容的存储后端(如旧版 memcached)。
中文字体文件体积大、渲染慢,线上环境别每次生成都读磁盘;把字体资源用 file_get_contents() 读一次缓存进静态变量,或者改用 imagick 扩展(支持字体缓存),否则并发稍高就会卡住。



















