短信日志

main
岩仔88 3 weeks ago
parent 583dfaf79c
commit 1ccafe88e2

@ -68,4 +68,40 @@ class SmsController
} }
return $code; return $code;
} }
public function CallBack(Request $request)
{
// 1. 解析 JSON 数据 (腾讯云回调是数组格式)
$dataList = json_decode($request->getContent(), true);
// 兼容:如果不是数组,强制转为数组
if (!is_array($dataList)) {
$dataList = $dataList ? [$dataList] : [];
}
foreach ($dataList as $item) {
$sid = $item['sid'] ?? null;
// 2. 只有当 sid 存在时才处理
if ($sid) {
// 3. 查找并更新:找到就存 JSON找不到自动忽略 (update 影响行数为 0)
DB::table('sms_log')
->where('sid', $sid)
->update([
'callback_content' => json_encode($item, JSON_UNESCAPED_UNICODE),
'updated_at' => now()
]);
}
}
// 4. 必须按文档返回特定格式,否则腾讯云会重试
return response()->json(['result' => 0, 'errmsg' => 'OK']);
}
public function TestSend()
{
$service = new TencentSmsApiService();
$ss= $service->send('19933509886',"2567513", ["小明","转入"]);
}
} }

@ -2,6 +2,7 @@
namespace App\Services; namespace App\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -29,11 +30,12 @@ class TencentSmsApiService
public function send(string $phoneNumber, string $templateId, array $params = []) public function send(string $phoneNumber, string $templateId, array $params = [])
{ {
// 1. 基础配置与时间戳
$action = 'SendSms'; $action = 'SendSms';
$timestamp = time(); $timestamp = time();
$date = gmdate('Y-m-d', $timestamp); $date = gmdate('Y-m-d', $timestamp);
// 构造请求体 // 2. 构造请求体 (Body)
$body = [ $body = [
'PhoneNumberSet' => [$phoneNumber], 'PhoneNumberSet' => [$phoneNumber],
'SmsSdkAppId' => $this->sdkAppId, 'SmsSdkAppId' => $this->sdkAppId,
@ -45,46 +47,124 @@ class TencentSmsApiService
$body['TemplateParamSet'] = $params; $body['TemplateParamSet'] = $params;
} }
// 注意JSON_UNESCAPED_UNICODE 和 JSON_UNESCAPED_SLASHES 是腾讯云签名必须的
$payload = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $payload = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
// Canonical Request // 3. 构造规范请求 (Canonical Request)
$canonicalUri = '/'; $canonicalUri = '/';
$canonicalQueryString = ''; $canonicalQueryString = '';
$canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:{$this->host}\n"; $canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:{$this->host}\n";
$signedHeaders = 'content-type;host'; $signedHeaders = 'content-type;host';
$hashedPayload = hash('sha256', $payload); $hashedPayload = hash('sha256', $payload);
$canonicalRequest = "POST\n{$canonicalUri}\n{$canonicalQueryString}\n{$canonicalHeaders}\n{$signedHeaders}\n{$hashedPayload}"; $canonicalRequest = "POST\n{$canonicalUri}\n{$canonicalQueryString}\n{$canonicalHeaders}\n{$signedHeaders}\n{$hashedPayload}";
// String to Sign // 4. 构造待签名字符串 (String to Sign)
$algorithm = 'TC3-HMAC-SHA256'; $algorithm = 'TC3-HMAC-SHA256';
$date = gmdate('Y-m-d', $timestamp);
$credentialScope = "{$date}/{$this->service}/tc3_request"; $credentialScope = "{$date}/{$this->service}/tc3_request";
$hashedCanonicalRequest = hash('sha256', $canonicalRequest); $hashedCanonicalRequest = hash('sha256', $canonicalRequest);
$stringToSign = "{$algorithm}\n{$timestamp}\n{$credentialScope}\n{$hashedCanonicalRequest}"; $stringToSign = "{$algorithm}\n{$timestamp}\n{$credentialScope}\n{$hashedCanonicalRequest}";
// Signature // 5. 计算签名 (Signature)
$kSecret = 'TC3' . $this->secretKey; $kSecret = 'TC3' . $this->secretKey;
$kDate = hash_hmac('sha256', $date, $kSecret, true); $kDate = hash_hmac('sha256', $date, $kSecret, true);
$kService = hash_hmac('sha256', $this->service, $kDate, true); $kService = hash_hmac('sha256', $this->service, $kDate, true);
$kSigning = hash_hmac('sha256', 'tc3_request', $kService, true); $kSigning = hash_hmac('sha256', 'tc3_request', $kService, true);
$signature = hash_hmac('sha256', $stringToSign, $kSigning, 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}"; $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([ $response = Http::withHeaders([
'Authorization' => $authorization, 'Authorization' => $authorization,
'Content-Type' => 'application/json; charset=utf-8', 'Content-Type' => 'application/json; charset=utf-8',
'Host' => $this->host, 'Host' => $this->host,
'X-TC-Action' => $action, 'X-TC-Action' => $action,
'X-TC-Timestamp' => $timestamp, 'X-TC-Timestamp' => (string)$timestamp,
'X-TC-Version' => $this->version, 'X-TC-Version' => $this->version,
'X-TC-Region' => $this->region, 'X-TC-Region' => $this->region,
])->withBody($payload, 'application/json; charset=utf-8') ])
->withBody($payload, 'application/json; charset=utf-8')
->post("https://{$this->endpoint}"); ->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;
}
} }
} }

@ -93,5 +93,6 @@ Route::post('mp/InsertInfo','App\Http\Controllers\API\LiuYanController@Mp_Insert
Route::any('/SmsCallBack','App\Http\Controllers\API\SmsController@CallBack' )->middleware('log');
Route::any('/TestSendSms','App\Http\Controllers\API\SmsController@TestSend' )->middleware('log');//测试发送短信

@ -25,4 +25,7 @@ Route::get('/wxLogin/{env}', function ($env) {
}); });
//微信登录授权获取openid //微信登录授权获取openid
Route::get('/wxGetCode','App\Http\Controllers\API\mH5\LoginController@wxGetCode' ); Route::get('/wxGetCode','App\Http\Controllers\API\mH5\LoginController@wxGetCode' );
Route::get('/test','App\Http\Controllers\TestController@DBtest' ); Route::get('/test','App\Http\Controllers\TestController@DBtest' );

@ -136,6 +136,14 @@ const router = createRouter({
title: '日切' title: '日切'
} }
}, },
{
path: '/systemMngr/smsLog',
name: 'SystemMngrSmsLog',
component: () => import('../views/SystemMngr/SmsLog.vue'),
meta: {
title: '短信日志'
}
},
{ {
path: '/yewu/weixinUserList', path: '/yewu/weixinUserList',
name: 'YeWuWeixinUserList', name: 'YeWuWeixinUserList',

@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>
Loading…
Cancel
Save