新增功能

This commit is contained in:
Your Name
2026-03-04 15:32:30 +08:00
parent a07e844c47
commit ab77f5488d
2266 changed files with 177942 additions and 3444 deletions
+174
View File
@@ -0,0 +1,174 @@
<?php
namespace app\common\service;
/**
* 腾讯云IM UserSig生成类(官方算法)
* 参考:https://github.com/tencentyun/tls-sig-api-v2-php
*/
class TLSSigAPIv2
{
private $sdkappid;
private $key;
public function __construct($sdkappid, $key)
{
$this->sdkappid = $sdkappid;
$this->key = $key;
}
/**
* 生成UserSig
* @param string $identifier 用户ID
* @param int $expire 过期时间(秒)
* @return string UserSig
*/
public function genUserSig($identifier, $expire = 86400)
{
return $this->__genSig($identifier, $expire, '', false);
}
/**
* 生成带权限的UserSig
* @param string $identifier 用户ID
* @param int $expire 过期时间(秒)
* @param string $userbuf 用户权限buffer
* @return string UserSig
*/
public function genUserSigWithUserBuf($identifier, $expire, $userbuf)
{
return $this->__genSig($identifier, $expire, $userbuf, true);
}
/**
* 内部方法:生成签名
*/
private function __genSig($identifier, $expire, $userbuf, $userbuf_enabled)
{
$current = time();
$sigDoc = [
'TLS.ver' => '2.0',
'TLS.identifier' => strval($identifier),
'TLS.sdkappid' => intval($this->sdkappid),
'TLS.expire' => intval($expire),
'TLS.time' => intval($current)
];
$base64_userbuf = '';
if ($userbuf_enabled) {
$base64_userbuf = base64_encode($userbuf);
$sigDoc['TLS.userbuf'] = $base64_userbuf;
}
$sig = $this->hmacsha256($identifier, $current, $expire, $base64_userbuf, $userbuf_enabled);
$sigDoc['TLS.sig'] = base64_encode($sig);
$json_text = json_encode($sigDoc);
$compressed = gzcompress($json_text);
return $this->base64_url_encode($compressed);
}
/**
* 生成HMAC-SHA256签名
*/
private function hmacsha256($identifier, $curr_time, $expire, $base64_userbuf, $userbuf_enabled)
{
$content_to_be_signed = "TLS.identifier:" . $identifier . "\n"
. "TLS.sdkappid:" . $this->sdkappid . "\n"
. "TLS.time:" . $curr_time . "\n"
. "TLS.expire:" . $expire . "\n";
if ($userbuf_enabled) {
$content_to_be_signed .= "TLS.userbuf:" . $base64_userbuf . "\n";
}
return hash_hmac('sha256', $content_to_be_signed, $this->key, true);
}
/**
* Base64 URL安全编码
*/
private function base64_url_encode($input)
{
return str_replace(['+', '/', '='], ['*', '-', '_'], base64_encode($input));
}
/**
* Base64 URL安全解码
*/
private function base64_url_decode($base64_url_string)
{
return base64_decode(str_replace(['*', '-', '_'], ['+', '/', '='], $base64_url_string));
}
/**
* 验证UserSig
* @param string $sig UserSig
* @param string $identifier 用户ID
* @param int $init_time 初始化时间
* @param int $expire_time 过期时间
* @param string $userbuf 用户权限buffer
* @param string $error_msg 错误信息
* @return bool 是否有效
*/
public function verifySig($sig, $identifier, &$init_time, &$expire_time, &$userbuf, &$error_msg)
{
try {
$error_msg = '';
$compressed_sig = $this->base64_url_decode($sig);
$pre_level = error_reporting(E_ERROR);
$uncompressed_sig = gzuncompress($compressed_sig);
error_reporting($pre_level);
if ($uncompressed_sig === false) {
throw new \Exception('gzuncompress error');
}
$sig_doc = json_decode($uncompressed_sig, true);
if ($sig_doc === false) {
throw new \Exception('json_decode error');
}
if ($sig_doc['TLS.identifier'] !== $identifier) {
throw new \Exception('identifier dosen\'t match');
}
if ($sig_doc['TLS.sdkappid'] != $this->sdkappid) {
throw new \Exception('sdkappid dosen\'t match');
}
$sig = base64_decode($sig_doc['TLS.sig']);
if ($sig === false) {
throw new \Exception('sig base64_decode error');
}
$init_time = $sig_doc['TLS.time'];
$expire_time = $sig_doc['TLS.expire'];
$curr_time = time();
if ($curr_time > $init_time + $expire_time) {
throw new \Exception('sig expired');
}
$userbuf_enabled = false;
$base64_userbuf = '';
if (isset($sig_doc['TLS.userbuf'])) {
$base64_userbuf = $sig_doc['TLS.userbuf'];
$userbuf = base64_decode($base64_userbuf);
$userbuf_enabled = true;
}
$sigCalculated = $this->hmacsha256($identifier, $init_time, $expire_time, $base64_userbuf, $userbuf_enabled);
if ($sig != $sigCalculated) {
throw new \Exception('verify failed');
}
return true;
} catch (\Exception $ex) {
$error_msg = $ex->getMessage();
return false;
}
}
}
@@ -0,0 +1,273 @@
<?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()
];
}
}
/**
* @notes 发送HTTP POST请求
* @param string $url
* @param string $data
* @return string|false
*/
private function httpPost(string $url, string $data)
{
$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, 10);
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;
}
}