appId = $appId; } if ($token) { $this->token = $token; } } /** * 发送语音通知 * @param string $phone 被叫手机号 * @param string $templateId 模板ID * @param array $templateArgs 模板参数 * @param array $options 其他选项 * @return array */ public function send($phone, $templateId, $templateArgs = [], $options = []) { try { // 验证手机号 if (!$this->checkPhone($phone)) { return ['success' => false, 'message' => '手机号格式错误']; } // 验证模板ID if (empty($templateId)) { return ['success' => false, 'message' => '模板ID不能为空']; } // 准备请求数据 $data = $this->prepareData($phone, $templateId, $templateArgs, $options); // 生成签名和时间戳 $timestamp = $this->getTimestamp(); $sig = $this->generateSig($timestamp); // 发送请求 $result = $this->makeRequest($data, $sig, $timestamp); // 返回结果 return [ 'success' => true, 'call_id' => $result['callId'] ?? '', 'result' => $result['result'] ?? '', 'message' => $result['message'] ?? '发送成功', 'raw_data' => $result ]; } catch (\Exception $e) { Log::error('语音通知发送失败: ' . $e->getMessage()); return [ 'success' => false, 'message' => '发送失败: ' . $e->getMessage() ]; } } /** * 准备请求数据 * @param string $phone * @param string $templateId * @param array $templateArgs * @param array $options * @return array */ protected function prepareData($phone, $templateId, $templateArgs, $options) { $data = [ 'calleeNumber' => $phone, 'templateId' => $templateId, ]; // 模板参数 if (!empty($templateArgs)) { $data['templateArgs'] = $templateArgs; } else { // 如果模板无参数,传空对象 $data['templateArgs'] = new \stdClass(); } // 可选参数处理 if (!empty($options['displayNumber'])) { $data['displayNumber'] = $options['displayNumber']; } if (isset($options['replayTimes']) && in_array($options['replayTimes'], [1, 2, 3])) { $data['replayTimes'] = $options['replayTimes']; } if (isset($options['ttsFirst']) && in_array($options['ttsFirst'], [0, 1])) { $data['ttsFirst'] = $options['ttsFirst']; } if (isset($options['recordButton']) && in_array($options['recordButton'], [0, 1])) { $data['recordButton'] = $options['recordButton']; } if (isset($options['callRec']) && in_array($options['callRec'], [0, 1])) { $data['callRec'] = $options['callRec']; } if (!empty($options['callbackUrl'])) { $data['callbackUrl'] = $options['callbackUrl']; } return $data; } /** * 生成签名 * @param string $timestamp * @return string */ protected function generateSig($timestamp) { $str = $this->appId . $this->token . $timestamp; return md5($str); } /** * 获取时间戳(毫秒) * @return string */ protected function getTimestamp() { return round(microtime(true) * 1000); } /** * 发送HTTP请求 * @param array $data * @param string $sig * @param string $timestamp * @return array * @throws \Exception */ protected function makeRequest($data, $sig, $timestamp) { // 构建URL $url = sprintf('%s/%s/%s/%s/%s/%s', $this->baseUrl, $this->platform, $this->version, $this->functionCode, $this->appId, $sig ); // 构建Authorization头部 $auth = base64_encode($this->appId . ':' . $timestamp); // 准备请求头 $headers = [ 'Host: ' . parse_url($this->baseUrl, PHP_URL_HOST), 'Authorization: ' . $auth, 'Accept: application/json', 'Content-Type: application/json', ]; // 初始化cURL $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($data, JSON_UNESCAPED_UNICODE), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => $this->timeout, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, ]); // 执行请求 $response = curl_exec($ch); // 检查错误 if ($error = curl_error($ch)) { throw new \Exception('网络请求失败: ' . $error); } $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // 检查HTTP状态码 if ($httpCode !== 200) { throw new \Exception('HTTP错误: ' . $httpCode); } // 解析JSON响应 $result = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new \Exception('响应数据解析失败'); } // 检查响应结果 if (!isset($result['result'])) { throw new \Exception('响应缺少result字段'); } // 000000 表示成功 if ($result['result'] !== '000000') { $message = $result['message'] ?? '未知错误'; throw new \Exception($message . ' (code: ' . $result['result'] . ')'); } return $result; } /** * 验证手机号格式 * @param string $phone * @return bool */ protected function checkPhone($phone) { return preg_match('/^1[3-9]\d{9}$/', $phone) === 1; } /** * 设置自定义凭证 * @param string $appId * @param string $token * @return $this */ public function setCredentials($appId, $token) { $this->appId = $appId; $this->token = $token; return $this; } /** * 设置API地址 * @param string $url * @return $this */ public function setBaseUrl($url) { $this->baseUrl = $url; return $this; } /** * 设置超时时间 * @param int $seconds * @return $this */ public function setTimeout($seconds) { $this->timeout = $seconds; return $this; } }