using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace CampusErrand.Services;
///
/// 腾讯 IM 服务,负责生成 UserSig 和调用服务端 REST API
///
public class TencentIMService
{
private readonly long _sdkAppId;
private readonly string _secretKey;
private readonly HttpClient _httpClient;
private const string AdminAccount = "administrator";
public TencentIMService(IConfiguration configuration, IHttpClientFactory httpClientFactory)
{
var config = configuration.GetSection("TencentIM");
_sdkAppId = config.GetValue("SDKAppId");
_secretKey = config["SecretKey"]!;
_httpClient = httpClientFactory.CreateClient();
}
public long SDKAppId => _sdkAppId;
///
/// 生成 UserSig
///
public string GenerateUserSig(string userId, int expireSeconds = 604800)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var obj = new Dictionary
{
["TLS.ver"] = "2.0",
["TLS.identifier"] = userId,
["TLS.sdkappid"] = _sdkAppId,
["TLS.expire"] = expireSeconds,
["TLS.time"] = now
};
var contentToSign = $"TLS.identifier:{userId}\nTLS.sdkappid:{_sdkAppId}\nTLS.time:{now}\nTLS.expire:{expireSeconds}\n";
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_secretKey));
var sig = hmac.ComputeHash(Encoding.UTF8.GetBytes(contentToSign));
obj["TLS.sig"] = Convert.ToBase64String(sig);
var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(obj));
using var output = new MemoryStream();
using (var zlib = new System.IO.Compression.ZLibStream(output, System.IO.Compression.CompressionLevel.Optimal))
{
zlib.Write(jsonBytes, 0, jsonBytes.Length);
}
return Convert.ToBase64String(output.ToArray())
.Replace('+', '*')
.Replace('/', '-')
.Replace('=', '_');
}
///
/// 通过服务端 REST API 拉取两个用户之间的漫游消息
///
public async Task GetRoamMessagesAsync(string fromUserId, string toUserId, int maxCnt = 100, long minTime = 0, long maxTime = 0)
{
var adminSig = GenerateUserSig(AdminAccount);
var random = Random.Shared.Next(100000, 999999);
var url = $"https://console.tim.qq.com/v4/openim/admin_getroammsg?sdkappid={_sdkAppId}&identifier={AdminAccount}&usersig={adminSig}&random={random}&contenttype=json";
var body = new
{
Operator_Account = fromUserId,
Peer_Account = toUserId,
MaxCnt = maxCnt,
MinTime = minTime,
MaxTime = maxTime > 0 ? maxTime : DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
var content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(url, content);
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine($"[IM] 拉取漫游消息: {fromUserId} -> {toUserId}, 响应: {json}");
return JsonSerializer.Deserialize(json);
}
///
/// 创建 IM 群组(每个订单一个群)
///
public async Task CreateGroupAsync(string groupId, string groupName, string ownerImId, string runnerImId)
{
var adminSig = GenerateUserSig(AdminAccount);
var random = Random.Shared.Next(100000, 999999);
var url = $"https://console.tim.qq.com/v4/group_open_http_svc/create_group?sdkappid={_sdkAppId}&identifier={AdminAccount}&usersig={adminSig}&random={random}&contenttype=json";
var body = new
{
Type = "Work",
GroupId = groupId,
Name = groupName,
Owner_Account = ownerImId,
MemberList = new[]
{
new { Member_Account = runnerImId }
}
};
var content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(url, content);
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine($"[IM] 创建群组: {groupId}, 响应: {json}");
var result = JsonSerializer.Deserialize(json);
var actionStatus = result.GetProperty("ActionStatus").GetString();
if (actionStatus == "OK")
return result.GetProperty("GroupId").GetString();
// 群已存在也算成功
var errorCode = result.GetProperty("ErrorCode").GetInt32();
if (errorCode == 10021)
return groupId;
return null;
}
///
/// 拉取群漫游消息(管理后台用)
///
public async Task GetGroupMessagesAsync(string groupId, int reqMsgSeq = 0, int reqMsgNumber = 20)
{
var adminSig = GenerateUserSig(AdminAccount);
var random = Random.Shared.Next(100000, 999999);
var url = $"https://console.tim.qq.com/v4/group_open_http_svc/group_msg_get_simple?sdkappid={_sdkAppId}&identifier={AdminAccount}&usersig={adminSig}&random={random}&contenttype=json";
// ReqMsgSeq 为 0 时不传该字段,让腾讯 IM 自动返回最新消息
object body = reqMsgSeq > 0
? new { GroupId = groupId, ReqMsgSeq = reqMsgSeq, ReqMsgNumber = reqMsgNumber }
: new { GroupId = groupId, ReqMsgNumber = reqMsgNumber };
var content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(url, content);
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine($"[IM] 拉取群消息 {groupId}: {json.Substring(0, Math.Min(json.Length, 500))}");
return JsonSerializer.Deserialize(json);
}
///
/// 解散 IM 群组
///
public async Task DestroyGroupAsync(string groupId)
{
var adminSig = GenerateUserSig(AdminAccount);
var random = Random.Shared.Next(100000, 999999);
var url = $"https://console.tim.qq.com/v4/group_open_http_svc/destroy_group?sdkappid={_sdkAppId}&identifier={AdminAccount}&usersig={adminSig}&random={random}&contenttype=json";
var body = new { GroupId = groupId };
var content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(url, content);
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine($"[IM] 解散群组 {groupId}: {json}");
}
}