586 lines
19 KiB
C#
586 lines
19 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel.DataAnnotations;
|
||
using FreeSql.DataAnnotations;
|
||
using ZhonTai.Admin.Core.Entities;
|
||
|
||
namespace NPP.SmartSchedue.Api.Contracts.Domain.Integration
|
||
{
|
||
/// <summary>
|
||
/// 任务变更事件实体
|
||
/// 记录任务变更事件,用于变更感知和自动触发机制
|
||
///
|
||
/// 业务思考:
|
||
/// 1. 事件驱动架构:基于事件的变更检测和处理机制
|
||
/// 2. 变更溯源:完整记录变更的前因后果和处理过程
|
||
/// 3. 批量处理:支持批量变更事件的聚合处理
|
||
/// 4. 性能优化:通过事件队列机制避免实时处理对系统性能的影响
|
||
/// </summary>
|
||
[Table(Name = "sse_task_change_event")]
|
||
[Index("idx_task_id", "TaskId")] // 任务ID索引
|
||
[Index("idx_integration_record", "IntegrationRecordId")] // 整合记录索引
|
||
[Index("idx_event_time", "EventTime")] // 事件时间索引
|
||
[Index("idx_processing_status", "ProcessingStatus")] // 处理状态索引
|
||
[Index("idx_tenant_time", "TenantId,EventTime")] // 租户+时间组合索引
|
||
public class TaskChangeEventEntity : EntityTenant
|
||
{
|
||
/// <summary>
|
||
/// 任务ID
|
||
/// 发生变更的任务ID
|
||
/// </summary>
|
||
[Required]
|
||
public long TaskId { get; set; }
|
||
|
||
/// <summary>
|
||
/// 任务代码
|
||
/// 冗余存储,提高查询性能
|
||
/// </summary>
|
||
[Column(StringLength = 50)]
|
||
[Required]
|
||
public string TaskCode { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 关联的整合记录ID
|
||
/// 指向受影响的整合记录
|
||
/// </summary>
|
||
public long? IntegrationRecordId { get; set; }
|
||
|
||
/// <summary>
|
||
/// 根版本整合记录ID
|
||
/// 指向版本链的根节点,用于快速定位整个版本链
|
||
/// </summary>
|
||
public long? RootIntegrationRecordId { get; set; }
|
||
|
||
/// <summary>
|
||
/// 事件类型
|
||
/// TaskModified, TaskDeleted, TaskStatusChanged, TaskPriorityChanged,
|
||
/// TaskTimeChanged, TaskPersonnelChanged, TaskEquipmentChanged, TaskCompleted
|
||
/// </summary>
|
||
[Column(StringLength = 50)]
|
||
[Required]
|
||
public string EventType { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 变更字段名称
|
||
/// 具体发生变更的字段名,多个字段用逗号分隔
|
||
/// </summary>
|
||
[Column(StringLength = 200)]
|
||
public string ChangedFields { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 变更前的值(JSON格式)
|
||
/// 存储变更前的字段值快照
|
||
/// </summary>
|
||
[Column(DbType = "json")]
|
||
public string BeforeValueJson { get; set; } = "{}";
|
||
|
||
/// <summary>
|
||
/// 变更后的值(JSON格式)
|
||
/// 存储变更后的字段值快照
|
||
/// </summary>
|
||
[Column(DbType = "json")]
|
||
public string AfterValueJson { get; set; } = "{}";
|
||
|
||
/// <summary>
|
||
/// 事件发生时间
|
||
/// 任务实际发生变更的时间
|
||
/// </summary>
|
||
[Required]
|
||
public DateTime EventTime { get; set; } = DateTime.Now;
|
||
|
||
/// <summary>
|
||
/// 变更触发用户ID
|
||
/// 执行变更操作的用户ID
|
||
/// </summary>
|
||
public long? TriggeredByUserId { get; set; }
|
||
|
||
/// <summary>
|
||
/// 变更触发用户名
|
||
/// </summary>
|
||
[Column(StringLength = 50)]
|
||
public string TriggeredByUserName { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 变更触发用户真实姓名
|
||
/// </summary>
|
||
[Column(StringLength = 50)]
|
||
public string TriggeredByRealName { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 事件来源
|
||
/// Manual-手动操作, System-系统自动, Import-数据导入, API-API调用, Sync-同步操作
|
||
/// </summary>
|
||
[Column(StringLength = 20)]
|
||
[Required]
|
||
public string EventSource { get; set; } = "Manual";
|
||
|
||
/// <summary>
|
||
/// 事件优先级
|
||
/// 1-9,9为最高优先级,影响处理顺序
|
||
/// </summary>
|
||
[Required]
|
||
public int EventPriority { get; set; } = 5;
|
||
|
||
/// <summary>
|
||
/// 影响评估评分
|
||
/// 0-100分,评估该变更对整合记录的潜在影响
|
||
/// </summary>
|
||
public int ImpactAssessmentScore { get; set; } = 0;
|
||
|
||
/// <summary>
|
||
/// 处理状态
|
||
/// Pending-待处理, Processing-处理中, Completed-已完成, Failed-处理失败, Ignored-已忽略
|
||
/// </summary>
|
||
[Column(StringLength = 20)]
|
||
[Required]
|
||
public string ProcessingStatus { get; set; } = "Pending";
|
||
|
||
/// <summary>
|
||
/// 处理开始时间
|
||
/// </summary>
|
||
public DateTime? ProcessingStartTime { get; set; }
|
||
|
||
/// <summary>
|
||
/// 处理完成时间
|
||
/// </summary>
|
||
public DateTime? ProcessingCompletedTime { get; set; }
|
||
|
||
/// <summary>
|
||
/// 处理人员用户ID
|
||
/// 实际处理该事件的用户ID
|
||
/// </summary>
|
||
public long? ProcessedByUserId { get; set; }
|
||
|
||
/// <summary>
|
||
/// 处理人员用户名
|
||
/// </summary>
|
||
[Column(StringLength = 50)]
|
||
public string ProcessedByUserName { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 处理结果摘要
|
||
/// 简述处理结果和采取的行动
|
||
/// </summary>
|
||
[Column(StringLength = 500)]
|
||
public string ProcessingResultSummary { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 处理详情JSON
|
||
/// 存储详细的处理过程和结果信息
|
||
/// </summary>
|
||
[Column(DbType = "json")]
|
||
public string ProcessingDetailsJson { get; set; } = "{}";
|
||
|
||
/// <summary>
|
||
/// 是否触发了重新分配
|
||
/// </summary>
|
||
[Required]
|
||
public bool TriggeredReallocation { get; set; } = false;
|
||
|
||
/// <summary>
|
||
/// 创建的新版本ID
|
||
/// 如果该事件导致创建了新版本
|
||
/// </summary>
|
||
public long? CreatedNewVersionId { get; set; }
|
||
|
||
/// <summary>
|
||
/// 事件分组ID
|
||
/// 用于将相关的多个事件分组处理,如批量修改
|
||
/// </summary>
|
||
public long? EventGroupId { get; set; }
|
||
|
||
/// <summary>
|
||
/// 事件分组序号
|
||
/// 在同一分组内的事件序号
|
||
/// </summary>
|
||
public int EventGroupSequence { get; set; } = 1;
|
||
|
||
/// <summary>
|
||
/// 是否需要通知
|
||
/// 标记该事件是否需要发送通知给相关人员
|
||
/// </summary>
|
||
[Required]
|
||
public bool RequiresNotification { get; set; } = true;
|
||
|
||
/// <summary>
|
||
/// 通知状态
|
||
/// NotSent-未发送, Sent-已发送, Failed-发送失败, NotRequired-无需发送
|
||
/// </summary>
|
||
[Column(StringLength = 20)]
|
||
[Required]
|
||
public string NotificationStatus { get; set; } = "NotSent";
|
||
|
||
/// <summary>
|
||
/// 通知发送时间
|
||
/// </summary>
|
||
public DateTime? NotificationSentTime { get; set; }
|
||
|
||
/// <summary>
|
||
/// 重试次数
|
||
/// 处理失败后的重试次数
|
||
/// </summary>
|
||
[Required]
|
||
public int RetryCount { get; set; } = 0;
|
||
|
||
/// <summary>
|
||
/// 最大重试次数
|
||
/// </summary>
|
||
[Required]
|
||
public int MaxRetryCount { get; set; } = 3;
|
||
|
||
/// <summary>
|
||
/// 下次重试时间
|
||
/// </summary>
|
||
public DateTime? NextRetryTime { get; set; }
|
||
|
||
/// <summary>
|
||
/// 错误信息
|
||
/// 记录处理过程中的错误信息
|
||
/// </summary>
|
||
[Column(StringLength = 1000)]
|
||
public string ErrorMessage { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 事件标签
|
||
/// 用于事件分类和筛选,多个标签用逗号分隔
|
||
/// </summary>
|
||
[Column(StringLength = 200)]
|
||
public string EventTags { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 业务备注
|
||
/// </summary>
|
||
[Column(StringLength = 500)]
|
||
public string Remarks { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 是否为测试事件
|
||
/// </summary>
|
||
[Required]
|
||
public bool IsTestEvent { get; set; } = false;
|
||
|
||
/// <summary>
|
||
/// 数据版本
|
||
/// </summary>
|
||
[Required]
|
||
public int DataVersion { get; set; } = 1;
|
||
|
||
#region 计算属性
|
||
|
||
/// <summary>
|
||
/// 处理耗时(毫秒)
|
||
/// </summary>
|
||
[Navigate(nameof(ProcessingStartTime))]
|
||
public long ProcessingElapsedMilliseconds
|
||
{
|
||
get
|
||
{
|
||
if (ProcessingStartTime.HasValue && ProcessingCompletedTime.HasValue)
|
||
{
|
||
return (long)(ProcessingCompletedTime.Value - ProcessingStartTime.Value).TotalMilliseconds;
|
||
}
|
||
return 0;
|
||
}
|
||
set { } // FreeSql需要set访问器,但这是计算属性,所以提供空实现
|
||
}
|
||
|
||
/// <summary>
|
||
/// 事件年龄(自事件发生到现在的分钟数)
|
||
/// </summary>
|
||
[Navigate(nameof(EventTime))]
|
||
public int EventAgeMinutes
|
||
{
|
||
get
|
||
{
|
||
return (int)(DateTime.Now - EventTime).TotalMinutes;
|
||
}
|
||
set { } // FreeSql需要set访问器,但这是计算属性,所以提供空实现
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否超时
|
||
/// 判断事件是否长时间未处理
|
||
/// </summary>
|
||
[Navigate(nameof(EventTime))]
|
||
public bool IsOverdue
|
||
{
|
||
get
|
||
{
|
||
if (ProcessingStatus == "Completed" || ProcessingStatus == "Ignored")
|
||
return false;
|
||
|
||
var timeoutMinutes = EventPriority >= 8 ? 30 : EventPriority >= 5 ? 120 : 480; // 高优先级30分钟,中等2小时,低优先级8小时
|
||
return EventAgeMinutes > timeoutMinutes;
|
||
}
|
||
set { } // FreeSql需要set访问器,但这是计算属性,所以提供空实现
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 业务方法
|
||
|
||
/// <summary>
|
||
/// 获取变更前的值
|
||
/// 将JSON反序列化为字典
|
||
/// </summary>
|
||
public Dictionary<string, object> GetBeforeValues()
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(BeforeValueJson) || BeforeValueJson == "{}")
|
||
return new Dictionary<string, object>();
|
||
|
||
return System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(BeforeValueJson)
|
||
?? new Dictionary<string, object>();
|
||
}
|
||
catch
|
||
{
|
||
return new Dictionary<string, object>();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取变更后的值
|
||
/// 将JSON反序列化为字典
|
||
/// </summary>
|
||
public Dictionary<string, object> GetAfterValues()
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(AfterValueJson) || AfterValueJson == "{}")
|
||
return new Dictionary<string, object>();
|
||
|
||
return System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(AfterValueJson)
|
||
?? new Dictionary<string, object>();
|
||
}
|
||
catch
|
||
{
|
||
return new Dictionary<string, object>();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置变更前的值
|
||
/// </summary>
|
||
public void SetBeforeValues(Dictionary<string, object> values)
|
||
{
|
||
BeforeValueJson = System.Text.Json.JsonSerializer.Serialize(values ?? new Dictionary<string, object>());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置变更后的值
|
||
/// </summary>
|
||
public void SetAfterValues(Dictionary<string, object> values)
|
||
{
|
||
AfterValueJson = System.Text.Json.JsonSerializer.Serialize(values ?? new Dictionary<string, object>());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取处理详情
|
||
/// </summary>
|
||
public Dictionary<string, object> GetProcessingDetails()
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(ProcessingDetailsJson) || ProcessingDetailsJson == "{}")
|
||
return new Dictionary<string, object>();
|
||
|
||
return System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(ProcessingDetailsJson)
|
||
?? new Dictionary<string, object>();
|
||
}
|
||
catch
|
||
{
|
||
return new Dictionary<string, object>();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置处理详情
|
||
/// </summary>
|
||
public void SetProcessingDetails(Dictionary<string, object> details)
|
||
{
|
||
ProcessingDetailsJson = System.Text.Json.JsonSerializer.Serialize(details ?? new Dictionary<string, object>());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开始处理事件
|
||
/// 设置处理开始时间和状态
|
||
/// </summary>
|
||
public void StartProcessing(long processedByUserId, string processedByUserName)
|
||
{
|
||
ProcessingStatus = "Processing";
|
||
ProcessingStartTime = DateTime.Now;
|
||
ProcessedByUserId = processedByUserId;
|
||
ProcessedByUserName = processedByUserName;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 完成处理事件
|
||
/// 设置处理完成时间和状态
|
||
/// </summary>
|
||
public void CompleteProcessing(string resultSummary, bool triggeredReallocation = false, long? createdVersionId = null)
|
||
{
|
||
ProcessingStatus = "Completed";
|
||
ProcessingCompletedTime = DateTime.Now;
|
||
ProcessingResultSummary = resultSummary;
|
||
TriggeredReallocation = triggeredReallocation;
|
||
CreatedNewVersionId = createdVersionId;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理失败
|
||
/// 设置失败状态和错误信息
|
||
/// </summary>
|
||
public void FailProcessing(string errorMessage)
|
||
{
|
||
ProcessingStatus = "Failed";
|
||
ProcessingCompletedTime = DateTime.Now;
|
||
ErrorMessage = errorMessage;
|
||
RetryCount++;
|
||
|
||
// 计算下次重试时间(指数退避)
|
||
if (RetryCount <= MaxRetryCount)
|
||
{
|
||
var delayMinutes = Math.Pow(2, RetryCount) * 5; // 5, 10, 20 分钟
|
||
NextRetryTime = DateTime.Now.AddMinutes(delayMinutes);
|
||
ProcessingStatus = "Pending"; // 重置为待处理状态以便重试
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 忽略事件
|
||
/// 设置忽略状态,不再处理
|
||
/// </summary>
|
||
public void IgnoreEvent(string reason)
|
||
{
|
||
ProcessingStatus = "Ignored";
|
||
ProcessingCompletedTime = DateTime.Now;
|
||
ProcessingResultSummary = $"事件已忽略: {reason}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断是否可以重试
|
||
/// </summary>
|
||
public bool CanRetry()
|
||
{
|
||
return ProcessingStatus == "Pending" &&
|
||
RetryCount < MaxRetryCount &&
|
||
(!NextRetryTime.HasValue || NextRetryTime.Value <= DateTime.Now);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算影响评估评分
|
||
/// 基于变更类型、字段和业务规则计算影响评分
|
||
/// </summary>
|
||
public int CalculateImpactScore()
|
||
{
|
||
int score = 0;
|
||
|
||
// 基于事件类型的基础分数
|
||
score += EventType switch
|
||
{
|
||
"TaskDeleted" => 90,
|
||
"TaskStatusChanged" => 80,
|
||
"TaskTimeChanged" => 70,
|
||
"TaskPriorityChanged" => 60,
|
||
"TaskPersonnelChanged" => 75,
|
||
"TaskEquipmentChanged" => 75,
|
||
"TaskModified" => 50,
|
||
"TaskCompleted" => 30,
|
||
_ => 20
|
||
};
|
||
|
||
// 基于变更字段的影响调整
|
||
if (ChangedFields.Contains("WorkOrderDate")) score += 20;
|
||
if (ChangedFields.Contains("ShiftId")) score += 15;
|
||
if (ChangedFields.Contains("Priority")) score += 10;
|
||
if (ChangedFields.Contains("AssignedPersonnelId")) score += 15;
|
||
if (ChangedFields.Contains("AssignedEquipmentId")) score += 15;
|
||
if (ChangedFields.Contains("Status")) score += 25;
|
||
|
||
// 基于事件优先级调整
|
||
score += EventPriority * 2;
|
||
|
||
return Math.Min(100, Math.Max(0, score));
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
/// <summary>
|
||
/// 事件处理状态枚举
|
||
/// </summary>
|
||
public static class TaskChangeEventProcessingStatus
|
||
{
|
||
/// <summary>
|
||
/// 待处理
|
||
/// </summary>
|
||
public const string Pending = "Pending";
|
||
|
||
/// <summary>
|
||
/// 处理中
|
||
/// </summary>
|
||
public const string Processing = "Processing";
|
||
|
||
/// <summary>
|
||
/// 已完成
|
||
/// </summary>
|
||
public const string Completed = "Completed";
|
||
|
||
/// <summary>
|
||
/// 处理失败
|
||
/// </summary>
|
||
public const string Failed = "Failed";
|
||
|
||
/// <summary>
|
||
/// 已忽略
|
||
/// </summary>
|
||
public const string Ignored = "Ignored";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 事件类型枚举
|
||
/// </summary>
|
||
public static class TaskChangeEventType
|
||
{
|
||
/// <summary>
|
||
/// 任务修改
|
||
/// </summary>
|
||
public const string TaskModified = "TaskModified";
|
||
|
||
/// <summary>
|
||
/// 任务删除
|
||
/// </summary>
|
||
public const string TaskDeleted = "TaskDeleted";
|
||
|
||
/// <summary>
|
||
/// 任务状态变更
|
||
/// </summary>
|
||
public const string TaskStatusChanged = "TaskStatusChanged";
|
||
|
||
/// <summary>
|
||
/// 任务优先级变更
|
||
/// </summary>
|
||
public const string TaskPriorityChanged = "TaskPriorityChanged";
|
||
|
||
/// <summary>
|
||
/// 任务时间变更
|
||
/// </summary>
|
||
public const string TaskTimeChanged = "TaskTimeChanged";
|
||
|
||
/// <summary>
|
||
/// 任务人员变更
|
||
/// </summary>
|
||
public const string TaskPersonnelChanged = "TaskPersonnelChanged";
|
||
|
||
/// <summary>
|
||
/// 任务设备变更
|
||
/// </summary>
|
||
public const string TaskEquipmentChanged = "TaskEquipmentChanged";
|
||
|
||
/// <summary>
|
||
/// 任务完成
|
||
/// </summary>
|
||
public const string TaskCompleted = "TaskCompleted";
|
||
}
|
||
} |