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