using OrderSupportTicket.Models; using OrderSupportTicket.Models.Api; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; namespace OrderSupportTicket.Services; /// /// 工单列表本地缓存的读取与未读比较。 /// public sealed class WorkOrderCacheService { private const string ConfigName = "ordersupportticket"; private static readonly object SyncRoot = new object(); /// /// 读取指定账号的工单列表缓存。 /// public WorkOrderListCache Load(string userName) { if (string.IsNullOrWhiteSpace(userName)) return null; try { lock (SyncRoot) { var path = NaiBa.Util.Helper.ConfigHelper.GetConfigUserPath(ConfigName); if (!File.Exists(path)) return null; var cache = NaiBa.Util.Helper.ConfigHelper .LoadConfig(ConfigName); if (cache?.Items == null) return null; if (!string.Equals(cache.UserName, userName, StringComparison.Ordinal)) return null; return cache; } } catch { // 本地缓存损坏或读取失败时按无缓存处理。 return null; } } /// /// 保存工单列表缓存。 /// public void Save(WorkOrderListCache cache) { if (cache == null) throw new ArgumentNullException(nameof(cache)); if (string.IsNullOrWhiteSpace(cache.UserName)) throw new ArgumentException("缓存缺少登录账号。", nameof(cache)); lock (SyncRoot) { NaiBa.Util.Helper.ConfigHelper.SaveConfig(ConfigName, cache); } } /// /// 根据服务端工单列表和历史缓存计算每个工单的未读消息数。 /// 返回值的键为 "appId|wechatOrderId"。 /// public Dictionary BuildUnreadMap( IEnumerable workOrders, WorkOrderListCache cache) { var result = new Dictionary(StringComparer.Ordinal); var savedItems = (cache?.Items ?? new List()) .Where(item => item != null && !string.IsNullOrWhiteSpace(item.OrderId)) .GroupBy(item => item.OrderId, StringComparer.Ordinal) .ToDictionary( group => group.Key, group => group.First(), StringComparer.Ordinal); var hasSavedCache = cache?.Items != null && cache.Items.Count > 0; foreach (var dto in workOrders ?? Enumerable.Empty()) { if (dto == null || string.IsNullOrWhiteSpace(dto.AppId) || string.IsNullOrWhiteSpace(dto.WechatOrderId)) { continue; } var hasNewMessage = !hasSavedCache; if (hasSavedCache) { if (savedItems.TryGetValue(dto.WechatOrderId, out var saved)) { hasNewMessage = ParseTimestamp(dto.LastMessageAt) > saved.LastMessageAt; } else { // 缓存中没有该工单,说明是新增工单。 hasNewMessage = true; } } if (!hasNewMessage) continue; // 有历史基线时,时间更新但服务端未读数为 0 也按 1 条提示; // 首次运行没有基线时只信任服务端未读数,避免把历史工单全部标成未读。 var unreadCount = hasSavedCache ? Math.Max(1, dto.UnreadCount) : dto.UnreadCount; if (unreadCount <= 0) continue; result[BuildTicketKey(dto.AppId, dto.WechatOrderId)] = unreadCount; } return result; } /// /// 构建工单实时通知使用的唯一键。 /// public static string BuildTicketKey(string appId, string wechatOrderId) { return appId + "|" + wechatOrderId; } /// /// 解析服务端 ISO 8601 时间字符串。 /// public static DateTimeOffset ParseTimestamp(string value) { if (DateTimeOffset.TryParse( value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed)) { return parsed; } return default; } }