92 lines
3.6 KiB
PHP
92 lines
3.6 KiB
PHP
<?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);
|
|
}
|
|
}
|