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); } }