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; }
}