Laravel如何实现登录页面验证码语音播报功能?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1522个文字,预计阅读时间需要7分钟。
Laravel 官方并未提供语音验证码(TTS)功能,但你可以使用以下方法实现:
常见错误现象:Call to undefined function App\Http\Controllers\textToSpeech()(误以为 Laravel 内置了 TTS 函数);或前端点击“听语音”没反应,其实是后端没返回可播放的 audio/mp3 地址。
- 必须选一个 TTS 服务商:阿里云
aliyun-openapi-php-sdk、腾讯云tencentcloud-sdk-php、百度 AI 的php-sdk,或更轻量的开源方案如espeak-ng(仅限 Linux 服务器本地合成,无网络依赖但音质差、中文支持弱) - 验证码文本不能直接传敏感内容:比如把用户手机号、邮箱全文喂给 TTS;应只播随机 4–6 位数字(
$code = Str::random(4, '0123456789')),并确保该$code同时存入缓存(Cache::put('voice_code_'.$request->ip(), $code, 300)) - 不要在登录接口里实时调 TTS:会拖慢响应,且并发高时可能触发服务商限流;建议预生成并缓存音频 URL,或用「懒加载」方式——前端点“听”才调一次
Laravel 控制器中调用阿里云 TTS 的最小可行写法
以阿里云语音合成(aliyun-openapi-php-sdk)为例,它比腾讯云更稳定支持中文数字读法(如“二零二四” vs “两千零二十四”)。关键不是 SDK 多复杂,而是怎么绕过鉴权失败和编码坑。
常见错误现象:InvalidAccessKeyId.NotFound(AK/SK 没配对);SignatureDoesNotMatch(时间戳未用 UTC);返回空音频或 400 错误但没报具体原因。
- 安装 SDK:
composer require alibabacloud/sdk - 配置
config/services.php加:'aliyun_tts' => ['access_key_id' => env('ALIYUN_TTS_ACCESS_KEY_ID'), 'access_key_secret' => env('ALIYUN_TTS_ACCESS_KEY_SECRET'), 'region' => 'cn-shanghai'] - 控制器里调用要点:
— 必须用date_default_timezone_set('UTC')(阿里云校验时间戳)
— 文本要 urlencode,且长度 ≤ 300 字符(超长会被截断,但不会报错)
— 使用Format => 'mp3'和SampleRate => 16000,兼容性最好
use AlibabaCloud\Client\AlibabaCloud; use AlibabaCloud\Client\Exception\ServerException; // ... 在方法内 AlibabaCloud::accessKeyClient( config('services.aliyun_tts.access_key_id'), config('services.aliyun_tts.access_key_secret') )->regionId(config('services.aliyun_tts.region'))->asDefaultClient(); try { $result = AlibabaCloud::rpc() ->product('Alinlp') ->version('2020-06-29') ->action('CreateTtsTask') ->method('POST') ->options([ 'query' => [ 'Text' => urlencode($code), 'Format' => 'mp3', 'SampleRate' => 16000, 'Voice' => 'siyue', // 阿里推荐中文女声,避免用 'xiaoyun'(读数字易吞音) ], ]) ->request(); $audioUrl = $result->toArray()['Data']['AudioUrl'] ?? null; } catch (ServerException $e) { \Log::error('TTS failed: ' . $e->getErrorMessage()); $audioUrl = null; }
前端如何安全可靠地播放语音验证码
不能直接用 <audio src="xxx"> 硬写死 URL,因为语音链接有时效性(阿里云临时 URL 通常 30 分钟过期),且需防止被恶意刷取。核心是「一次一链」+「绑定 IP/Session」。
常见错误现象:刷新页面后“重听”按钮失效;多人共用同一 IP 时互相干扰;语音反复播放但校验总失败(缓存了旧 code)。
- 后端返回结构必须含两个字段:
audio_url(带签名的临时地址)和play_id(唯一标识本次语音请求,如 md5($code.$ip.time())),前端每次播放都带上这个play_id到校验接口 - 前端 JS 播放前先检查
audio元素是否已加载完成:audio.oncanplay = () => audio.play(),避免 iOS Safari 报NotAllowedError - 禁用连续点击:按钮点击后立即
disabled,3 秒后恢复;同时限制每分钟最多 3 次请求(后端用RateLimiter::attempt控制) - 别用
autoplay:移动端基本被浏览器拦截,必须用户手势触发(如 click)
无障碍校验逻辑必须和图形/短信验证码解耦
语音验证码不是“换种方式输数字”,而是为视障用户提供的独立通道。如果沿用原有 login 表单提交逻辑,会导致 screen reader 用户无法区分当前走的是哪条路径,也容易让校验规则混在一起出错。
常见错误现象:开启 TalkBack 后,输入框朗读为“请输入验证码”,但没说明是“语音播报的四位数字”;提交时提示“验证码错误”,却不告知用户“您听到的是哪几个数字”,无法复核。
- 单独建一个接口,如
POST /login/voice-verify,只接收play_id和input_code,不做密码校验,只比对缓存中的原始$code - 响应体必须含语义化字段:
"message": "请确认您听到的数字是:二、零、二、四"(用中文数字而非阿拉伯数字朗读,符合无障碍习惯) - 表单 HTML 中用
<label for="voice_code">请输入语音播报的四位数字</label>,并加aria-describedby关联提示文字,确保 VoiceOver/TalkBack 能完整读出上下文 - 别在同一个
Auth::attempt()里塞语音逻辑:它只管凭证,语音只是前置校验环节,通过后再进标准登录流程
本文共计1522个文字,预计阅读时间需要7分钟。
Laravel 官方并未提供语音验证码(TTS)功能,但你可以使用以下方法实现:
常见错误现象:Call to undefined function App\Http\Controllers\textToSpeech()(误以为 Laravel 内置了 TTS 函数);或前端点击“听语音”没反应,其实是后端没返回可播放的 audio/mp3 地址。
- 必须选一个 TTS 服务商:阿里云
aliyun-openapi-php-sdk、腾讯云tencentcloud-sdk-php、百度 AI 的php-sdk,或更轻量的开源方案如espeak-ng(仅限 Linux 服务器本地合成,无网络依赖但音质差、中文支持弱) - 验证码文本不能直接传敏感内容:比如把用户手机号、邮箱全文喂给 TTS;应只播随机 4–6 位数字(
$code = Str::random(4, '0123456789')),并确保该$code同时存入缓存(Cache::put('voice_code_'.$request->ip(), $code, 300)) - 不要在登录接口里实时调 TTS:会拖慢响应,且并发高时可能触发服务商限流;建议预生成并缓存音频 URL,或用「懒加载」方式——前端点“听”才调一次
Laravel 控制器中调用阿里云 TTS 的最小可行写法
以阿里云语音合成(aliyun-openapi-php-sdk)为例,它比腾讯云更稳定支持中文数字读法(如“二零二四” vs “两千零二十四”)。关键不是 SDK 多复杂,而是怎么绕过鉴权失败和编码坑。
常见错误现象:InvalidAccessKeyId.NotFound(AK/SK 没配对);SignatureDoesNotMatch(时间戳未用 UTC);返回空音频或 400 错误但没报具体原因。
- 安装 SDK:
composer require alibabacloud/sdk - 配置
config/services.php加:'aliyun_tts' => ['access_key_id' => env('ALIYUN_TTS_ACCESS_KEY_ID'), 'access_key_secret' => env('ALIYUN_TTS_ACCESS_KEY_SECRET'), 'region' => 'cn-shanghai'] - 控制器里调用要点:
— 必须用date_default_timezone_set('UTC')(阿里云校验时间戳)
— 文本要 urlencode,且长度 ≤ 300 字符(超长会被截断,但不会报错)
— 使用Format => 'mp3'和SampleRate => 16000,兼容性最好
use AlibabaCloud\Client\AlibabaCloud; use AlibabaCloud\Client\Exception\ServerException; // ... 在方法内 AlibabaCloud::accessKeyClient( config('services.aliyun_tts.access_key_id'), config('services.aliyun_tts.access_key_secret') )->regionId(config('services.aliyun_tts.region'))->asDefaultClient(); try { $result = AlibabaCloud::rpc() ->product('Alinlp') ->version('2020-06-29') ->action('CreateTtsTask') ->method('POST') ->options([ 'query' => [ 'Text' => urlencode($code), 'Format' => 'mp3', 'SampleRate' => 16000, 'Voice' => 'siyue', // 阿里推荐中文女声,避免用 'xiaoyun'(读数字易吞音) ], ]) ->request(); $audioUrl = $result->toArray()['Data']['AudioUrl'] ?? null; } catch (ServerException $e) { \Log::error('TTS failed: ' . $e->getErrorMessage()); $audioUrl = null; }
前端如何安全可靠地播放语音验证码
不能直接用 <audio src="xxx"> 硬写死 URL,因为语音链接有时效性(阿里云临时 URL 通常 30 分钟过期),且需防止被恶意刷取。核心是「一次一链」+「绑定 IP/Session」。
常见错误现象:刷新页面后“重听”按钮失效;多人共用同一 IP 时互相干扰;语音反复播放但校验总失败(缓存了旧 code)。
- 后端返回结构必须含两个字段:
audio_url(带签名的临时地址)和play_id(唯一标识本次语音请求,如 md5($code.$ip.time())),前端每次播放都带上这个play_id到校验接口 - 前端 JS 播放前先检查
audio元素是否已加载完成:audio.oncanplay = () => audio.play(),避免 iOS Safari 报NotAllowedError - 禁用连续点击:按钮点击后立即
disabled,3 秒后恢复;同时限制每分钟最多 3 次请求(后端用RateLimiter::attempt控制) - 别用
autoplay:移动端基本被浏览器拦截,必须用户手势触发(如 click)
无障碍校验逻辑必须和图形/短信验证码解耦
语音验证码不是“换种方式输数字”,而是为视障用户提供的独立通道。如果沿用原有 login 表单提交逻辑,会导致 screen reader 用户无法区分当前走的是哪条路径,也容易让校验规则混在一起出错。
常见错误现象:开启 TalkBack 后,输入框朗读为“请输入验证码”,但没说明是“语音播报的四位数字”;提交时提示“验证码错误”,却不告知用户“您听到的是哪几个数字”,无法复核。
- 单独建一个接口,如
POST /login/voice-verify,只接收play_id和input_code,不做密码校验,只比对缓存中的原始$code - 响应体必须含语义化字段:
"message": "请确认您听到的数字是:二、零、二、四"(用中文数字而非阿拉伯数字朗读,符合无障碍习惯) - 表单 HTML 中用
<label for="voice_code">请输入语音播报的四位数字</label>,并加aria-describedby关联提示文字,确保 VoiceOver/TalkBack 能完整读出上下文 - 别在同一个
Auth::attempt()里塞语音逻辑:它只管凭证,语音只是前置校验环节,通过后再进标准登录流程

