Asoka.Wang 21f044712c 1
2025-08-27 18:39:19 +08:00

586 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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-99为最高优先级影响处理顺序
/// </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";
}
}