diff --git a/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/Statistics/FailureReasonDetailExportItem.cs b/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/Statistics/FailureReasonDetailExportItem.cs index a61f0790..be8fbfb4 100644 --- a/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/Statistics/FailureReasonDetailExportItem.cs +++ b/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/Statistics/FailureReasonDetailExportItem.cs @@ -15,10 +15,10 @@ public sealed class FailureReasonDetailExportItem public string FailureReason { get; init; } = string.Empty; [ExporterHeader(DisplayName = "预计发送时间")] - public DateTimeOffset? ExpectSendTime { get; init; } + public DateTime? ExpectSendTime { get; init; } [ExporterHeader(DisplayName = "实际发送时间")] - public DateTimeOffset? SendTime { get; init; } + public DateTime? SendTime { get; init; } [ExporterHeader(DisplayName = "消息ID")] public string MessageId { get; init; } = string.Empty; diff --git a/src/Services/Masa.Mc.Service/Application/ChannelStatistics/ChannelStatisticsQueryHandler.cs b/src/Services/Masa.Mc.Service/Application/ChannelStatistics/ChannelStatisticsQueryHandler.cs index 016a8e9d..0ddf214e 100644 --- a/src/Services/Masa.Mc.Service/Application/ChannelStatistics/ChannelStatisticsQueryHandler.cs +++ b/src/Services/Masa.Mc.Service/Application/ChannelStatistics/ChannelStatisticsQueryHandler.cs @@ -175,14 +175,14 @@ public async Task ExportChannelFailureReasonDetailsAsync(ExportChannelFailureRea var exportQuery = records .Where(x => x.Success == false) .OrderByDescending(x => x.SendTime ?? x.ExpectSendTime ?? DateTimeOffset.MinValue) - .Select(x => new FailureReasonDetailExportItem + .Select(x => new { - DisplayName = x.DisplayName, - ChannelUserIdentity = x.ChannelUserIdentity, - FailureReason = string.IsNullOrWhiteSpace(x.FailureReason) ? "Unknown" : x.FailureReason, - ExpectSendTime = x.ExpectSendTime, - SendTime = x.SendTime, - MessageId = x.MessageId + x.DisplayName, + x.ChannelUserIdentity, + x.FailureReason, + x.ExpectSendTime, + x.SendTime, + x.MessageId }); const int maxExportCount = 100000; @@ -191,7 +191,17 @@ public async Task ExportChannelFailureReasonDetailsAsync(ExportChannelFailureRea { throw new UserFriendlyException("导出数据量过大,请缩小筛选范围"); } - var exportItems = await exportQuery.ToListAsync(); + var exportRecords = await exportQuery.ToListAsync(); + var exportTimeZone = CultureTimeZoneResolver.GetDefaultCultureTimeZone(); + var exportItems = exportRecords.Select(x => new FailureReasonDetailExportItem + { + DisplayName = x.DisplayName, + ChannelUserIdentity = x.ChannelUserIdentity, + FailureReason = string.IsNullOrWhiteSpace(x.FailureReason) ? "Unknown" : x.FailureReason, + ExpectSendTime = CultureTimeZoneResolver.ConvertTime(x.ExpectSendTime, exportTimeZone), + SendTime = CultureTimeZoneResolver.ConvertTime(x.SendTime, exportTimeZone), + MessageId = x.MessageId + }).ToList(); query.Result = await _exporter.ExportAsByteArray(exportItems); } diff --git a/src/Services/Masa.Mc.Service/Infrastructure/Extensions/CultureConfiguration.cs b/src/Services/Masa.Mc.Service/Infrastructure/Culture/CultureConfiguration.cs similarity index 92% rename from src/Services/Masa.Mc.Service/Infrastructure/Extensions/CultureConfiguration.cs rename to src/Services/Masa.Mc.Service/Infrastructure/Culture/CultureConfiguration.cs index df5fdbe8..df5041fd 100644 --- a/src/Services/Masa.Mc.Service/Infrastructure/Extensions/CultureConfiguration.cs +++ b/src/Services/Masa.Mc.Service/Infrastructure/Culture/CultureConfiguration.cs @@ -1,7 +1,7 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the Apache License. See LICENSE.txt in the project root for license information. -namespace Masa.Mc.Service.Admin.Infrastructure.Extensions; +namespace Masa.Mc.Service.Admin.Infrastructure.Culture; public static class CultureConfiguration { diff --git a/src/Services/Masa.Mc.Service/Infrastructure/Culture/CultureTimeZoneResolver.cs b/src/Services/Masa.Mc.Service/Infrastructure/Culture/CultureTimeZoneResolver.cs new file mode 100644 index 00000000..d2eef918 --- /dev/null +++ b/src/Services/Masa.Mc.Service/Infrastructure/Culture/CultureTimeZoneResolver.cs @@ -0,0 +1,68 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Service.Admin.Infrastructure.Culture; + +public static class CultureTimeZoneResolver +{ + private static readonly IReadOnlyDictionary RegionTimeZoneIds = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["CN"] = "China Standard Time" + }; + + public static TimeZoneInfo GetDefaultCultureTimeZone() + { + var culture = System.Globalization.CultureInfo.DefaultThreadCurrentCulture + ?? System.Globalization.CultureInfo.DefaultThreadCurrentUICulture + ?? System.Globalization.CultureInfo.CurrentCulture; + + return GetCultureTimeZone(culture); + } + + public static DateTime? ConvertToDefaultCultureTime(DateTimeOffset? time) + { + return ConvertTime(time, GetDefaultCultureTimeZone()); + } + + public static DateTime? ConvertTime(DateTimeOffset? time, TimeZoneInfo timeZone) + { + if (!time.HasValue) + { + return null; + } + + return TimeZoneInfo.ConvertTime(time.Value, timeZone).DateTime; + } + + private static TimeZoneInfo GetCultureTimeZone(System.Globalization.CultureInfo culture) + { + if (TryGetRegionName(culture, out var regionName) && + RegionTimeZoneIds.TryGetValue(regionName, out var timeZoneId)) + { + return TimeZoneUtil.FindTimeZoneById(timeZoneId); + } + + return TimeZoneInfo.Utc; + } + + private static bool TryGetRegionName(System.Globalization.CultureInfo culture, out string regionName) + { + regionName = string.Empty; + + if (culture.IsNeutralCulture || string.IsNullOrWhiteSpace(culture.Name)) + { + return false; + } + + try + { + regionName = new System.Globalization.RegionInfo(culture.Name).TwoLetterISORegionName; + return true; + } + catch (ArgumentException) + { + return false; + } + } + +} diff --git a/src/Services/Masa.Mc.Service/_Imports.cs b/src/Services/Masa.Mc.Service/_Imports.cs index 8646aa95..00635156 100644 --- a/src/Services/Masa.Mc.Service/_Imports.cs +++ b/src/Services/Masa.Mc.Service/_Imports.cs @@ -182,6 +182,7 @@ global using Masa.Mc.Service.Admin.EntityFrameworkCore; global using Masa.Mc.Service.Admin.Infrastructure.Authentication; global using Masa.Mc.Service.Admin.Infrastructure.ChannelUserFinder.Provider.Auth; +global using Masa.Mc.Service.Admin.Infrastructure.Culture; global using Masa.Mc.Service.Admin.Infrastructure.Extensions; global using Masa.Mc.Service.Admin.Infrastructure.MessageTaskJobService; global using Masa.Mc.Service.Admin.Infrastructure.Middleware;