根本原因是字符集未做视觉隔离,php默认62字符池含11个混淆字符;应先用array_diff()剔除黑名单$confusing,再shuffle()切片生成验证码,避免array_rand()警告。

为什么“0”和“O”、“1”和“l”总被用户输错
根本原因不是字体渲染问题,而是字符集本身没做视觉隔离。GD 绘图再清晰,如果源字符串里同时存在 0 和 O,用户在小屏或弱光下根本分不清。PHP 默认的 range('0','9') + range('a','z') + range('A','Z') 会把所有 62 个字符全塞进去,其中至少 8 个是高危混淆对(0/O、1/l/I、2/Z、5/S、8/B)。不从源头过滤,后期加扭曲、加噪点反而增加人类识别负担。
用 array_diff() 快速剔除混淆字符
这是最轻量、兼容性最好的方法,适合已在线上运行的老项目做渐进式修复。关键不是“怎么删”,而是“删哪些”——直接复用经过验证的黑名单:
- 定义混淆数组:
$confusing = ['0', 'O', '1', 'l', 'I', '2', 'Z', '5', 'S', '8', 'B']; - 构造原始池:
$all = array_merge(range('a','z'), range('A','Z'), range('0','9')); - 执行差集:
$safe = array_values(array_diff($all, $confusing));(array_values()必须加,否则array_rand()可能报错) - 生成验证码:
$code = implode('', array_map(fn($i) => $safe[$i], array_rand($safe, 4)));
注意:别用 str_replace() 处理字符串,因为大小写敏感且易漏掉变体(比如 'o' 小写没在黑名单里)。
preg_match() 校验法只适合补救场景
如果你不能改字符池(比如第三方 SDK 封装了生成逻辑),就只能在生成后做拦截。但必须用否定式正则,否则逻辑反了:
- 错误写法:
preg_match('/[0OlI1]/', $code)—— 匹配到就丢弃,但没匹配到的字符串可能含2或Z,依然混淆 - 正确写法:
!preg_match('/^[^0OlI12Z5S8B]+$/i', $code)—— 全字符都不在黑名单里才通过 - 循环重试时加防死锁:
for ($i = 0; $i
这个方案性能差(尤其长度 > 5 时重试概率飙升),仅建议用于临时 patch 或灰度验证。
生成无重复字符时,shuffle() 比 array_rand() 更稳
很多人用 array_rand($safe, 4) 生成索引,但若字符池不够大(比如只剩 3 个可用字符),array_rand() 会警告并返回空数组。更可靠的做法是打乱后切片:
$shuffled = $safe; shuffle($shuffled);$code = implode('', array_slice($shuffled, 0, 4));- 如果担心池子太小导致截取不足,先判断:
if (count($safe)
真正容易被忽略的是:混淆字符剔除后,剩余字符数可能跌破验证码长度要求。32 字符白名单(如 'ACDEFGHJKMNPQRTUVWXY34679')只够生成 4~5 位码,6 位需扩展或降级容忍度。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











