本文目录导读:

我来详细说明PHP语音验证码的实现方法,这是一个相对复杂的功能,需要第三方服务商支持。
选择语音验证码服务商
目前主流的选择有:
- 阿里云语音服务
- 腾讯云语音
- 容联云通讯
- twilio(国际)
实现流程
graph TD
A[用户点击获取语音验证码] --> B[后端生成验证码]
B --> C[调用语音API服务]
C --> D[服务商打电话给用户]
D --> E[用户接听并听到验证码]
E --> F[用户输入验证码]
F --> G[验证码校验]
具体实现代码
1 阿里云语音验证码示例
<?php
require_once 'aliyun-php-sdk-core/Config.php';
use AliyunDyplsapi\Dyplsapi\Request\V20170525 as Dyplsapi;
use Aliyun\Core\DefaultAcsClient;
use Aliyun\Core\Profile\DefaultProfile;
class VoiceCodeService {
private $accessKeyId;
private $accessKeySecret;
public function __construct($accessKeyId, $accessKeySecret) {
$this->accessKeyId = $accessKeyId;
$this->accessKeySecret = $accessKeySecret;
}
/**
* 发送语音验证码
*/
public function sendVoiceCode($phone, $code) {
try {
// 初始化客户端
$profile = DefaultProfile::getProfile(
'cn-hangzhou',
$this->accessKeyId,
$this->accessKeySecret
);
$client = new DefaultAcsClient($profile);
// 创建语音通知请求
$request = new Dyplsapi\VoiceSingleCallRequest();
$request->setPhoneNumbers($phone);
$request->setCalledShowNumber('10690000000'); // 显示号码
$request->setTtsCode('TTS_000000'); // 语音模板ID
$request->setTtsParam(json_encode([
'code' => $code
]));
$request->setVolume(100); // 音量
// 发送请求
$response = $client->getAcsResponse($request);
// 保存验证码到缓存
$this->saveCodeToCache($phone, $code);
return [
'success' => true,
'message' => '语音验证码发送成功',
'data' => $response
];
} catch (Exception $e) {
return [
'success' => false,
'message' => '发送失败:' . $e->getMessage()
];
}
}
/**
* 保存验证码到缓存
*/
private function saveCodeToCache($phone, $code) {
// 使用Redis存储
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'voice_code:' . md5($phone);
$redis->setex($key, 300, json_encode([
'code' => $code,
'phone' => $phone,
'expires' => time() + 300
]));
}
/**
* 验证语音验证码
*/
public function verifyCode($phone, $inputCode) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'voice_code:' . md5($phone);
$codeData = $redis->get($key);
if (!$codeData) {
return ['success' => false, 'message' => '验证码已过期'];
}
$codeData = json_decode($codeData, true);
if ($codeData['code'] == $inputCode) {
// 验证成功后删除
$redis->del($key);
return ['success' => true, 'message' => '验证成功'];
}
return ['success' => false, 'message' => '验证码错误'];
}
}
// 使用示例
$voiceService = new VoiceCodeService(
'your-access-key-id',
'your-access-key-secret'
);
// 生成4位或6位验证码
$code = random_int(1000, 9999);
// 或 $code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
// 发送
$result = $voiceService->sendVoiceCode('13800138000', $code);
print_r($result);
?>
2 使用Twilio(国际服务)
<?php
require_once 'vendor/autoload.php';
use Twilio\Rest\Client;
class TwilioVoiceCode {
private $sid;
private $token;
private $fromNumber;
public function __construct($sid, $token, $fromNumber) {
$this->sid = $sid;
$this->token = $token;
$this->fromNumber = $fromNumber;
}
public function sendVoiceCode($toPhone, $code) {
$twilio = new Client($this->sid, $this->token);
try {
$call = $twilio->calls->create(
$toPhone, // 接收方
$this->fromNumber, // 发送方
[
"twiml" => $this->generateTwiML($code)
]
);
return [
'success' => true,
'callSid' => $call->sid,
'message' => '语音验证码已发送'
];
} catch (Exception $e) {
return [
'success' => false,
'message' => '发送失败:' . $e->getMessage()
];
}
}
private function generateTwiML($code) {
$message = "您的验证码是:";
// 将数字转为语音(TwiML设置)
$letters = str_split($code);
$speech = '';
foreach ($letters as $letter) {
$speech .= $letter . ',';
}
return "
<Response>
<Say voice='alice' language='zh-CN'>
您的验证码是:{$speech}。
验证码将在5分钟内有效。
</Say>
</Response>
";
}
}
// 使用
$twilioVoice = new TwilioVoiceCode(
'your-twilio-sid',
'your-twilio-token',
'+12345678901'
);
$result = $twilioVoice->sendVoiceCode('+8613800138000', 123456);
?>
通用封装类
<?php
class VoiceCodeManager {
private $provider;
private $config;
private $cache;
public function __construct($provider = 'aliyun', $config = []) {
$this->provider = $provider;
$this->config = $config;
$this->initCache();
}
private function initCache() {
$this->cache = new Redis();
$this->cache->connect(
$this->config['redis_host'] ?? '127.0.0.1',
$this->config['redis_port'] ?? 6379
);
}
/**
* 发送语音验证码
*/
public function send($phone, $length = 6) {
// 1. 检查频率
if (!$this->checkRateLimit($phone)) {
return ['error' => '发送太频繁,请稍后再试'];
}
// 2. 生成验证码
$code = $this->generateCode($length);
// 3. 根据服务商发送
$result = $this->sendViaProvider($phone, $code);
// 4. 保存到缓存
$this->saveCode($phone, $code);
return $result;
}
/**
* 生成验证码
*/
private function generateCode($length) {
if ($length == 6) {
return str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
}
return str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT);
}
/**
* 频率限制
*/
private function checkRateLimit($phone) {
$key = 'voice_rate_' . md5($phone);
$count = $this->cache->get($key);
if ($count >= 3) { // 最多3次/小时
return false;
}
$this->cache->incr($key);
$this->cache->expire($key, 3600);
return true;
}
/**
* 发送逻辑
*/
private function sendViaProvider($phone, $code) {
switch ($this->provider) {
case 'aliyun':
// 阿里云实现
break;
case 'tencent':
// 腾讯云实现
break;
case 'twilio':
// Twilio实现
break;
}
}
/**
* 保存验证码
*/
private function saveCode($phone, $code) {
$key = 'voice_code_' . md5($phone);
$this->cache->setex($key, 300, json_encode([
'code' => $code,
'phone' => $phone,
'created_at' => time()
]));
}
/**
* 验证验证码
*/
public function verify($phone, $inputCode) {
$key = 'voice_code_' . md5($phone);
$data = $this->cache->get($key);
if (!$data) {
return false;
}
$data = json_decode($data, true);
if ($data['code'] == $inputCode) {
$this->cache->del($key);
return true;
}
return false;
}
}
前端集成示例
<!DOCTYPE html>
<html>
<head>语音验证码</title>
</head>
<body>
<input type="text" id="phone" placeholder="请输入手机号">
<input type="text" id="code" placeholder="请输入验证码">
<button id="sendVoiceBtn" onclick="sendVoiceCode()">获取语音验证码</button>
<button onclick="verifyCode()">验证</button>
<script>
function sendVoiceCode() {
const phone = document.getElementById('phone').value;
fetch('/api/send_voice_code.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone: phone
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('语音验证码已拨打,请接听电话');
// 启动倒计时
countDown();
} else {
alert(data.message);
}
});
}
function verifyCode() {
const phone = document.getElementById('phone').value;
const code = document.getElementById('code').value;
fetch('/api/verify_code.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone: phone,
code: code
})
})
.then(response => response.json())
.then(data => {
alert(data.message);
});
}
function countDown() {
let seconds = 60;
const btn = document.getElementById('sendVoiceBtn');
btn.disabled = true;
btn.textContent = seconds + '秒后重试';
const interval = setInterval(() => {
seconds--;
btn.textContent = seconds + '秒后重试';
if (seconds <= 0) {
clearInterval(interval);
btn.disabled = false;
btn.textContent = '获取语音验证码';
}
}, 1000);
}
</script>
</body>
</html>
安全建议
- 验证码长度:建议6位数字
- 有效期:5分钟内有效
- 频率限制:同一号码1小时内最多3次
- IP限制:记录IP,防止恶意攻击
- SSL加密:全程使用HTTPS
- 日志记录:记录所有请求用于排查问题
注意事项
- 语音验证码可能需要收费,具体查看服务商价格
- 需要提前在服务商处申请语音模板(部分含中文数字播报)
- 可能受运营商限制,某些号码无法接收
- 建议做备用方案(短信验证码)以防语音服务不稳定
如果需要了解具体的服务商接入细节,请告诉我你计划使用哪家服务商。