first commit

This commit is contained in:
Your Name
2026-09-08 11:40:15 +08:00
commit a5353f7eb5
9568 changed files with 1646214 additions and 0 deletions
@@ -0,0 +1,362 @@
<?php
namespace app\common\service;
/**
* 腾讯云IM服务类
* Class TencentImService
* @package app\common\service
*/
class TencentImService
{
private $sdkAppId;
private $secretKey;
private $adminIdentifier = 'administrator'; // 管理员账号
public function __construct()
{
$config = config('project.trtc');
$this->sdkAppId = $config['sdkAppId'];
$this->secretKey = $config['secretKey'];
\think\facade\Log::info('TencentImService初始化 - sdkAppId: ' . $this->sdkAppId . ', secretKey长度: ' . strlen($this->secretKey));
}
/**
* @notes 生成UserSig(使用腾讯云官方算法)
* @param string $userId
* @param int $expire
* @return string|false
*/
private function generateUserSig(string $userId, int $expire = 86400)
{
try {
$api = new TLSSigAPIv2($this->sdkAppId, $this->secretKey);
$userSig = $api->genUserSig($userId, $expire);
\think\facade\Log::info('UserSig生成成功 - userId: ' . $userId . ', sig长度: ' . strlen($userSig));
return $userSig;
} catch (\Exception $e) {
\think\facade\Log::error('生成UserSig失败: ' . $e->getMessage());
return false;
}
}
/**
* @notes 导入单个账号到IM
* @param string $userId 用户ID
* @param string $nick 昵称(可选)
* @param string $faceUrl 头像URL(可选)
* @return array|false
*/
public function importAccount(string $userId, string $nick = '', string $faceUrl = '')
{
try {
\think\facade\Log::info('开始导入IM账号 - userId: ' . $userId . ', nick: ' . $nick . ', sdkAppId: ' . $this->sdkAppId);
// 生成管理员UserSig
$adminUserSig = $this->generateUserSig($this->adminIdentifier);
if (!$adminUserSig) {
throw new \Exception('生成管理员UserSig失败');
}
\think\facade\Log::info('管理员UserSig生成成功');
// 构建请求URL
$random = rand(0, 4294967295);
$url = sprintf(
'https://console.tim.qq.com/v4/im_open_login_svc/account_import?sdkappid=%s&identifier=%s&usersig=%s&random=%s&contenttype=json',
$this->sdkAppId,
$this->adminIdentifier,
urlencode($adminUserSig),
$random
);
\think\facade\Log::info('请求URL构建完成');
// 构建请求体
$data = [
'UserID' => $userId
];
if ($nick) {
$data['Nick'] = $nick;
}
if ($faceUrl) {
$data['FaceUrl'] = $faceUrl;
}
\think\facade\Log::info('请求数据: ' . json_encode($data, JSON_UNESCAPED_UNICODE));
// 发送请求
$result = $this->httpPost($url, json_encode($data));
\think\facade\Log::info('HTTP响应: ' . substr($result, 0, 500));
if (!$result) {
throw new \Exception('请求失败:无响应');
}
$response = json_decode($result, true);
if (!$response) {
throw new \Exception('响应解析失败:' . substr($result, 0, 200));
}
\think\facade\Log::info('响应解析成功: ' . json_encode($response, JSON_UNESCAPED_UNICODE));
// 检查响应状态
if ($response['ActionStatus'] !== 'OK') {
$errorMsg = sprintf(
'导入账号失败 - ErrorCode: %s, ErrorInfo: %s',
$response['ErrorCode'] ?? 'unknown',
$response['ErrorInfo'] ?? '未知错误'
);
throw new \Exception($errorMsg);
}
return [
'success' => true,
'userId' => $userId,
'message' => '账号导入成功'
];
} catch (\Exception $e) {
$errorMsg = sprintf(
'导入IM账号失败 - userId: %s, error: %s',
$userId,
$e->getMessage()
);
\think\facade\Log::error($errorMsg);
return [
'success' => false,
'userId' => $userId,
'message' => $e->getMessage()
];
}
}
/**
* @notes 批量导入账号
* @param array $accounts 账号列表 [['userId' => 'xxx', 'nick' => 'xxx', 'faceUrl' => 'xxx'], ...]
* @return array
*/
public function batchImportAccounts(array $accounts): array
{
$results = [];
foreach ($accounts as $account) {
$userId = $account['userId'] ?? '';
$nick = $account['nick'] ?? '';
$faceUrl = $account['faceUrl'] ?? '';
if (!$userId) {
continue;
}
$result = $this->importAccount($userId, $nick, $faceUrl);
$results[] = $result;
}
return $results;
}
/**
* @notes 删除账号
* @param array $userIds 用户ID列表
* @return array|false
*/
public function deleteAccounts(array $userIds)
{
try {
// 生成管理员UserSig
$adminUserSig = $this->generateUserSig($this->adminIdentifier);
if (!$adminUserSig) {
throw new \Exception('生成管理员UserSig失败');
}
// 构建请求URL
$random = rand(0, 4294967295);
$url = sprintf(
'https://console.tim.qq.com/v4/im_open_login_svc/account_delete?sdkappid=%s&identifier=%s&usersig=%s&random=%s&contenttype=json',
$this->sdkAppId,
$this->adminIdentifier,
urlencode($adminUserSig),
$random
);
// 构建请求体
$data = [
'DeleteItem' => array_map(function($userId) {
return ['UserID' => $userId];
}, $userIds)
];
// 发送请求
$result = $this->httpPost($url, json_encode($data));
if (!$result) {
throw new \Exception('请求失败');
}
$response = json_decode($result, true);
if (!$response) {
throw new \Exception('响应解析失败');
}
// 检查响应状态
if ($response['ActionStatus'] !== 'OK') {
throw new \Exception($response['ErrorInfo'] ?? '删除账号失败');
}
return [
'success' => true,
'message' => '账号删除成功'
];
} catch (\Exception $e) {
\think\facade\Log::error('删除IM账号失败', [
'userIds' => $userIds,
'error' => $e->getMessage()
]);
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* 拉取单聊(C2C)漫游消息
* @see https://cloud.tencent.com/document/product/269/2739
*
* @param string $operatorAccount 会话一方 UserID(如 doctor_1
* @param string $peerAccount 会话另一方 UserID(如 patient_2
* @return array{success:bool,msgList:array,complete:int,lastMsgKey:?string,lastMsgTime:?int,error:string,rawErrorCode:int}
*/
public function adminGetRoamMsg(
string $operatorAccount,
string $peerAccount,
int $maxCnt = 100,
int $minTime = 0,
int $maxTime = 4294967295,
?string $lastMsgKey = null,
?int $lastMsgTime = null
): array {
$empty = [
'success' => false,
'msgList' => [],
'complete' => 1,
'lastMsgKey' => null,
'lastMsgTime' => null,
'error' => '',
'rawErrorCode' => 0,
];
try {
$adminUserSig = $this->generateUserSig($this->adminIdentifier);
if (!$adminUserSig) {
$empty['error'] = '生成管理员UserSig失败';
return $empty;
}
$random = rand(0, 4294967295);
$url = sprintf(
'https://console.tim.qq.com/v4/openim/admin_getroammsg?sdkappid=%s&identifier=%s&usersig=%s&random=%s&contenttype=json',
$this->sdkAppId,
$this->adminIdentifier,
urlencode($adminUserSig),
$random
);
$data = [
'Operator_Account' => $operatorAccount,
'Peer_Account' => $peerAccount,
'MaxCnt' => $maxCnt,
'MinTime' => $minTime,
'MaxTime' => $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);
if (!$result) {
$empty['error'] = 'IM接口无响应';
return $empty;
}
$response = json_decode($result, true);
if (!$response) {
$empty['error'] = 'IM响应解析失败';
return $empty;
}
$code = (int)($response['ErrorCode'] ?? -1);
$empty['rawErrorCode'] = $code;
if (($response['ActionStatus'] ?? '') !== 'OK') {
$empty['error'] = $response['ErrorInfo'] ?? ('ErrorCode ' . $code);
return $empty;
}
$msgList = $response['MsgList'] ?? [];
if (!is_array($msgList)) {
$msgList = [];
}
return [
'success' => true,
'msgList' => $msgList,
'complete' => (int)($response['Complete'] ?? 1),
'lastMsgKey' => $response['LastMsgKey'] ?? null,
'lastMsgTime' => isset($response['LastMsgTime']) ? (int)$response['LastMsgTime'] : null,
'error' => '',
'rawErrorCode' => $code,
];
} catch (\Exception $e) {
$empty['error'] = $e->getMessage();
return $empty;
}
}
/**
* @notes 发送HTTP POST请求
* @param string $url
* @param string $data
* @return string|false
*/
private function httpPost(string $url, string $data, int $timeout = 10)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen($data)
]);
$result = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
\think\facade\Log::error('HTTP请求失败', [
'url' => $url,
'error' => $error
]);
return false;
}
return $result;
}
}