feat: 完成会员消息触达后端模块

This commit is contained in:
2026-03-04 11:53:52 +08:00
parent 2970134200
commit a8cfda88f7
33 changed files with 4282 additions and 0 deletions

View File

@@ -0,0 +1,530 @@
namespace TakeoutSaaS.Application.App.Members.MessageReach.Dto;
/// <summary>
/// 消息触达统计 DTO。
/// </summary>
public sealed class MemberMessageReachStatsDto
{
/// <summary>
/// 本月发送消息条数。
/// </summary>
public int MonthlySentCount { get; init; }
/// <summary>
/// 本月触达人数。
/// </summary>
public int ReachMemberCount { get; init; }
/// <summary>
/// 打开率百分比0-100
/// </summary>
public decimal OpenRate { get; init; }
/// <summary>
/// 转化率百分比0-100
/// </summary>
public decimal ConversionRate { get; init; }
}
/// <summary>
/// 消息列表结果 DTO。
/// </summary>
public sealed class MemberMessageReachListResultDto
{
/// <summary>
/// 列表项。
/// </summary>
public IReadOnlyList<MemberMessageReachListItemDto> Items { get; init; } = [];
/// <summary>
/// 总数。
/// </summary>
public int TotalCount { get; init; }
/// <summary>
/// 页码。
/// </summary>
public int Page { get; init; }
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; init; }
}
/// <summary>
/// 消息列表项 DTO。
/// </summary>
public sealed class MemberMessageReachListItemDto
{
/// <summary>
/// 消息标识。
/// </summary>
public long MessageId { get; init; }
/// <summary>
/// 消息标题。
/// </summary>
public string Title { get; init; } = string.Empty;
/// <summary>
/// 渠道。
/// </summary>
public IReadOnlyList<string> Channels { get; init; } = [];
/// <summary>
/// 目标描述。
/// </summary>
public string AudienceText { get; init; } = string.Empty;
/// <summary>
/// 预计触达人数。
/// </summary>
public int EstimatedReachCount { get; init; }
/// <summary>
/// 发送状态。
/// </summary>
public string Status { get; init; } = string.Empty;
/// <summary>
/// 发送时间UTC
/// </summary>
public DateTime? SentAt { get; init; }
/// <summary>
/// 定时发送时间UTC
/// </summary>
public DateTime? ScheduledAt { get; init; }
/// <summary>
/// 打开率百分比0-100
/// </summary>
public decimal OpenRate { get; init; }
/// <summary>
/// 转化率百分比0-100
/// </summary>
public decimal ConversionRate { get; init; }
}
/// <summary>
/// 消息详情 DTO。
/// </summary>
public sealed class MemberMessageReachDetailDto
{
/// <summary>
/// 消息标识。
/// </summary>
public long MessageId { get; init; }
/// <summary>
/// 模板标识。
/// </summary>
public long? TemplateId { get; init; }
/// <summary>
/// 消息标题。
/// </summary>
public string Title { get; init; } = string.Empty;
/// <summary>
/// 消息正文。
/// </summary>
public string Content { get; init; } = string.Empty;
/// <summary>
/// 渠道。
/// </summary>
public IReadOnlyList<string> Channels { get; init; } = [];
/// <summary>
/// 目标类型。
/// </summary>
public string AudienceType { get; init; } = string.Empty;
/// <summary>
/// 目标标签。
/// </summary>
public IReadOnlyList<string> AudienceTags { get; init; } = [];
/// <summary>
/// 目标描述。
/// </summary>
public string AudienceText { get; init; } = string.Empty;
/// <summary>
/// 预计触达人数。
/// </summary>
public int EstimatedReachCount { get; init; }
/// <summary>
/// 发送时间类型。
/// </summary>
public string ScheduleType { get; init; } = string.Empty;
/// <summary>
/// 定时发送时间UTC
/// </summary>
public DateTime? ScheduledAt { get; init; }
/// <summary>
/// 发送状态。
/// </summary>
public string Status { get; init; } = string.Empty;
/// <summary>
/// 实际发送时间UTC
/// </summary>
public DateTime? SentAt { get; init; }
/// <summary>
/// 发送成功数量。
/// </summary>
public int SentCount { get; init; }
/// <summary>
/// 已读数量。
/// </summary>
public int ReadCount { get; init; }
/// <summary>
/// 转化数量。
/// </summary>
public int ConvertedCount { get; init; }
/// <summary>
/// 打开率百分比0-100
/// </summary>
public decimal OpenRate { get; init; }
/// <summary>
/// 转化率百分比0-100
/// </summary>
public decimal ConversionRate { get; init; }
/// <summary>
/// 最后错误信息。
/// </summary>
public string? LastError { get; init; }
/// <summary>
/// 收件明细。
/// </summary>
public IReadOnlyList<MemberMessageReachRecipientDto> Recipients { get; init; } = [];
}
/// <summary>
/// 收件明细 DTO。
/// </summary>
public sealed class MemberMessageReachRecipientDto
{
/// <summary>
/// 会员标识。
/// </summary>
public long MemberId { get; init; }
/// <summary>
/// 渠道。
/// </summary>
public string Channel { get; init; } = string.Empty;
/// <summary>
/// 状态。
/// </summary>
public string Status { get; init; } = string.Empty;
/// <summary>
/// 手机号快照。
/// </summary>
public string? Mobile { get; init; }
/// <summary>
/// OpenId 快照。
/// </summary>
public string? OpenId { get; init; }
/// <summary>
/// 发送时间UTC
/// </summary>
public DateTime? SentAt { get; init; }
/// <summary>
/// 已读时间UTC
/// </summary>
public DateTime? ReadAt { get; init; }
/// <summary>
/// 转化时间UTC
/// </summary>
public DateTime? ConvertedAt { get; init; }
/// <summary>
/// 失败信息。
/// </summary>
public string? ErrorMessage { get; init; }
}
/// <summary>
/// 模板列表结果 DTO。
/// </summary>
public sealed class MemberMessageTemplateListResultDto
{
/// <summary>
/// 列表项。
/// </summary>
public IReadOnlyList<MemberMessageTemplateDto> Items { get; init; } = [];
/// <summary>
/// 总数。
/// </summary>
public int TotalCount { get; init; }
/// <summary>
/// 页码。
/// </summary>
public int Page { get; init; }
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; init; }
}
/// <summary>
/// 模板 DTO。
/// </summary>
public sealed class MemberMessageTemplateDto
{
/// <summary>
/// 模板标识。
/// </summary>
public long TemplateId { get; init; }
/// <summary>
/// 模板名称。
/// </summary>
public string Name { get; init; } = string.Empty;
/// <summary>
/// 模板分类。
/// </summary>
public string Category { get; init; } = string.Empty;
/// <summary>
/// 模板内容。
/// </summary>
public string Content { get; init; } = string.Empty;
/// <summary>
/// 使用次数。
/// </summary>
public int UsageCount { get; init; }
/// <summary>
/// 最近使用时间UTC
/// </summary>
public DateTime? LastUsedAt { get; init; }
}
/// <summary>
/// 目标人群估算 DTO。
/// </summary>
public sealed class MemberMessageAudienceEstimateDto
{
/// <summary>
/// 预计触达人数。
/// </summary>
public int ReachCount { get; init; }
}
/// <summary>
/// 消息调度元信息 DTO。
/// </summary>
public sealed class MemberMessageDispatchMetaDto
{
/// <summary>
/// 消息标识。
/// </summary>
public long MessageId { get; init; }
/// <summary>
/// 发送状态。
/// </summary>
public string Status { get; init; } = string.Empty;
/// <summary>
/// 发送时间类型。
/// </summary>
public string ScheduleType { get; init; } = string.Empty;
/// <summary>
/// 定时发送时间UTC
/// </summary>
public DateTime? ScheduledAt { get; init; }
/// <summary>
/// Hangfire 任务 ID。
/// </summary>
public string? HangfireJobId { get; init; }
}
/// <summary>
/// 保存消息请求输入。
/// </summary>
public sealed class SaveMemberMessageInput
{
/// <summary>
/// 消息标识。
/// </summary>
public long? MessageId { get; init; }
/// <summary>
/// 门店标识。
/// </summary>
public long? StoreId { get; init; }
/// <summary>
/// 模板标识。
/// </summary>
public long? TemplateId { get; init; }
/// <summary>
/// 标题。
/// </summary>
public string Title { get; init; } = string.Empty;
/// <summary>
/// 内容。
/// </summary>
public string Content { get; init; } = string.Empty;
/// <summary>
/// 渠道。
/// </summary>
public IReadOnlyList<string> Channels { get; init; } = [];
/// <summary>
/// 目标类型。
/// </summary>
public string AudienceType { get; init; } = string.Empty;
/// <summary>
/// 目标标签。
/// </summary>
public IReadOnlyList<string> AudienceTags { get; init; } = [];
/// <summary>
/// 发送时间类型。
/// </summary>
public string ScheduleType { get; init; } = string.Empty;
/// <summary>
/// 定时发送时间UTC
/// </summary>
public DateTime? ScheduledAt { get; init; }
/// <summary>
/// 提交动作draft/send
/// </summary>
public string SubmitAction { get; init; } = "draft";
}
/// <summary>
/// 搜索消息输入。
/// </summary>
public sealed class SearchMemberMessageInput
{
/// <summary>
/// 状态过滤。
/// </summary>
public string? Status { get; init; }
/// <summary>
/// 渠道过滤。
/// </summary>
public string? Channel { get; init; }
/// <summary>
/// 标题关键词。
/// </summary>
public string? Keyword { get; init; }
/// <summary>
/// 页码。
/// </summary>
public int Page { get; init; } = 1;
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; init; } = 10;
}
/// <summary>
/// 搜索模板输入。
/// </summary>
public sealed class SearchMemberMessageTemplateInput
{
/// <summary>
/// 分类。
/// </summary>
public string? Category { get; init; }
/// <summary>
/// 关键词。
/// </summary>
public string? Keyword { get; init; }
/// <summary>
/// 页码。
/// </summary>
public int Page { get; init; } = 1;
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; init; } = 10;
}
/// <summary>
/// 保存模板输入。
/// </summary>
public sealed class SaveMemberMessageTemplateInput
{
/// <summary>
/// 模板标识。
/// </summary>
public long? TemplateId { get; init; }
/// <summary>
/// 模板名称。
/// </summary>
public string Name { get; init; } = string.Empty;
/// <summary>
/// 模板分类。
/// </summary>
public string Category { get; init; } = string.Empty;
/// <summary>
/// 模板内容。
/// </summary>
public string Content { get; init; } = string.Empty;
}
/// <summary>
/// 估算人群输入。
/// </summary>
public sealed class MemberMessageAudienceEstimateInput
{
/// <summary>
/// 目标类型。
/// </summary>
public string AudienceType { get; init; } = string.Empty;
/// <summary>
/// 标签列表。
/// </summary>
public IReadOnlyList<string> Tags { get; init; } = [];
}