From 533cf2215fd1c96dbff5b748d9e351a42e953bb4 Mon Sep 17 00:00:00 2001 From: wzh425 Date: Wed, 1 Jul 2026 13:32:54 +0800 Subject: [PATCH 1/3] feat: enhance message record handling with success status indexing and event triggering - Added logic to trigger an UpdateMessageTaskHistoryStatusEvent when a message record is created with a non-empty MessageTaskHistoryId. - Updated the database index for MessageRecords to include both MessageTaskHistoryId and Success for improved query performance. - Created a new migration to reflect the changes in the database schema, ensuring the integrity of the message record history. --- .../Aggregates/MessageRecord.cs | 5 + ...ssageRecordHistorySuccessIndex.Designer.cs | 1442 +++++++++++++++++ ...951_AddMessageRecordHistorySuccessIndex.cs | 33 + .../Migrations/McDbContextModelSnapshot.cs | 4 +- .../McDbContextModelBuilderExtensions.cs | 2 +- .../SendSmsMessageEventHandler.cs | 6 + .../UpdateMessageTaskHistoryStatusHandler.cs | 36 +- 7 files changed, 1520 insertions(+), 8 deletions(-) create mode 100644 src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.Designer.cs create mode 100644 src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.cs diff --git a/src/Domain/Masa.Mc.Domain/MessageRecords/Aggregates/MessageRecord.cs b/src/Domain/Masa.Mc.Domain/MessageRecords/Aggregates/MessageRecord.cs index cca6945c..8225bb07 100644 --- a/src/Domain/Masa.Mc.Domain/MessageRecords/Aggregates/MessageRecord.cs +++ b/src/Domain/Masa.Mc.Domain/MessageRecords/Aggregates/MessageRecord.cs @@ -87,6 +87,11 @@ public void UpdateResult(bool success, string failureReason) { Success = success; FailureReason = failureReason; + + if (MessageTaskHistoryId != Guid.Empty) + { + AddDomainEvent(new UpdateMessageTaskHistoryStatusEvent(MessageTaskHistoryId)); + } } public virtual T GetDataValue(string name) diff --git a/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.Designer.cs b/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.Designer.cs new file mode 100644 index 00000000..c4df7a42 --- /dev/null +++ b/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.Designer.cs @@ -0,0 +1,1442 @@ +// +using System; +using Masa.Mc.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Masa.Mc.EntityFrameworkCore.PostgreSql.Migrations +{ + [DbContext(typeof(McDbContext))] + [Migration("20260701032951_AddMessageRecordHistorySuccessIndex")] + partial class AddMessageRecordHistorySuccessIndex + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Masa.BuildingBlocks.Dispatcher.IntegrationEvents.Logs.IntegrationEventLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EventId") + .HasColumnType("uuid"); + + b.Property("EventTypeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpandContent") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(36) + .HasColumnType("character varying(36)") + .HasColumnName("RowVersion"); + + b.Property("State") + .HasColumnType("integer"); + + b.Property("TimesSent") + .HasColumnType("integer"); + + b.Property("TransactionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EventId", "RowVersion" }, "IX_EventId_Version"); + + b.HasIndex(new[] { "State", "ModificationTime" }, "IX_State_MTime"); + + b.HasIndex(new[] { "State", "TimesSent", "ModificationTime" }, "IX_State_TimesSent_MTime"); + + b.ToTable("IntegrationEventLog", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Apps.Aggregates.AppDeviceToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("DeviceToken") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Platform") + .HasColumnType("integer"); + + b.Property("RegisteredTime") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId", "UserId", "Platform"); + + b.ToTable("AppDeviceTokens", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Apps.Aggregates.AppVendorConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Options") + .IsRequired() + .HasColumnType("text"); + + b.Property("Vendor") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId", "Vendor"); + + b.ToTable("AppVendorConfigs", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Channels.Aggregates.AppChannel", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("Code"); + + b.Property("Color") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Color"); + + b.Property("DisplayName") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("DisplayName"); + + b.Property("Provider") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("integer") + .HasColumnName("Provider"); + + b.Property("Scheme") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Scheme"); + + b.Property("SchemeField") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("SchemeField"); + + b.Property("Type") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("integer") + .HasColumnName("Type"); + + b.HasKey("Id"); + + b.ToTable("Channels", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Channels.Aggregates.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("Code"); + + b.Property("Color") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Color"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("DisplayName") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("DisplayName"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsStatic") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Provider") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("integer") + .HasColumnName("Provider"); + + b.Property("Scheme") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Scheme"); + + b.Property("SchemeField") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("SchemeField"); + + b.Property("Type") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("integer") + .HasColumnName("Type"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("Type"); + + b.ToTable("Channels", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageInfos.Aggregates.MessageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("MessageInfos", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageReceipts.Aggregates.SmsInbound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddSerial") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("Mobile") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("SendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SmsContent") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.HasKey("Id"); + + b.HasIndex("Provider"); + + b.HasIndex("SendTime"); + + b.HasIndex("ChannelId", "SendTime"); + + b.HasIndex("Mobile", "SendTime"); + + b.ToTable("SmsInbounds", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageRecords.Aggregates.MessageRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("ChannelUserIdentity") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExpectSendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text"); + + b.Property("FailureReason") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("MessageEntityId") + .HasColumnType("uuid"); + + b.Property("MessageEntityType") + .HasColumnType("integer"); + + b.Property("MessageId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MessageTaskHistoryId") + .HasColumnType("uuid"); + + b.Property("MessageTaskId") + .HasColumnType("uuid"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("SendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.Property("SystemId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Variables") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("MessageId"); + + b.HasIndex("UserId"); + + b.HasIndex("MessageTaskHistoryId", "Success"); + + b.ToTable("MessageRecords", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageReceiverUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelUserIdentity") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MessageTaskHistoryId") + .HasColumnType("uuid"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Variables") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("MessageTaskHistoryId", "ChannelUserIdentity", "Platform"); + + b.ToTable("MessageReceiverUsers", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("ChannelType") + .HasColumnType("integer"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .HasColumnType("integer"); + + b.Property("ExpectSendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsDraft") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("ReceiverType") + .HasColumnType("integer"); + + b.Property("Receivers") + .IsRequired() + .HasColumnType("text"); + + b.Property("SchedulerJobId") + .HasColumnType("uuid"); + + b.Property("SelectReceiverType") + .HasColumnType("integer"); + + b.Property("SendRules") + .IsRequired() + .HasColumnType("text"); + + b.Property("SendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Sign") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Source") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("SystemId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Variables") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("Source"); + + b.ToTable("MessageTasks", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTaskHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletionTime") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsTest") + .HasColumnType("boolean"); + + b.Property("MessageTaskId") + .HasColumnType("uuid"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("SchedulerTaskId") + .HasColumnType("uuid"); + + b.Property("SendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TaskHistoryNo") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("WithdrawTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("MessageTaskId"); + + b.ToTable("MessageTaskHistorys", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuditReason") + .IsRequired() + .HasColumnType("text"); + + b.Property("AuditStatus") + .HasColumnType("integer"); + + b.Property("AuditTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Example") + .IsRequired() + .HasColumnType("text"); + + b.Property("InvalidTime") + .HasColumnType("timestamp with time zone"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsStatic") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Options") + .IsRequired() + .HasColumnType("text"); + + b.Property("PerDayLimit") + .HasColumnType("bigint"); + + b.Property("Sign") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TemplateId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("TemplateType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("Code"); + + b.ToTable("MessageTemplates", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplateItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("DisplayText") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("MappingCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageTemplateId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MessageTemplateId"); + + b.HasIndex("Code", "MessageTemplateId"); + + b.ToTable("MessageTemplateItems", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTemplates.Aggregates.SmsTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuditReason") + .IsRequired() + .HasColumnType("text"); + + b.Property("AuditStatus") + .HasColumnType("integer"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("TemplateCode") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("TemplateContent") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("TemplateType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.ToTable("SmsTemplates", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.ReceiverGroups.Aggregates.ReceiverGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("ReceiverGroups", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Unsubscriptions.Aggregates.Unsubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("ChannelProvider") + .HasColumnType("integer"); + + b.Property("ChannelType") + .HasColumnType("integer"); + + b.Property("ChannelUserIdentity") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("Keyword") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("LastInboundMessageId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ResubscribedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ScopeRefId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ScopeType") + .HasColumnType("integer"); + + b.Property("Source") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UnsubscribedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LastInboundMessageId"); + + b.HasIndex("UserId"); + + b.HasIndex("ChannelType", "ChannelProvider", "UnsubscribedAt"); + + b.HasIndex("ChannelUserIdentity", "Status", "ScopeType"); + + b.HasIndex("ChannelId", "ChannelUserIdentity", "Status", "UnsubscribedAt"); + + b.HasIndex("ChannelId", "ChannelUserIdentity", "ScopeType", "ScopeRefId", "Status", "UnsubscribedAt"); + + b.ToTable("Unsubscriptions", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Unsubscriptions.Aggregates.UnsubscriptionTimeline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .HasColumnType("integer"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("Detail") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UnsubscriptionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UnsubscriptionId", "OccurredAt"); + + b.HasIndex("OccurredAt", "Action", "UnsubscriptionId"); + + b.ToTable("UnsubscriptionTimelines", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.WebsiteMessages.Aggregates.WebsiteMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.Property("IsWithdrawn") + .HasColumnType("boolean"); + + b.Property("LinkUrl") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MessageTaskHistoryId") + .HasColumnType("uuid"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("ReadTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("UserId", "ChannelId"); + + b.ToTable("WebsiteMessages", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.WebsiteMessages.Aggregates.WebsiteMessageCursor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("UpdateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebsiteMessageCursors", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Channels.Aggregates.AppChannel", b => + { + b.HasOne("Masa.Mc.Domain.Channels.Aggregates.Channel", null) + .WithOne() + .HasForeignKey("Masa.Mc.Domain.Channels.Aggregates.AppChannel", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageInfos.Aggregates.MessageInfo", b => + { + b.OwnsOne("Masa.Mc.Domain.MessageInfos.Aggregates.MessageContent", "MessageContent", b1 => + { + b1.Property("MessageInfoId") + .HasColumnType("uuid"); + + b1.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasColumnName("Content"); + + b1.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b1.Property("IsJump") + .HasMaxLength(128) + .HasColumnType("boolean") + .HasColumnName("IsJump"); + + b1.Property("JumpUrl") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("JumpUrl"); + + b1.Property("Markdown") + .IsRequired() + .HasColumnType("text") + .HasColumnName("Markdown"); + + b1.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Title"); + + b1.HasKey("MessageInfoId"); + + b1.ToTable("MessageInfos"); + + b1.WithOwner() + .HasForeignKey("MessageInfoId"); + }); + + b.Navigation("MessageContent") + .IsRequired(); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageRecords.Aggregates.MessageRecord", b => + { + b.HasOne("Masa.Mc.Domain.Channels.Aggregates.AppChannel", "Channel") + .WithMany() + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageReceiverUser", b => + { + b.HasOne("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTaskHistory", null) + .WithMany("ReceiverUsers") + .HasForeignKey("MessageTaskHistoryId"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTask", b => + { + b.HasOne("Masa.Mc.Domain.Channels.Aggregates.AppChannel", "Channel") + .WithMany() + .HasForeignKey("ChannelId"); + + b.Navigation("Channel"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTaskHistory", b => + { + b.HasOne("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTask", "MessageTask") + .WithMany() + .HasForeignKey("MessageTaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MessageTask"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplate", b => + { + b.OwnsOne("Masa.Mc.Domain.MessageInfos.Aggregates.MessageContent", "MessageContent", b1 => + { + b1.Property("MessageTemplateId") + .HasColumnType("uuid"); + + b1.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasColumnName("Content"); + + b1.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b1.Property("IsJump") + .HasMaxLength(128) + .HasColumnType("boolean") + .HasColumnName("IsJump"); + + b1.Property("JumpUrl") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("JumpUrl"); + + b1.Property("Markdown") + .IsRequired() + .HasColumnType("text") + .HasColumnName("Markdown"); + + b1.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Title"); + + b1.HasKey("MessageTemplateId"); + + b1.ToTable("MessageTemplates"); + + b1.WithOwner() + .HasForeignKey("MessageTemplateId"); + }); + + b.OwnsOne("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplateUnsubscribeConfig", "UnsubscribeConfig", b1 => + { + b1.Property("MessageTemplateId") + .HasColumnType("uuid"); + + b1.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("Enabled"); + + b1.Property("ResubscribeAutoReplyTemplateId") + .HasColumnType("uuid") + .HasColumnName("ResubscribeAutoReplyTemplateId"); + + b1.Property("ResubscribeKeyword") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("") + .HasColumnName("ResubscribeKeyword"); + + b1.Property("UnsubscribeAutoReplyTemplateId") + .HasColumnType("uuid") + .HasColumnName("UnsubscribeAutoReplyTemplateId"); + + b1.Property("UnsubscribeKeyword") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("") + .HasColumnName("UnsubscribeKeyword"); + + b1.HasKey("MessageTemplateId"); + + b1.ToTable("MessageTemplateUnsubscribeConfigs", (string)null); + + b1.WithOwner() + .HasForeignKey("MessageTemplateId"); + }); + + b.Navigation("MessageContent") + .IsRequired(); + + b.Navigation("UnsubscribeConfig"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplateItem", b => + { + b.HasOne("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplate", null) + .WithMany("Items") + .HasForeignKey("MessageTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Masa.Mc.Domain.ReceiverGroups.Aggregates.ReceiverGroup", b => + { + b.OwnsMany("Masa.Mc.Domain.ReceiverGroups.Aggregates.ReceiverGroupItem", "Items", b1 => + { + b1.Property("GroupId") + .HasColumnType("uuid"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b1.Property("Avatar") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b1.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b1.Property("SubjectId") + .HasColumnType("uuid"); + + b1.Property("Type") + .HasColumnType("integer"); + + b1.HasKey("GroupId", "Id"); + + b1.ToTable("ReceiverGroupItems", (string)null); + + b1.WithOwner() + .HasForeignKey("GroupId"); + }); + + b.Navigation("Items"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Unsubscriptions.Aggregates.UnsubscriptionTimeline", b => + { + b.HasOne("Masa.Mc.Domain.Unsubscriptions.Aggregates.Unsubscription", null) + .WithMany("Timelines") + .HasForeignKey("UnsubscriptionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Masa.Mc.Domain.WebsiteMessages.Aggregates.WebsiteMessage", b => + { + b.HasOne("Masa.Mc.Domain.Channels.Aggregates.AppChannel", "Channel") + .WithMany() + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Masa.Mc.Domain.WebsiteMessages.Aggregates.WebsiteMessageTag", "Tags", b1 => + { + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b1.Property("ChannelId") + .HasColumnType("uuid"); + + b1.Property("CreationTime") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone"); + + b1.Property("Tag") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Tag"); + + b1.Property("UserId") + .HasColumnType("uuid"); + + b1.Property("WebsiteMessageId") + .HasColumnType("uuid"); + + b1.HasKey("Id"); + + b1.HasIndex("ChannelId"); + + b1.HasIndex("Tag"); + + b1.HasIndex("UserId"); + + b1.HasIndex("WebsiteMessageId"); + + b1.ToTable("WebsiteMessageTags", (string)null); + + b1.WithOwner() + .HasForeignKey("WebsiteMessageId"); + }); + + b.Navigation("Channel"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTaskHistory", b => + { + b.Navigation("ReceiverUsers"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplate", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Unsubscriptions.Aggregates.Unsubscription", b => + { + b.Navigation("Timelines"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.cs b/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.cs new file mode 100644 index 00000000..80d52b3f --- /dev/null +++ b/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Masa.Mc.EntityFrameworkCore.PostgreSql.Migrations +{ + public partial class AddMessageRecordHistorySuccessIndex : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_MessageRecords_MessageTaskHistoryId", + table: "MessageRecords"); + + migrationBuilder.CreateIndex( + name: "IX_MessageRecords_MessageTaskHistoryId_Success", + table: "MessageRecords", + columns: new[] { "MessageTaskHistoryId", "Success" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_MessageRecords_MessageTaskHistoryId_Success", + table: "MessageRecords"); + + migrationBuilder.CreateIndex( + name: "IX_MessageRecords_MessageTaskHistoryId", + table: "MessageRecords", + column: "MessageTaskHistoryId"); + } + } +} diff --git a/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/McDbContextModelSnapshot.cs b/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/McDbContextModelSnapshot.cs index 197a0898..0f8f6f2c 100644 --- a/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/McDbContextModelSnapshot.cs +++ b/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/McDbContextModelSnapshot.cs @@ -478,10 +478,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("MessageId"); - b.HasIndex("MessageTaskHistoryId"); - b.HasIndex("UserId"); + b.HasIndex("MessageTaskHistoryId", "Success"); + b.ToTable("MessageRecords", (string)null); }); diff --git a/src/Infrastructure/Masa.Mc.EntityFrameworkCore/McDbContextModelBuilderExtensions.cs b/src/Infrastructure/Masa.Mc.EntityFrameworkCore/McDbContextModelBuilderExtensions.cs index 71f0f63b..478cdac6 100644 --- a/src/Infrastructure/Masa.Mc.EntityFrameworkCore/McDbContextModelBuilderExtensions.cs +++ b/src/Infrastructure/Masa.Mc.EntityFrameworkCore/McDbContextModelBuilderExtensions.cs @@ -135,7 +135,7 @@ public static void ConfigureMC(this ModelBuilder builder) b.Property(x => x.ExtraProperties).HasConversion(new ExtraPropertiesValueConverter()).Metadata.SetValueComparer(new ExtraPropertyDictionaryValueComparer()); b.Property(x => x.Variables).HasConversion(new ExtraPropertiesValueConverter()).Metadata.SetValueComparer(new ExtraPropertyDictionaryValueComparer()); b.HasIndex(x => x.UserId); - b.HasIndex(x => x.MessageTaskHistoryId); + b.HasIndex(x => new { x.MessageTaskHistoryId, x.Success }); b.HasIndex(x => x.MessageId); }); diff --git a/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendSmsMessageEventHandler.cs b/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendSmsMessageEventHandler.cs index 27e4edf2..45bd8fb8 100644 --- a/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendSmsMessageEventHandler.cs +++ b/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendSmsMessageEventHandler.cs @@ -155,6 +155,12 @@ public async Task SaveAsync(SendSmsMessageEvent eto) var messageTaskHistory = eto.MessageTaskHistory; + // 存在待回执记录时保持发送中,等待回执完成后再汇总最终状态。 + if (eto.MessageRecords.Any(x => x.Success == null)) + { + return; + } + var result = !eto.MessageRecords.Any(x => x.Success != true) ? MessageTaskHistoryStatuses.Success : (!eto.MessageRecords.Any(x => x.Success == true) ? MessageTaskHistoryStatuses.Fail : MessageTaskHistoryStatuses.PartialFailure); messageTaskHistory.SetResult(result); await _messageTaskHistoryRepository.UpdateAsync(messageTaskHistory); diff --git a/src/Services/Masa.Mc.Service/Domain/MessageTasks/EventHandler/UpdateMessageTaskHistoryStatusHandler.cs b/src/Services/Masa.Mc.Service/Domain/MessageTasks/EventHandler/UpdateMessageTaskHistoryStatusHandler.cs index 1bf8d025..3dc4c416 100644 --- a/src/Services/Masa.Mc.Service/Domain/MessageTasks/EventHandler/UpdateMessageTaskHistoryStatusHandler.cs +++ b/src/Services/Masa.Mc.Service/Domain/MessageTasks/EventHandler/UpdateMessageTaskHistoryStatusHandler.cs @@ -21,19 +21,45 @@ public async Task HandleEventAsync(UpdateMessageTaskHistoryStatusEvent eto) var history = await _historyRepository.FindAsync(x => x.Id == eto.MessageTaskHistoryId, false); if (history == null) return; + if (history.Status == MessageTaskHistoryStatuses.Withdrawn) + { + return; + } + + if (await _messageRecordRepository.AnyAsync(x => x.MessageTaskHistoryId == history.Id && x.Success == null)) + { + return; + } + var hasSuccessMessageRecord = await _messageRecordRepository.AnyAsync(x => x.MessageTaskHistoryId == history.Id && x.Success == true); + var hasFailMessageRecord = await _messageRecordRepository.AnyAsync(x => x.MessageTaskHistoryId == history.Id && x.Success == false); + + if (!hasSuccessMessageRecord && !hasFailMessageRecord) + { + return; + } - if (!await _messageRecordRepository.AnyAsync(x => x.MessageTaskHistoryId == history.Id && x.Success != true)) + MessageTaskHistoryStatuses targetStatus; + if (!hasFailMessageRecord) { - history.SetResult(MessageTaskHistoryStatuses.Success); + targetStatus = MessageTaskHistoryStatuses.Success; } else if (!hasSuccessMessageRecord) { - history.SetResult(MessageTaskHistoryStatuses.Fail); + targetStatus = MessageTaskHistoryStatuses.Fail; } - else if (hasSuccessMessageRecord) + else { - history.SetResult(MessageTaskHistoryStatuses.PartialFailure); + targetStatus = MessageTaskHistoryStatuses.PartialFailure; } + + if (history.Status == targetStatus) + { + return; + } + + history.SetResult(targetStatus); + + await _historyRepository.UpdateAsync(history); } } From ed36c899451f05d2961970c2a29eceb5ba186e9b Mon Sep 17 00:00:00 2001 From: wzh425 Date: Mon, 27 Jul 2026 17:51:00 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E5=AF=BC=E5=87=BA=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E5=8E=9F=E5=9B=A0=E6=98=8E=E7=BB=86=E6=97=B6=E9=97=B4=E6=8C=89?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E6=96=87=E5=8C=96=E6=97=B6=E5=8C=BA=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FailureReasonDetailExportItem.cs | 4 +- .../ChannelStatisticsQueryHandler.cs | 26 ++++--- .../Extensions/CultureTimeZoneResolver.cs | 68 +++++++++++++++++++ 3 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 src/Services/Masa.Mc.Service/Infrastructure/Extensions/CultureTimeZoneResolver.cs 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/CultureTimeZoneResolver.cs b/src/Services/Masa.Mc.Service/Infrastructure/Extensions/CultureTimeZoneResolver.cs new file mode 100644 index 00000000..45115a21 --- /dev/null +++ b/src/Services/Masa.Mc.Service/Infrastructure/Extensions/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.Extensions; + +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; + } + } + +} From e9577e2b169b7b42b1bbdc4c7c58730090d9ec1f Mon Sep 17 00:00:00 2001 From: wzh425 Date: Tue, 28 Jul 2026 10:58:14 +0800 Subject: [PATCH 3/3] refactor: remove unused culture configuration and timezone resolver classes - Deleted CultureConfiguration and CultureTimeZoneResolver classes as they are no longer needed. - Added a new using directive for Masa.Mc.Service.Admin.Infrastructure.Culture in _Imports.cs to support future culture-related functionalities. --- .../{Extensions => Culture}/CultureConfiguration.cs | 2 +- .../{Extensions => Culture}/CultureTimeZoneResolver.cs | 2 +- src/Services/Masa.Mc.Service/_Imports.cs | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) rename src/Services/Masa.Mc.Service/Infrastructure/{Extensions => Culture}/CultureConfiguration.cs (92%) rename src/Services/Masa.Mc.Service/Infrastructure/{Extensions => Culture}/CultureTimeZoneResolver.cs (97%) 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/Extensions/CultureTimeZoneResolver.cs b/src/Services/Masa.Mc.Service/Infrastructure/Culture/CultureTimeZoneResolver.cs similarity index 97% rename from src/Services/Masa.Mc.Service/Infrastructure/Extensions/CultureTimeZoneResolver.cs rename to src/Services/Masa.Mc.Service/Infrastructure/Culture/CultureTimeZoneResolver.cs index 45115a21..d2eef918 100644 --- a/src/Services/Masa.Mc.Service/Infrastructure/Extensions/CultureTimeZoneResolver.cs +++ b/src/Services/Masa.Mc.Service/Infrastructure/Culture/CultureTimeZoneResolver.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 CultureTimeZoneResolver { 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;