更新
This commit is contained in:
@@ -164,6 +164,100 @@ class TencentImService
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只读检查一批账号是否已导入 IM;不自动导入,也不在一次调用里拆成多个请求。
|
||||
* @see https://cloud.tencent.com/document/product/269/38417
|
||||
* @return array{existing:array,missing:array}
|
||||
* @throws \RuntimeException 单批最多 100 个不同账号;任何检查失败都保留错误码。
|
||||
*/
|
||||
public function checkAccounts(array $accounts): array
|
||||
{
|
||||
foreach ($accounts as $account) {
|
||||
if (!is_string($account) || trim($account) === '') {
|
||||
throw new \RuntimeException('IM账号查询参数必须是非空账号字符串');
|
||||
}
|
||||
}
|
||||
$accounts = array_values(array_unique($accounts, SORT_STRING));
|
||||
if ($accounts === []) {
|
||||
return ['existing' => [], 'missing' => []];
|
||||
}
|
||||
if (count($accounts) > 100) {
|
||||
throw new \RuntimeException('IM账号查询单批最多支持100个账号');
|
||||
}
|
||||
|
||||
try {
|
||||
$adminUserSig = $this->generateUserSig($this->adminIdentifier);
|
||||
if (!$adminUserSig) {
|
||||
throw new \RuntimeException('生成管理员UserSig失败');
|
||||
}
|
||||
$url = sprintf(
|
||||
'https://console.tim.qq.com/v4/im_open_login_svc/account_check?sdkappid=%s&identifier=%s&usersig=%s&random=%s&contenttype=json',
|
||||
$this->sdkAppId,
|
||||
$this->adminIdentifier,
|
||||
urlencode($adminUserSig),
|
||||
rand(0, 4294967295)
|
||||
);
|
||||
$data = ['CheckItem' => array_map(static function (string $account): array {
|
||||
return ['UserID' => $account];
|
||||
}, $accounts)];
|
||||
$result = $this->httpPost($url, json_encode($data, JSON_THROW_ON_ERROR), 15);
|
||||
if (!is_string($result) || $result === '') {
|
||||
throw new \RuntimeException('IM账号查询接口无响应');
|
||||
}
|
||||
$response = json_decode($result, true);
|
||||
if (!is_array($response) || !is_int($response['ErrorCode'] ?? null)) {
|
||||
throw new \RuntimeException('IM账号查询响应格式非法或缺少ErrorCode');
|
||||
}
|
||||
$code = $response['ErrorCode'];
|
||||
if (($response['ActionStatus'] ?? null) !== 'OK' || $code !== 0) {
|
||||
$info = is_string($response['ErrorInfo'] ?? null) ? trim($response['ErrorInfo']) : '';
|
||||
throw new \RuntimeException($info !== '' ? $info : 'IM账号查询失败:ErrorCode ' . $code, $code);
|
||||
}
|
||||
$items = $response['ResultItem'] ?? null;
|
||||
if (!is_array($items) || array_values($items) !== $items) {
|
||||
throw new \RuntimeException('IM账号查询响应缺少有效ResultItem列表');
|
||||
}
|
||||
$requested = array_fill_keys($accounts, true);
|
||||
$statuses = [];
|
||||
foreach ($items as $item) {
|
||||
if (!is_array($item) || !is_string($item['UserID'] ?? null)) {
|
||||
throw new \RuntimeException('IM账号查询结果缺少有效UserID');
|
||||
}
|
||||
$account = $item['UserID'];
|
||||
if (!isset($requested[$account]) || isset($statuses[$account])) {
|
||||
throw new \RuntimeException('IM账号查询结果包含未请求或重复的账号');
|
||||
}
|
||||
if (!is_int($item['ResultCode'] ?? null)) {
|
||||
throw new \RuntimeException('IM账号查询结果缺少有效ResultCode');
|
||||
}
|
||||
if ($item['ResultCode'] !== 0) {
|
||||
$info = is_string($item['ResultInfo'] ?? null) ? trim($item['ResultInfo']) : '';
|
||||
throw new \RuntimeException(
|
||||
$info !== '' ? $info : 'IM单个账号查询失败:ResultCode ' . $item['ResultCode'],
|
||||
$item['ResultCode']
|
||||
);
|
||||
}
|
||||
if (!in_array($item['AccountStatus'] ?? null, ['Imported', 'NotImported'], true)) {
|
||||
throw new \RuntimeException('IM账号查询结果缺少有效AccountStatus');
|
||||
}
|
||||
$statuses[$account] = $item['AccountStatus'];
|
||||
}
|
||||
if (count($statuses) !== count($accounts)) {
|
||||
throw new \RuntimeException('IM账号查询结果不完整,部分账号未返回检查结果');
|
||||
}
|
||||
|
||||
$out = ['existing' => [], 'missing' => []];
|
||||
foreach ($accounts as $account) {
|
||||
$out[$statuses[$account] === 'Imported' ? 'existing' : 'missing'][] = $account;
|
||||
}
|
||||
return $out;
|
||||
} catch (\RuntimeException $exception) {
|
||||
throw $exception;
|
||||
} catch (\Throwable $exception) {
|
||||
throw new \RuntimeException($exception->getMessage(), (int)$exception->getCode(), $exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 删除账号
|
||||
@@ -235,7 +329,7 @@ class TencentImService
|
||||
|
||||
/**
|
||||
* 拉取单聊(C2C)漫游消息
|
||||
* @see https://cloud.tencent.com/document/product/269/2739
|
||||
* @see https://cloud.tencent.cn/document/product/269/42794
|
||||
*
|
||||
* @param string $operatorAccount 会话一方 UserID(如 doctor_1)
|
||||
* @param string $peerAccount 会话另一方 UserID(如 patient_2)
|
||||
@@ -253,7 +347,7 @@ class TencentImService
|
||||
$empty = [
|
||||
'success' => false,
|
||||
'msgList' => [],
|
||||
'complete' => 1,
|
||||
'complete' => 0,
|
||||
'lastMsgKey' => null,
|
||||
'lastMsgTime' => null,
|
||||
'error' => '',
|
||||
@@ -278,45 +372,61 @@ class TencentImService
|
||||
'Peer_Account' => $peerAccount,
|
||||
'MaxCnt' => $maxCnt,
|
||||
'MinTime' => $minTime,
|
||||
'MaxTime' => $maxTime,
|
||||
// 保留旧方法参数;续页时间必须写入 MaxTime,LastMsgTime 仅为响应字段。
|
||||
'MaxTime' => $lastMsgTime ?? $maxTime,
|
||||
];
|
||||
if ($lastMsgKey !== null && $lastMsgKey !== '') {
|
||||
$data['LastMsgKey'] = $lastMsgKey;
|
||||
}
|
||||
if ($lastMsgTime !== null && $lastMsgTime > 0) {
|
||||
$data['LastMsgTime'] = $lastMsgTime;
|
||||
}
|
||||
// 增加超时时间到 60 秒
|
||||
$result = $this->httpPost($url, json_encode($data), 60);
|
||||
// 每个同步步骤只请求一页,超时后交由下一步骤重试。
|
||||
$result = $this->httpPost($url, json_encode($data, JSON_THROW_ON_ERROR), 15);
|
||||
if (!$result) {
|
||||
$empty['error'] = 'IM接口无响应';
|
||||
return $empty;
|
||||
}
|
||||
$response = json_decode($result, true);
|
||||
if (!$response) {
|
||||
if (!is_array($response)) {
|
||||
$empty['error'] = 'IM响应解析失败';
|
||||
return $empty;
|
||||
}
|
||||
$code = (int)($response['ErrorCode'] ?? -1);
|
||||
$code = is_int($response['ErrorCode'] ?? null) ? $response['ErrorCode'] : -1;
|
||||
$empty['rawErrorCode'] = $code;
|
||||
if (($response['ActionStatus'] ?? '') !== 'OK') {
|
||||
$empty['error'] = $response['ErrorInfo'] ?? ('ErrorCode ' . $code);
|
||||
if (($response['ActionStatus'] ?? '') !== 'OK' || $code !== 0) {
|
||||
$errorInfo = is_string($response['ErrorInfo'] ?? null) ? trim($response['ErrorInfo']) : '';
|
||||
$empty['error'] = $errorInfo !== '' ? $errorInfo : ('IM接口返回错误:ErrorCode ' . $code);
|
||||
return $empty;
|
||||
}
|
||||
$msgList = $response['MsgList'] ?? [];
|
||||
if (!is_array($msgList)) {
|
||||
$msgList = [];
|
||||
$msgList = $response['MsgList'] ?? null;
|
||||
if (!is_array($msgList) || array_values($msgList) !== $msgList) {
|
||||
$empty['error'] = 'IM响应消息列表 MsgList 非法';
|
||||
return $empty;
|
||||
}
|
||||
if (!in_array($response['Complete'] ?? null, [0, 1], true)) {
|
||||
$empty['error'] = 'IM响应分页状态 Complete 非法';
|
||||
return $empty;
|
||||
}
|
||||
if (isset($response['MsgCnt']) && (!is_int($response['MsgCnt']) || $response['MsgCnt'] !== count($msgList))) {
|
||||
$empty['error'] = 'IM响应消息条数 MsgCnt 与 MsgList 不一致';
|
||||
return $empty;
|
||||
}
|
||||
if (isset($response['LastMsgTime']) && !is_int($response['LastMsgTime'])) {
|
||||
$empty['error'] = 'IM响应游标 LastMsgTime 非法';
|
||||
return $empty;
|
||||
}
|
||||
if (isset($response['LastMsgKey']) && !is_string($response['LastMsgKey'])) {
|
||||
$empty['error'] = 'IM响应游标 LastMsgKey 非法';
|
||||
return $empty;
|
||||
}
|
||||
return [
|
||||
'success' => true,
|
||||
'msgList' => $msgList,
|
||||
'complete' => (int)($response['Complete'] ?? 1),
|
||||
'complete' => $response['Complete'],
|
||||
'lastMsgKey' => $response['LastMsgKey'] ?? null,
|
||||
'lastMsgTime' => isset($response['LastMsgTime']) ? (int)$response['LastMsgTime'] : null,
|
||||
'lastMsgTime' => $response['LastMsgTime'] ?? null,
|
||||
'error' => '',
|
||||
'rawErrorCode' => $code,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$empty['error'] = $e->getMessage();
|
||||
return $empty;
|
||||
}
|
||||
@@ -328,7 +438,7 @@ class TencentImService
|
||||
* @param string $data
|
||||
* @return string|false
|
||||
*/
|
||||
private function httpPost(string $url, string $data, int $timeout = 10)
|
||||
protected function httpPost(string $url, string $data, int $timeout = 10)
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user