using System.Diagnostics; namespace Admin.NET.Plugin.ApprovalFlow.Service; /// /// 流程通知服务(P4-16 重构)— 聚合所有 INotifyPusher,按配置 + 渠道启用状态并行调度, /// 每个渠道的发送结果写入 ApprovalFlowNotifyLog 便于运维追溯与降级审计。 /// public class FlowNotifyService : ITransient { private readonly IEnumerable _pushers; private readonly SqlSugarRepository _logRep; public FlowNotifyService( IEnumerable pushers, SqlSugarRepository logRep) { _pushers = pushers; _logRep = logRep; } public async Task NotifyUsers(List userIds, FlowNotification notification) { if (userIds.Count == 0) return; var cfg = App.GetConfig("ApprovalFlow:Notify") ?? new NotifyChannelConfig(); foreach (var pusher in _pushers) { if (!pusher.IsEnabled(cfg)) continue; var sw = Stopwatch.StartNew(); FlowNotifyPushResult result; try { result = await pusher.PushAsync(userIds, notification); } catch (Exception ex) { result = FlowNotifyPushResult.Fail(ex.Message); } sw.Stop(); await SafeWriteLog(notification, pusher.Channel, userIds, result, (int)sw.ElapsedMilliseconds); } } public async Task NotifyUrge(List userIds, long instanceId, string title) { await NotifyUsers(userIds, new FlowNotification { Type = FlowNotificationTypeEnum.Urge, InstanceId = instanceId, Title = $"【催办】{title}", Content = "流程发起人催促您尽快审批,请及时处理。", }); } public async Task NotifyNewTask(List userIds, long instanceId, string title, string? nodeName) { await NotifyUsers(userIds, new FlowNotification { Type = FlowNotificationTypeEnum.NewTask, InstanceId = instanceId, Title = $"【待审批】{title}", Content = $"您有一条新的审批任务({nodeName}),请及时处理。", }); } public async Task NotifyFlowCompleted(long initiatorId, long instanceId, string title, FlowInstanceStatusEnum finalStatus) { var statusText = finalStatus == FlowInstanceStatusEnum.Approved ? "已通过" : "已拒绝"; await NotifyUsers(new List { initiatorId }, new FlowNotification { Type = FlowNotificationTypeEnum.FlowCompleted, InstanceId = instanceId, Title = $"【审批{statusText}】{title}", Content = $"您发起的审批流程已{statusText}。", }); } /// /// 转办通知 — 通知被转办人 /// public async Task NotifyTransferred(long targetUserId, long instanceId, string title, string? fromName) { await NotifyUsers(new List { targetUserId }, new FlowNotification { Type = FlowNotificationTypeEnum.Transferred, InstanceId = instanceId, Title = $"【转办】{title}", Content = $"{fromName} 将一条审批任务转交给您,请及时处理。", }); } /// /// 退回通知 — 通知被退回节点的审批人 /// public async Task NotifyReturned(List userIds, long instanceId, string title, string? returnedBy) { await NotifyUsers(userIds, new FlowNotification { Type = FlowNotificationTypeEnum.Returned, InstanceId = instanceId, Title = $"【退回】{title}", Content = $"{returnedBy} 已退回该审批,请重新审核。", }); } /// /// 加签通知 — 通知被加签人 /// public async Task NotifyAddSign(long targetUserId, long instanceId, string title, string? fromName) { await NotifyUsers(new List { targetUserId }, new FlowNotification { Type = FlowNotificationTypeEnum.AddSign, InstanceId = instanceId, Title = $"【加签】{title}", Content = $"{fromName} 邀请您参与审批,请及时处理。", }); } /// /// 升级通知 — 通知升级目标人 /// public async Task NotifyEscalated(List targetUserIds, long instanceId, string title, string? fromName, string? nodeName) { await NotifyUsers(targetUserIds, new FlowNotification { Type = FlowNotificationTypeEnum.Escalated, InstanceId = instanceId, Title = $"【升级】{title}", Content = $"{fromName} 将审批任务({nodeName})升级给您,请及时处理。", }); } /// /// 超时提醒通知 /// public async Task NotifyTimeout(List userIds, long instanceId, string title) { await NotifyUsers(userIds, new FlowNotification { Type = FlowNotificationTypeEnum.Timeout, InstanceId = instanceId, Title = $"【超时提醒】{title}", Content = "您有一条审批任务已超时,请尽快处理。", }); } /// /// 撤回通知 — 通知被取消的审批人 /// public async Task NotifyWithdrawn(List userIds, long instanceId, string title, string? initiatorName) { await NotifyUsers(userIds, new FlowNotification { Type = FlowNotificationTypeEnum.Withdrawn, InstanceId = instanceId, Title = $"【已撤回】{title}", Content = $"{initiatorName} 已撤回该审批流程。", }); } // ═══════════════════════════════════════════ // 日志写入 // ═══════════════════════════════════════════ private async Task SafeWriteLog(FlowNotification notification, string channel, List userIds, FlowNotifyPushResult result, int elapsedMs) { try { var csv = string.Join(",", userIds); if (csv.Length > 1024) csv = csv.Substring(0, 1020) + "..."; var err = result.ErrorMessage; if (!string.IsNullOrEmpty(err) && err.Length > 1024) err = err.Substring(0, 1020) + "..."; await _logRep.InsertAsync(new ApprovalFlowNotifyLog { InstanceId = notification.InstanceId, NotifyType = notification.Type.ToString(), Channel = channel, Title = notification.Title, TargetUserIds = csv, TargetCount = userIds.Count, Success = result.Success, ErrorMessage = err, ElapsedMs = elapsedMs, }); } catch { // 日志写入失败不能影响主流程 } } } public class FlowNotification { public FlowNotificationTypeEnum Type { get; set; } public long InstanceId { get; set; } public string Title { get; set; } = ""; public string Content { get; set; } = ""; } /// /// 通知类型 /// public enum FlowNotificationTypeEnum { NewTask, Urge, FlowCompleted, Transferred, Returned, AddSign, Withdrawn, Escalated, Timeout, } /// /// 通知渠道配置(来自 ApprovalFlow:Notify JSON) /// public class NotifyChannelConfig { public bool SignalR { get; set; } = true; public bool DingTalk { get; set; } public bool WorkWeixin { get; set; } public bool Email { get; set; } public bool Sms { get; set; } /// P4-16: 钉钉群机器人 Webhook URL(含 access_token) public string? DingTalkWebhookUrl { get; set; } /// P4-16: 钉钉群机器人加签 Secret(可选,启用加签时必填) public string? DingTalkSecret { get; set; } /// P4-16: 企业微信群机器人 Webhook URL(含 key) public string? WorkWeixinWebhookUrl { get; set; } /// P4-16: 短信运营商侧的通知模板 Id(阿里云/腾讯云/自定义) public string? SmsTemplateId { get; set; } }