更新
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\adminapi\logic\tcm\DiagnosisLogic;
|
||||
use app\common\service\ImCallbackSignature;
|
||||
use JsonException;
|
||||
use think\facade\Log;
|
||||
use think\response\Json;
|
||||
use Throwable;
|
||||
|
||||
/** 腾讯 IM 单聊消息归档回调;使用腾讯签名认证,不使用患者登录会话。 */
|
||||
class ImController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['messageNotify'];
|
||||
|
||||
private const MAX_BODY_BYTES = 1048576;
|
||||
private const AFTER_SEND_COMMAND = 'C2C.CallbackAfterSendMsg';
|
||||
|
||||
public function messageNotify(): Json
|
||||
{
|
||||
if (strtoupper($this->request->method(true)) !== 'POST') {
|
||||
return $this->callbackFailure(405, 'method not allowed')->header(['Allow' => 'POST']);
|
||||
}
|
||||
$declaredLength = $this->request->header('content-length', '');
|
||||
if (is_scalar($declaredLength) && is_numeric($declaredLength) && (float) $declaredLength > self::MAX_BODY_BYTES) {
|
||||
return $this->callbackFailure(413, 'request body too large');
|
||||
}
|
||||
|
||||
$token = (string) config('im.callback_token', '');
|
||||
$sdkAppId = (string) config('project.trtc.sdkAppId', '');
|
||||
if (trim($token) === '' || preg_match('/^[1-9][0-9]*$/D', $sdkAppId) !== 1) {
|
||||
return $this->callbackFailure(503, 'callback authentication unavailable');
|
||||
}
|
||||
$query = $this->request->get();
|
||||
$actualSdkAppId = $query['SdkAppid'] ?? null;
|
||||
if ((!is_string($actualSdkAppId) && !is_int($actualSdkAppId)) || (string) $actualSdkAppId !== $sdkAppId
|
||||
|| !ImCallbackSignature::verify($token, $query['RequestTime'] ?? null, $query['Sign'] ?? null)) {
|
||||
return $this->callbackFailure(403, 'callback authentication failed');
|
||||
}
|
||||
|
||||
$raw = $this->request->getContent();
|
||||
if (strlen($raw) > self::MAX_BODY_BYTES) {
|
||||
return $this->callbackFailure(413, 'request body too large');
|
||||
}
|
||||
try {
|
||||
$decoded = json_decode($raw, false, 64, JSON_THROW_ON_ERROR);
|
||||
if (!$decoded instanceof \stdClass) {
|
||||
return $this->callbackFailure(400, 'invalid callback JSON object');
|
||||
}
|
||||
$payload = json_decode($raw, true, 64, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException) {
|
||||
return $this->callbackFailure(400, 'invalid callback JSON object');
|
||||
}
|
||||
|
||||
$command = $payload['CallbackCommand'] ?? null;
|
||||
if (!is_string($command) || $command === '' || strlen($command) > 128
|
||||
|| (array_key_exists('CallbackCommand', $query) && $query['CallbackCommand'] !== $command)) {
|
||||
return $this->callbackFailure(400, 'callback command mismatch');
|
||||
}
|
||||
if ($command !== self::AFTER_SEND_COMMAND) {
|
||||
return $this->callbackSuccess();
|
||||
}
|
||||
|
||||
try {
|
||||
DiagnosisLogic::archiveImCallbackMessage($payload);
|
||||
} catch (Throwable) {
|
||||
// 异常信息可能包含 SQL 或患者消息,日志只保留固定事件标记。
|
||||
try {
|
||||
Log::error('IM callback archive failed');
|
||||
} catch (Throwable) {
|
||||
// 日志存储不可用也必须保留腾讯失败回包。
|
||||
}
|
||||
return $this->callbackFailure(500, 'message archive failed');
|
||||
}
|
||||
|
||||
return $this->callbackSuccess();
|
||||
}
|
||||
|
||||
private function callbackSuccess(): Json
|
||||
{
|
||||
return json(['ActionStatus' => 'OK', 'ErrorCode' => 0, 'ErrorInfo' => '']);
|
||||
}
|
||||
|
||||
private function callbackFailure(int $status, string $message): Json
|
||||
{
|
||||
return json(['ActionStatus' => 'FAIL', 'ErrorCode' => $status, 'ErrorInfo' => $message], $status);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user