149 lines
4.6 KiB
C#
149 lines
4.6 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 工单列表本地缓存的读取与未读比较。
|
|
/// </summary>
|
|
public sealed class WorkOrderCacheService
|
|
{
|
|
private const string ConfigName = "ordersupportticket";
|
|
private static readonly object SyncRoot = new object();
|
|
|
|
/// <summary>
|
|
/// 读取指定账号的工单列表缓存。
|
|
/// </summary>
|
|
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<WorkOrderListCache>(ConfigName);
|
|
if (cache?.Items == null)
|
|
return null;
|
|
if (!string.Equals(cache.UserName, userName, StringComparison.Ordinal))
|
|
return null;
|
|
|
|
return cache;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// 本地缓存损坏或读取失败时按无缓存处理。
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存工单列表缓存。
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据服务端工单列表和历史缓存计算每个工单的未读消息数。
|
|
/// 返回值的键为 "appId|wechatOrderId"。
|
|
/// </summary>
|
|
public Dictionary<string, int> BuildUnreadMap(
|
|
IEnumerable<WorkOrderSummaryDto> workOrders,
|
|
WorkOrderListCache cache)
|
|
{
|
|
var result = new Dictionary<string, int>(StringComparer.Ordinal);
|
|
var savedItems = (cache?.Items ?? new List<WorkOrderListCacheItem>())
|
|
.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<WorkOrderSummaryDto>())
|
|
{
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 构建工单实时通知使用的唯一键。
|
|
/// </summary>
|
|
public static string BuildTicketKey(string appId, string wechatOrderId)
|
|
{
|
|
return appId + "|" + wechatOrderId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 解析服务端 ISO 8601 时间字符串。
|
|
/// </summary>
|
|
public static DateTimeOffset ParseTimestamp(string value)
|
|
{
|
|
if (DateTimeOffset.TryParse(
|
|
value,
|
|
CultureInfo.InvariantCulture,
|
|
DateTimeStyles.AssumeUniversal,
|
|
out var parsed))
|
|
{
|
|
return parsed;
|
|
}
|
|
return default;
|
|
}
|
|
} |