|
|
|
|
@ -2,6 +2,7 @@
|
|
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
|
|
|
|
|
@ -27,13 +28,14 @@ class TencentSmsApiService
|
|
|
|
|
$this->templateId = "2564483";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function send(string $phoneNumber,string $templateId, array $params = [])
|
|
|
|
|
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,
|
|
|
|
|
@ -45,46 +47,124 @@ class TencentSmsApiService
|
|
|
|
|
$body['TemplateParamSet'] = $params;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 注意:JSON_UNESCAPED_UNICODE 和 JSON_UNESCAPED_SLASHES 是腾讯云签名必须的
|
|
|
|
|
$payload = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
|
|
|
|
|
|
|
|
// Canonical Request
|
|
|
|
|
// 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}";
|
|
|
|
|
|
|
|
|
|
// String to Sign
|
|
|
|
|
// 4. 构造待签名字符串 (String to Sign)
|
|
|
|
|
$algorithm = 'TC3-HMAC-SHA256';
|
|
|
|
|
$date = gmdate('Y-m-d', $timestamp);
|
|
|
|
|
$credentialScope = "{$date}/{$this->service}/tc3_request";
|
|
|
|
|
$hashedCanonicalRequest = hash('sha256', $canonicalRequest);
|
|
|
|
|
$stringToSign = "{$algorithm}\n{$timestamp}\n{$credentialScope}\n{$hashedCanonicalRequest}";
|
|
|
|
|
|
|
|
|
|
// Signature
|
|
|
|
|
// 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); // 转为小写 hex 字符串!
|
|
|
|
|
$signature = bin2hex($signature);
|
|
|
|
|
|
|
|
|
|
// Authorization
|
|
|
|
|
// 6. 构造 Authorization 头
|
|
|
|
|
$authorization = "{$algorithm} Credential={$this->secretId}/{$credentialScope}, SignedHeaders={$signedHeaders}, Signature={$signature}";
|
|
|
|
|
|
|
|
|
|
// 发送请求:必须用 $payload 字符串,不能传数组!
|
|
|
|
|
// 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' => $timestamp,
|
|
|
|
|
'X-TC-Timestamp' => (string)$timestamp,
|
|
|
|
|
'X-TC-Version' => $this->version,
|
|
|
|
|
'X-TC-Region' => $this->region,
|
|
|
|
|
])->withBody($payload, 'application/json; charset=utf-8')
|
|
|
|
|
])
|
|
|
|
|
->withBody($payload, 'application/json; charset=utf-8')
|
|
|
|
|
->post("https://{$this->endpoint}");
|
|
|
|
|
|
|
|
|
|
return $response->json();
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|