secretId ="AKIDvxuqiU3OYbR3RcZJrBIDuOegU5YxEQWa"; $this->secretKey = "xnUyGK9lAnHYcS3SecOyLi2GOOOH4h7K"; $this->sdkAppId = "1401058974"; $this->signName ="秦皇岛安尔然"; $this->templateId = "2564483"; } public function send(string $phoneNumber, string $templateId, array $params = []) { // 1. 基础配置与时间戳 $action = 'SendSms'; $timestamp = time(); $date = gmdate('Y-m-d', $timestamp); // 2. 构造请求体 (Body) $body = [ 'PhoneNumberSet' => [$phoneNumber], 'SmsSdkAppId' => $this->sdkAppId, 'SignName' => $this->signName, 'TemplateId' => $templateId, ]; if (!empty($params)) { $body['TemplateParamSet'] = $params; } // 注意:JSON_UNESCAPED_UNICODE 和 JSON_UNESCAPED_SLASHES 是腾讯云签名必须的 $payload = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); // 3. 构造规范请求 (Canonical Request) $canonicalUri = '/'; $canonicalQueryString = ''; $canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:{$this->host}\n"; $signedHeaders = 'content-type;host'; $hashedPayload = hash('sha256', $payload); $canonicalRequest = "POST\n{$canonicalUri}\n{$canonicalQueryString}\n{$canonicalHeaders}\n{$signedHeaders}\n{$hashedPayload}"; // 4. 构造待签名字符串 (String to Sign) $algorithm = 'TC3-HMAC-SHA256'; $credentialScope = "{$date}/{$this->service}/tc3_request"; $hashedCanonicalRequest = hash('sha256', $canonicalRequest); $stringToSign = "{$algorithm}\n{$timestamp}\n{$credentialScope}\n{$hashedCanonicalRequest}"; // 5. 计算签名 (Signature) $kSecret = 'TC3' . $this->secretKey; $kDate = hash_hmac('sha256', $date, $kSecret, true); $kService = hash_hmac('sha256', $this->service, $kDate, true); $kSigning = hash_hmac('sha256', 'tc3_request', $kService, true); $signature = hash_hmac('sha256', $stringToSign, $kSigning, true); $signature = bin2hex($signature); // 6. 构造 Authorization 头 $authorization = "{$algorithm} Credential={$this->secretId}/{$credentialScope}, SignedHeaders={$signedHeaders}, Signature={$signature}"; // 7. 准备数据库初始数据 (此时尚未插入,等待请求结果) $logData = [ 'phone' => $phoneNumber, 'content' => json_encode($params, JSON_UNESCAPED_UNICODE), 'sid' => null, 'status' => 'sending', // 初始状态 'remark' => null ]; $msgId = null; try { // 8. 发送 HTTP 请求 $response = Http::withHeaders([ 'Authorization' => $authorization, 'Content-Type' => 'application/json; charset=utf-8', 'Host' => $this->host, 'X-TC-Action' => $action, 'X-TC-Timestamp' => (string)$timestamp, 'X-TC-Version' => $this->version, 'X-TC-Region' => $this->region, ]) ->withBody($payload, 'application/json; charset=utf-8') ->post("https://{$this->endpoint}"); // 9. 解析响应数据 $responseData = $response->json(); // 初始化提取变量 $serialNo = null; $code = 'Unknown'; $message = 'Unknown response format'; $isSuccess = false; // 防御性检查:确保 Response 结构和 SendStatusSet 存在 if (isset($responseData['Response']) && isset($responseData['Response']['SendStatusSet']) && is_array($responseData['Response']['SendStatusSet']) && count($responseData['Response']['SendStatusSet']) > 0) { $statusInfo = $responseData['Response']['SendStatusSet'][0]; $serialNo = $statusInfo['SerialNo'] ?? null; $code = $statusInfo['Code'] ?? 'UnknownCode'; $message = $statusInfo['Message'] ?? 'No message returned'; // 腾讯云成功标识通常为 "Ok" if ($code === 'Ok') { $isSuccess = true; } } else { // 如果返回结构完全不对(例如鉴权失败直接返回 Error 对象) if (isset($responseData['Response']['Error'])) { $code = $responseData['Response']['Error']['Code'] ?? 'ApiError'; $message = $responseData['Response']['Error']['Message'] ?? 'API Error occurred'; } else { $message = 'Invalid response structure from provider'; } } // 10. 更新日志数据 $logData['sid'] = $serialNo; $logData['status'] = $code; // 存储业务状态码 (Ok 或 FailedOperation.xxx) $logData['remark'] = $message; // 存储具体错误信息 // 11. 写入数据库 (一次性插入完整记录) $msgId = DB::table('sms_log')->insertGetId($logData); // 如果业务逻辑失败(有 sid 但 code 不是 Ok),可以选择记录警告日志 if (!$isSuccess) { // 这里可以根据需求选择是否抛出异常,或者仅记录日志 // \Log::warning("SMS Send Failed: {$phoneNumber}, Code: {$code}, Msg: {$message}, Sid: {$serialNo}"); } return $responseData; } catch (\Exception $e) { // 12. 异常处理:网络错误、超时、签名计算错误等 // 更新日志数据为失败状态 $logData['status'] = 'RequestFailed'; // 标记为请求级失败 $logData['remark'] = $e->getMessage(); // sid 保持 null // 写入数据库,确保不留脏数据 $msgId = DB::table('sms_log')->insertGetId($logData); // 重新抛出异常,让上层调用者知道发送失败了 throw $e; } } }